"""B-3 코드 리뷰 에이전트 테스트""" import sys, ast, os, asyncio, json from pathlib import Path os.environ.setdefault("GUARDIA_SECRET_KEY", "test-b3-secret-key-32bytes-padded!") os.environ.setdefault("DATABASE_URL", "sqlite+aiosqlite:///./test_b3.db") os.environ.setdefault("GUARDIA_PROJECTS_ROOT", r"C:\GUARDiA\projects") print("=== 1. 구문 검사 ===") files = ["core/code_review.py", "routers/code_review.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. models.py CodeReview 모델 확인 ===") with open("models.py", encoding="utf-8") as f: models_src = f.read() checks = [ ("class CodeReview(Base):", "CodeReview DB 모델"), ("class CodeReviewOut(BaseModel):", "CodeReviewOut Pydantic"), ("class CodeReviewRequest(BaseModel):", "CodeReviewRequest"), ("class ReviewSeverity(str, Enum):", "ReviewSeverity Enum"), ("class ReviewCategory(str, Enum):", "ReviewCategory Enum"), ("findings_json", "findings_json 컬럼"), ("project_dir", "project_dir 컬럼 (Project 모델)"), ("tech_stack", "tech_stack 컬럼"), ("last_review_score", "last_review_score 컬럼"), ] for sym, desc in checks: status = "OK" if sym in models_src else "ERR" if status == "ERR": ok = False print(f" {status} {desc}") print("\n=== 3. core/code_review.py 함수 확인 ===") with open("core/code_review.py", encoding="utf-8") as f: cr_src = f.read() fn_checks = [ ("def scan_source_files(", "파일 스캔 함수"), ("def detect_tech_stack(", "기술 스택 감지"), ("async def _call_ollama(", "Ollama API 호출"), ("def _build_review_prompt(", "프롬프트 생성"), ("def _parse_findings(", "findings 파싱"), ("def _calculate_score(", "점수 산출"), ("async def run_code_review(", "메인 리뷰 실행"), ("def quick_security_scan(", "빠른 보안 스캔"), ("SECURITY_PATTERNS", "보안 패턴 목록"), ("SKIP_DIRS", "제외 디렉토리 목록"), ] for sym, desc in fn_checks: status = "OK" if sym in cr_src else "ERR" if status == "ERR": ok = False print(f" {status} {desc}") print("\n=== 4. routers/code_review.py 엔드포인트 확인 ===") with open("routers/code_review.py", encoding="utf-8") as f: router_src = f.read() endpoint_checks = [ ('@router.post("", ', "POST /api/code-review (리뷰 요청)"), ('@router.get("/projects/list")', "GET /api/code-review/projects/list"), ('@router.get("/{review_id}", ', "GET /api/code-review/{id}"), ('@router.post("/quick-scan")', "POST /api/code-review/quick-scan"), ('@router.get("/{review_id}/findings")', "GET /api/code-review/{id}/findings"), ("BackgroundTasks", "비동기 백그라운드 실행"), ("_run_review_background", "백그라운드 실행 함수"), ] for sym, desc in endpoint_checks: status = "OK" if sym in router_src else "ERR" if status == "ERR": ok = False print(f" {status} {desc}") print("\n=== 5. projects/ 디렉토리 구조 확인 ===") projects_root = Path(os.environ["GUARDIA_PROJECTS_ROOT"]) expected_projects = [ "testcase-java-api", "testcase-py-api", "testcase-js-frontend", "testcase-php-legacy", ] for proj in expected_projects: status = "OK" if (projects_root / proj).exists() else "ERR" if status == "ERR": ok = False print(f" {status} {proj}") print("\n=== 6. scan_source_files 단위 테스트 ===") try: import importlib.util spec = importlib.util.spec_from_file_location("cr_mod", "core/code_review.py") cr_mod = importlib.util.module_from_spec(spec) spec.loader.exec_module(cr_mod) for proj in expected_projects: proj_path = projects_root / proj if proj_path.exists(): files = cr_mod.scan_source_files(proj_path) stack = cr_mod.detect_tech_stack(files) print(f" OK {proj}: {len(files)}개 파일, 스택={stack}") else: print(f" SKIP {proj}: 경로 없음") except Exception as e: print(f" INFO 임포트 오류 (정상): {type(e).__name__}: {str(e)[:80]}") print("\n=== 7. quick_security_scan 단위 테스트 ===") async def test_quick_scan(): try: from core.code_review import quick_security_scan, PROJECTS_ROOT for proj in expected_projects: proj_path = PROJECTS_ROOT / proj if not proj_path.exists(): continue findings = quick_security_scan(proj_path) crit = sum(1 for f in findings if f["severity"] == "CRITICAL") high = sum(1 for f in findings if f["severity"] == "HIGH") print(f" OK {proj}: {len(findings)}건 발견 (CRITICAL={crit}, HIGH={high})") except Exception as e: print(f" INFO 스캔 오류: {type(e).__name__}: {str(e)[:80]}") asyncio.run(test_quick_scan()) print("\n=== 8. 점수 산출 로직 테스트 ===") try: from core.code_review import _calculate_score cases = [ ([], 95, "빈 findings"), ([{"severity": "INFO"}] * 5, 95, "INFO만 5건"), ([{"severity": "LOW"}] * 3, 94, "LOW 3건 (6점 감점)"), ([{"severity": "MEDIUM"}] * 4, 80, "MEDIUM 4건 (20점 감점)"), ([{"severity": "HIGH"}] * 3, 70, "HIGH 3건 (30점 감점)"), ([{"severity": "CRITICAL"}] * 2 + [{"severity": "HIGH"}] * 3, 30, "CRITICAL 2건 + HIGH 3건"), ] for findings, expected, label in cases: score = _calculate_score(findings) status = "OK" if score == expected else f"WARN(got {score}, expected {expected})" print(f" {status} {label}: score={score}") except Exception as e: print(f" INFO 점수 계산 오류: {type(e).__name__}: {str(e)[:80]}") print("\n=== 9. findings 파싱 테스트 ===") try: from core.code_review import _parse_findings valid_json = '''[ {"severity": "CRITICAL", "category": "SECURITY", "line": 42, "message": "SQL 인젝션 취약점", "suggestion": "PreparedStatement 사용"}, {"severity": "HIGH", "category": "CODE_QUALITY", "line": null, "message": "null 반환", "suggestion": "Optional 사용"} ]''' result = _parse_findings(valid_json, "test/File.java") assert len(result) == 2 assert result[0]["severity"] == "CRITICAL" assert result[0]["file"] == "test/File.java" print(" OK 유효한 JSON 파싱") result2 = _parse_findings("LLM이 설명을 길게 써서... []", "test/File.java") assert result2 == [] print(" OK 빈 배열 파싱") result3 = _parse_findings("완전 잘못된 응답", "test/File.java") assert result3 == [] print(" OK 잘못된 응답 파싱 (빈 배열 반환)") except Exception as e: print(f" ERR findings 파싱 오류: {type(e).__name__}: {e}") ok = False print("\n=== 10. 하네스 구조 확인 ===") harness_checks = [ (r"C:\GUARDiA\itsm\.claude\agents\sr-manager.md", "SR 매니저 에이전트"), (r"C:\GUARDiA\itsm\.claude\agents\code-reviewer.md", "코드 리뷰 에이전트"), (r"C:\GUARDiA\itsm\.claude\agents\deploy-engineer.md", "배포 엔지니어 에이전트"), (r"C:\GUARDiA\itsm\.claude\agents\sla-guardian.md", "SLA 가디언 에이전트"), (r"C:\GUARDiA\itsm\.claude\agents\incident-responder.md", "인시던트 대응 에이전트"), (r"C:\GUARDiA\itsm\.claude\skills\guardia-orchestrator\SKILL.md", "오케스트레이터 스킬"), (r"C:\GUARDiA\itsm\.claude\skills\code-review\SKILL.md", "코드 리뷰 스킬"), (r"C:\GUARDiA\itsm\.claude\skills\sr-lifecycle\SKILL.md", "SR 생명주기 스킬"), (r"C:\GUARDiA\itsm\.claude\skills\deploy-pipeline\SKILL.md", "배포 파이프라인 스킬"), ] for path, desc in harness_checks: status = "OK" if Path(path).exists() else "ERR" if status == "ERR": ok = False print(f" {status} {desc}") print("\n=== 11. main.py 등록 확인 ===") with open("main.py", encoding="utf-8") as f: main_src = f.read() main_checks = [ ("code_review", "code_review 라우터 임포트"), ("code_review.router", "code_review 라우터 등록"), ] for sym, desc in main_checks: status = "OK" if sym in main_src else "ERR" if status == "ERR": ok = False print(f" {status} {desc}") print("\n=== B-3 코드 리뷰 에이전트 테스트 완료 ===") if ok: print("모든 검사 통과") else: sys.exit(1)