zioinfo-mail/itsm/test_b4_kb_agent.py
DESKTOP-TKLFCPR\ython e228faabf5 feat(itsm): G-1~G-12 확장 기능 + 하네스/봇/설치스크립트 구현
G-1: 메신저 Webhook Relay + _send_to_room 실제 httpx 호출 구현
G-2: POST /api/tasks/bulk SR 대량작업 엔드포인트 (최대 100건)
G-3: 라이선스 만료 알림 스케줄러 (매일 09:00 KST)
G-4: 체험판 upgrade_banner 필드 + license.py 배너 로직
G-5: core/auto_rca.py + incidents/problem auto-rca 엔드포인트
G-6: core/deploy_impact.py + vibe impact-analysis 엔드포인트
G-7: core/ticket_classifier.py + SR 생성 시 AI 분류 + ai-suggestion API
G-8: VulnPatchRecord 모델 + vuln_scan 패치추적 4개 엔드포인트
G-9: core/jira_sync.py + gateway Jira/Confluence 연동 엔드포인트
G-10: core/push_notify.py + routers/push.py + PushSubscription 모델
G-11: approvals 다중승인 (위임/서명/기한초과/마감연장)
G-12: alembic.ini + migrations/ + cicd/migrate_to_postgres.sh

하네스: guardia-orchestrator 확장기능 Phase 반영
봇명령어: /sr /status /license /bulk 슬래시 명령어 추가
설치스크립트: setup/ (Ubuntu, CentOS, RHEL, Windows) --test 옵션 포함

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 18:18:52 +09:00

200 lines
7.4 KiB
Python

