zioinfo-mail/workspace/guardia-itsm/test_a5_oncall.py
DESKTOP-TKLFCPR\ython cfe2901a55 refactor(structure): consolidate all projects under workspace/
- itsm/    -> workspace/guardia-itsm/
- manager/ -> workspace/guardia-manager/
- app/     -> workspace/guardia-messenger/
- manual/  -> workspace/guardia-docs/

workspace/zioinfo-web/ unchanged.
git mv preserves full commit history.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 23:50:56 +09:00

143 lines
5.6 KiB
Python

"""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)