"""A-5 On-Call 자동 로테이션 테스트""" import sys, ast, os, json os.environ.setdefault("GUARDIA_SECRET_KEY", "test-secret-key-32bytes-padding!!") os.environ.setdefault("DATABASE_URL", "sqlite+aiosqlite:///./test_a5.db") # ── 1. 구문 검사 ───────────────────────────────────────────────────────────── print("=== 1. 구문 검사 ===") files = [ "core/oncall_rotate.py", "routers/oncall.py", "models.py", "core/scheduler.py", ] ok = True for f in files: try: with open(f, encoding="utf-8") as fh: src = fh.read() ast.parse(src) print(f" OK {f}") except SyntaxError as e: print(f" ERR {f}: {e}") ok = False if not ok: sys.exit(1) # ── 2. OncallRotateConfig 모델 스키마 확인 ─────────────────────────────────── print("\n=== 2. 모델 스키마 확인 ===") from models import OncallRotateConfig, OncallRotateConfigOut, OncallRotateConfigUpdate cfg_fields = [ "id", "is_active", "engineer_list", "current_index", "rotate_days", "default_shift", "escalation_chain", "notify_on_assign", "advance_days", ] table_cols = {c.key for c in OncallRotateConfig.__table__.columns} for field in cfg_fields: status = "OK" if field in table_cols else "ERR" print(f" {status} OncallRotateConfig.{field}") out_fields = OncallRotateConfigOut.model_fields for field in ["id", "is_active", "engineer_list", "current_index", "rotate_days"]: status = "OK" if field in out_fields else "ERR" print(f" {status} OncallRotateConfigOut.{field}") # ── 3. core/oncall_rotate.py 임포트 ───────────────────────────────────────── print("\n=== 3. oncall_rotate 임포트 테스트 ===") try: from core.oncall_rotate import ( get_or_create_rotate_config, get_current_oncall, auto_rotate_oncall, escalate_oncall, _notify_oncall_assigned, ) print(" OK 모든 함수 임포트 성공") for fn_name, fn in [ ("get_or_create_rotate_config", get_or_create_rotate_config), ("get_current_oncall", get_current_oncall), ("auto_rotate_oncall", auto_rotate_oncall), ("escalate_oncall", escalate_oncall), ]: import asyncio as _asyncio import inspect if inspect.iscoroutinefunction(fn): print(f" OK {fn_name} is async") else: print(f" ERR {fn_name} is NOT async") except ImportError as e: print(f" ERR 임포트 실패: {e}") ok = False # ── 4. JSON 직렬화 로직 검증 ───────────────────────────────────────────────── print("\n=== 4. JSON 직렬화 로직 검증 ===") # engineer_list JSON 직렬화/역직렬화 engineers = ["alice", "bob", "charlie"] serialized = json.dumps(engineers, ensure_ascii=False) deserialized = json.loads(serialized) assert deserialized == engineers, "JSON roundtrip failed" print(f" OK engineer_list JSON: {serialized}") # 로테이션 인덱스 순환 for idx in range(6): next_idx = (idx + 1) % len(engineers) engineer = engineers[idx % len(engineers)] # 마지막 idx=5 → idx%3=2 → charlie, next_idx=0 assert engineer == "charlie", f"Expected charlie, got {engineer}" assert next_idx == 0, f"Expected 0, got {next_idx}" print(f" OK 로테이션 순환 인덱스 (0→1→2→0)") # advance_days 날짜 계산 from datetime import date, timedelta advance = 1 target = date.today() + timedelta(days=advance) assert target > date.today(), "target_date should be after today" print(f" OK advance_days=1 → target_date={target}") # ── 5. 라우터 엔드포인트 확인 ──────────────────────────────────────────────── print("\n=== 5. 라우터 엔드포인트 확인 ===") import importlib.util spec = importlib.util.spec_from_file_location("oncall_mod", "routers/oncall.py") oncall_mod = importlib.util.module_from_spec(spec) try: spec.loader.exec_module(oncall_mod) router = oncall_mod.router routes = {} for r in router.routes: if hasattr(r, "methods"): routes[r.path] = list(r.methods) expected = [ "/rotate/config", "/on-duty", "/escalate", "/rotate/trigger", ] for path in expected: found = any(path in r for r in routes.keys()) status = "OK" if found else "WARN" print(f" {status} 경로 존재: {path}") print(f" INFO 전체 라우트: {list(routes.keys())}") except Exception as e: print(f" INFO 라우터 로드 중 외부 의존성 에러 (정상): {type(e).__name__}: {str(e)[:80]}") # ── 6. 스케줄러 job 등록 확인 ──────────────────────────────────────────────── print("\n=== 6. scheduler.py oncall 작업 확인 ===") with open("core/scheduler.py", encoding="utf-8") as f: sched_src = f.read() if "oncall_auto_rotate" in sched_src: print(" OK oncall_auto_rotate job id 존재") if "auto_rotate_oncall" in sched_src: print(" OK auto_rotate_oncall 함수 참조 존재") if "On-Call 자동 로테이션 (00:05)" in sched_src: print(" OK job name 존재") print("\n=== 테스트 완료: A-5 On-Call 자동 로테이션 ===") if ok: print("모든 검사 통과") else: sys.exit(1)