"""B-4 KB 자동 업데이트 에이전트 테스트"""
import sys, ast, asyncio, os
os.environ.setdefault("GUARDIA_SECRET_KEY", "test-b4-secret-key-32bytes-padded!")
os.environ.setdefault("DATABASE_URL", "sqlite+aiosqlite:///./test_b4.db")
ok = True
print("=== 1. 구문 검사 ===")
files = ["core/kb_agent.py", "routers/kb_agent.py", "main.py"]
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
print("\n=== 2. models.py KBDocument 확장 컬럼 확인 ===")
with open("models.py", encoding="utf-8") as f:
models_src = f.read()
checks = [
("source_sr_id", "source_sr_id 컬럼 (KB-SR 연결)"),
("author", "author 컬럼 (kb-agent)"),
("updated_at", "updated_at 컬럼"),
]
for sym, desc in checks:
# models.py의 KBDocument 섹션 안에 있는지 확인
kb_start = models_src.find("class KBDocument(Base):")
kb_end = models_src.find("\n\nclass ", kb_start + 1)
kb_section = models_src[kb_start:kb_end] if kb_end > 0 else models_src[kb_start:]
status = "OK" if sym in kb_section else "ERR"
if status == "ERR":
ok = False
print(f" {status} {desc}")
print("\n=== 3. core/kb_agent.py 함수 확인 ===")
with open("core/kb_agent.py", encoding="utf-8") as f:
kb_src = f.read()
fn_checks = [
("def classify_category(", "카테고리 분류 함수"),
("def extract_tags_rule(", "태그 추출 함수"),
("async def extract_kb_with_llm(", "LLM KB 추출"),
("def extract_kb_rule(", "규칙 기반 KB 추출"),
("def compute_similarity(", "유사도 계산"),
("async def find_similar_kb(", "유사 KB 검색"),
("async def auto_create_kb_from_sr(", "SR→KB 자동 생성"),
("async def run_kb_agent_batch(", "일괄 처리"),
("doc_id_val", "doc_id 생성 로직"),
("_CATEGORY_KEYWORDS", "카테고리 키워드 맵"),
("OLLAMA_URL", "Ollama URL"),
]
for sym, desc in fn_checks:
status = "OK" if sym in kb_src else "ERR"
if status == "ERR":
ok = False
print(f" {status} {desc}")
print("\n=== 4. routers/kb_agent.py 엔드포인트 확인 ===")
with open("routers/kb_agent.py", encoding="utf-8") as f:
router_src = f.read()
endpoint_checks = [
('@router.post("/run")', "POST /api/kb-agent/run"),
('@router.post("/analyze/{sr_id}")', "POST /api/kb-agent/analyze/{sr_id}"),
('@router.get("/candidates")', "GET /api/kb-agent/candidates"),
('@router.get("/stats")', "GET /api/kb-agent/stats"),
('@router.post("/kb"', "POST /api/kb-agent/kb (수동 생성)"),
('@router.put("/kb/{kb_id}"', "PUT /api/kb-agent/kb/{id}"),
("run_kb_agent_batch", "배치 실행 함수 호출"),
("auto_create_kb_from_sr", "SR 분석 함수 호출"),
]
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. main.py 등록 확인 ===")
with open("main.py", encoding="utf-8") as f:
main_src = f.read()
main_checks = [
("kb_agent", "kb_agent 라우터 임포트"),
("kb_agent.router", "kb_agent 라우터 등록"),
]
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=== 6. classify_category 테스트 ===")
try:
import importlib.util
spec = importlib.util.spec_from_file_location("kb_mod", "core/kb_agent.py")
kb_mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(kb_mod)
cases = [
("서버 CPU 과부하", "서버 CPU 메모리 문제입니다", "서버 운영"),
("톰캣 배포 오류", "tomcat WAS 배포 실패", "배포"),
("오라클 DB 연결 실패", "database connection pool 소진", "DB"),
("SSL 인증서 만료", "HTTPS 보안 SSL 오류", "네트워크"),
]
for title, desc, expected in cases:
cat = kb_mod.classify_category(title, desc)
status = "OK" if cat == expected else f"WARN(got {cat}, expected {expected})"
print(f" {status} '{title}'{cat}")
except Exception as e:
print(f" ERR classify_category 오류: {type(e).__name__}: {e}")
ok = False
print("\n=== 7. extract_tags_rule 테스트 ===")
try:
cases2 = [
("톰캣 재기동", "tomcat restart java WAS", "", ["java", "restart", "tomcat"]),
("MySQL DB 연결", "mysql database connection", "", ["mysql"]),
("Nginx SSL 설정", "nginx ssl tls 설정", "", ["nginx", "ssl"]),
]
for title, desc, solution, expected_tags in cases2:
tags = kb_mod.extract_tags_rule(title, desc, solution)
# 기대 태그 중 하나라도 있으면 OK
found = [t for t in expected_tags if t in tags]
status = "OK" if found else f"WARN(got {tags}, expected subset of {expected_tags})"
print(f" {status} '{title}' → tags={tags}")
except Exception as e:
print(f" ERR extract_tags_rule 오류: {type(e).__name__}: {e}")
ok = False
print("\n=== 8. compute_similarity 테스트 ===")
try:
cases3 = [
("서버 CPU 과부하 장애", "서버 CPU 과부하 장애", 1.0), # 동일
("서버 CPU 장애", "서버 메모리 문제", 0.1), # 낮은 유사도 (일부 겹침)
("완전 다른 텍스트 xyz", "전혀 관련 없음 abc def", 0.0), # 매우 낮음
]
for t1, t2, min_expected in cases3:
sim = kb_mod.compute_similarity(t1, t2)
status = "OK" if sim >= min_expected else f"WARN(got {sim:.2f}, min={min_expected})"
print(f" {status} 유사도({t1[:15]}|{t2[:15]}) = {sim:.2f}")
except Exception as e:
print(f" ERR compute_similarity 오류: {type(e).__name__}: {e}")
ok = False
print("\n=== 9. extract_kb_rule 테스트 ===")
try:
result = kb_mod.extract_kb_rule(
title="서버 CPU 과부하 장애",
description="CPU 사용률이 95%를 초과하여 서비스가 중단됨",
work_log="$ top -bn1 | head -20\n$ kill -9 12345",
sr_type="OTHER",
)
assert "title" in result, "title 없음"
assert "category" in result, "category 없음"
assert "symptom" in result, "symptom 없음"
assert "solution" in result, "solution 없음"
assert "tags" in result, "tags 없음"
assert len(result["commands"]) > 0, f"commands 없음: {result['commands']}"
print(f" OK KB 추출: title='{result['title'][:40]}', category={result['category']}")
print(f" OK commands 추출: {result['commands']}")
except AssertionError as e:
print(f" ERR extract_kb_rule 오류: {e}")
ok = False
except Exception as e:
print(f" ERR extract_kb_rule 예외: {type(e).__name__}: {e}")
ok = False
print("\n=== 10. Ollama 폴백 테스트 ===")
async def test_llm_fallback():
try:
result = await kb_mod.extract_kb_with_llm(
title="서버 오류",
description="CPU 과부하",
work_log="재기동 완료",
sr_type="OTHER",
model="llama3",
timeout=2,
)
assert result is None or isinstance(result, dict), "폴백 타입 오류"
print(f" OK LLM 폴백 (None 반환): {type(result).__name__}")
except Exception as e:
print(f" ERR LLM 폴백: {type(e).__name__}: {e}")
asyncio.run(test_llm_fallback())
print("\n=== B-4 KB 자동 업데이트 에이전트 테스트 완료 ===")
if ok:
print("모든 검사 통과")
else:
sys.exit(1)