zioinfo-mail/workspace/guardia-itsm/test_b3_code_review.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

223 lines
8.4 KiB
Python

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