Commit 2d5219ad authored by DevPilot's avatar DevPilot

fix(core): findOrFail returns 404 instead of 500, add route smoke tester

كل رابط لسجل محذوف أو مش موجود كان بيطلّع صفحة 500 لأن findOrFail
بيرمي RuntimeException من غير code، والـ ExceptionHandler بيعتبر أي
code غير 401/403/404 عطل سيرفر.

tools/route_smoke.py بيفتح كل GET route في النظام (٨٣٠ شاشة) ويبلّغ
عن أي 500 برسالة الخطأ.
parent c9938287
......@@ -47,7 +47,12 @@ abstract class Model
{
$instance = static::find($id);
if ($instance === null) {
throw new \RuntimeException("Record not found in " . static::$table . " with id {$id}");
// السجل مش موجود = 404، مش عطل في السيرفر. من غير الكود ده كل
// رابط لسجل محذوف كان بيطلّع صفحة 500.
throw new \RuntimeException(
"Record not found in " . static::$table . " with id {$id}",
404
);
}
return $instance;
}
......
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
فحص دخان لكل شاشات النظام — بيفتح كل GET route ويبلّغ عن أي 500.
الفكرة إن أغلب الأعطال بتتكتشف لما حد يفتح الشاشة لأول مرة. الأداة دي
بتفتحهم كلهم مرة واحدة بدل ما نستنى المستخدم يقع فيهم.
python3 tools/route_smoke.py # الموقع الحي
python3 tools/route_smoke.py --base http://localhost:8080
python3 tools/route_smoke.py --module Accounting # موديول واحد بس
بيطبع كل route رجّع 500 مع أول سطر من رسالة الخطأ.
"""
import argparse
import glob
import os
import re
import sys
import requests
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
DEFAULT_BASE = 'https://clubmanagement.caprover.al-arcade.com'
# routes مش المفروض نضربها في فحص آلي
SKIP = re.compile(r'/(logout|export|download|print|pdf|backup|restore|cron|webhook)\b', re.I)
def discover_routes(module_filter=None):
routes = []
for f in sorted(glob.glob(os.path.join(ROOT, 'app/Modules/*/Routes.php'))):
module = f.split(os.sep)[-2]
if module_filter and module.lower() != module_filter.lower():
continue
src = open(f, encoding='utf-8').read()
for m in re.finditer(r"\[\s*'(GET)'\s*,\s*'([^']+)'", src):
routes.append((m.group(2), module))
return routes
def fill_params(path, ids):
"""بيحط أرقام حقيقية مكان الـ route params."""
def repl(m):
name = m.group(1)
return str(ids.get(name, ids.get('id', 1)))
return re.sub(r'\{(\w+)(?::[^}]+)?\}', repl, path)
def login(session, base, username, password):
r = session.get(f'{base}/login', timeout=30)
token = ''
m = re.search(r'name="_csrf_token"\s+value="([^"]+)"', r.text)
if m:
token = m.group(1)
r = session.post(f'{base}/login',
data={'username': username, 'password': password, '_csrf_token': token},
timeout=30, allow_redirects=True)
ok = '/login' not in r.url
return ok
def error_summary(html):
m = re.search(r'Message:\s*</strong>?\s*([^<\n]{0,160})', html)
if m:
return m.group(1).strip()
m = re.search(r'(SQLSTATE\[[^\]]+\][^<\n]{0,140})', html)
if m:
return m.group(1).strip()
m = re.search(r'Exception:\s*</strong>?\s*([^<\n]{0,80})', html)
return m.group(1).strip() if m else ''
def main():
ap = argparse.ArgumentParser()
ap.add_argument('--base', default=DEFAULT_BASE)
ap.add_argument('--module')
ap.add_argument('--user', default='admin')
ap.add_argument('--password', default=os.environ.get('SMOKE_PASSWORD', ''))
ap.add_argument('--limit', type=int, default=0)
args = ap.parse_args()
if not args.password:
print('set --password or SMOKE_PASSWORD', file=sys.stderr)
return 2
s = requests.Session()
s.headers['User-Agent'] = 'club-route-smoke/1.0'
if not login(s, args.base, args.user, args.password):
print('login failed', file=sys.stderr)
return 2
print('logged in\n')
routes = discover_routes(args.module)
if args.limit:
routes = routes[:args.limit]
ids = {'id': 1}
failures, checked, skipped = [], 0, 0
for path, module in routes:
if SKIP.search(path):
skipped += 1
continue
url = args.base + fill_params(path, ids)
try:
r = s.get(url, timeout=45, allow_redirects=True)
except Exception as e:
failures.append((module, path, 'ERR', str(e)[:120]))
continue
checked += 1
if r.status_code >= 500:
failures.append((module, path, r.status_code, error_summary(r.text)))
print(f' [{r.status_code}] {module:22} {path}\n {error_summary(r.text)}')
print(f'\nchecked {checked}, skipped {skipped}, failures {len(failures)}')
if failures:
print('\n--- summary ---')
for module, path, code, msg in failures:
print(f'{code} {module:22} {path}\n {msg}')
return 1 if failures else 0
if __name__ == '__main__':
sys.exit(main())
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment