"""A-1 WebSocket 실시간 대시보드 테스트""" import sys, ast, os, asyncio, json os.environ.setdefault("GUARDIA_SECRET_KEY", "test-ws-secret-key-32bytes-pad!!!") os.environ.setdefault("DATABASE_URL", "sqlite+aiosqlite:///./test_ws.db") print("=== 1. 구문 검사 ===") files = ["routers/ws.py", "main.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) print("\n=== 2. ConnectionManager 단위 테스트 ===") # ws.py 임포트 (DB 의존성 없는 클래스만 테스트) import importlib.util spec = importlib.util.spec_from_file_location("ws_mod", "routers/ws.py") ws_mod = importlib.util.module_from_spec(spec) try: spec.loader.exec_module(ws_mod) manager = ws_mod.ConnectionManager() # 채널 정의 확인 channels = ws_mod._CHANNELS assert "all" in channels assert "sr" in channels assert "sla" in channels assert "deploy" in channels assert "oncall" in channels assert "batch" in channels print(f" OK 채널 정의: {sorted(channels)}") # _CHANNEL_EVENT_MAP 확인 event_map = ws_mod._CHANNEL_EVENT_MAP assert "sr_created" in event_map["sr"] assert "sla_violation" in event_map["sla"] assert event_map["all"] is None # all = 모든 이벤트 print(f" OK 채널 이벤트 매핑 확인") # connection_count 초기값 assert manager.connection_count() == 0 print(f" OK connection_count 초기값 = 0") # connections_info 빈 목록 assert manager.connections_info() == [] print(f" OK connections_info 빈 목록") except Exception as e: print(f" INFO 외부 의존성 에러 (정상): {type(e).__name__}: {str(e)[:80]}") print("\n=== 3. 이벤트 브로드캐스트 단위 테스트 ===") async def test_broadcast(): from routers.ws import ConnectionManager, _CHANNEL_EVENT_MAP # 더미 WebSocket 객체 (실제 연결 없이) class MockWS: def __init__(self): self.messages = [] self.closed = False async def accept(self): pass async def send_text(self, text): self.messages.append(json.loads(text)) async def close(self, code=None, reason=None): self.closed = True mgr = ConnectionManager() # 연결 ws1 = MockWS() ws2 = MockWS() await mgr.connect(ws1, "alice", "ADMIN", "all") await mgr.connect(ws2, "bob", "ENGINEER", "sr") assert mgr.connection_count() == 2 print(f" OK 2개 연결 등록") # 전체 채널 이벤트 브로드캐스트 await mgr.broadcast("sr_created", {"sr_id": "SR-001", "title": "테스트"}) assert len(ws1.messages) == 1, f"Expected 1 msg for alice, got {len(ws1.messages)}" assert len(ws2.messages) == 1, f"Expected 1 msg for bob, got {len(ws2.messages)}" assert ws1.messages[0]["type"] == "sr_created" assert ws1.messages[0]["sr_id"] == "SR-001" print(f" OK sr_created 브로드캐스트 (2명 수신)") # deploy 이벤트 → sr 채널 구독자(bob)는 수신 안함 await mgr.broadcast("deploy_completed", {"session_id": 1}) assert len(ws1.messages) == 2, f"alice should have 2 msgs, got {len(ws1.messages)}" assert len(ws2.messages) == 1, f"bob (sr channel) should still have 1 msg, got {len(ws2.messages)}" print(f" OK deploy_completed: alice 수신, bob(sr채널) 미수신") # sla_violation → sr 채널 (bob) 포함 여부 확인 # sr 채널의 allowed = {"sr_created", "sr_updated", "sr_status_changed"} # sla_violation is NOT in sr channel events await mgr.broadcast("sla_violation", {"sr_id": "SR-001"}) assert len(ws2.messages) == 1, f"bob (sr channel) should not receive sla_violation" print(f" OK sla_violation: bob(sr채널) 미수신") # disconnect mgr.disconnect(ws1) assert mgr.connection_count() == 1 print(f" OK disconnect: 1명 남음") asyncio.run(test_broadcast()) print("\n=== 4. main.py 등록 확인 ===") with open("main.py", encoding="utf-8") as f: main_src = f.read() checks = [ ("ws_router", "ws 라우터 임포트"), ("ws_router.router", "ws 라우터 등록"), ("_integrate_with_sse_bus", "SSE 통합 패치"), ] for sym, desc in checks: status = "OK" if sym in main_src else "ERR" if status == "ERR": ok = False print(f" {status} {desc} ({sym})") print("\n=== A-1 WebSocket 테스트 완료 ===") if ok: print("모든 검사 통과") else: sys.exit(1)