feat(test): B5 整合測試框架 — 真實 DB, 5/5 通過
Some checks failed
CD Pipeline / build-and-deploy (push) Failing after 2m34s

新增:
- docker-compose.test.yml: CI 用臨時 pgvector PostgreSQL (port 15432)
- tests/factories.py: Incident/Approval/Knowledge/RAG 測試資料工廠
- tests/integration/test_b5_core_flows.py: 5 個 E2E 整合測試 (5/5 PASSED 1.03s)
- tests/integration/setup_test_schema.sql: CI schema 初始化 SQL
- cd.yaml: 新增 Integration Tests B5 step
- scripts/sync_dev_db.py: dev DB 同步工具

修正:
- .env.test: DATABASE_URL 指向 awoooi_dev (本機設定, gitignore 不入庫)

禁止 Mock 鐵律: 所有 DB 測試使用真實 PostgreSQL, 無 SQLite/MagicMock

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
OG T
2026-04-10 11:22:57 +08:00
parent ab6f6faa32
commit 49bfbd573c
6 changed files with 646 additions and 0 deletions

109
scripts/sync_dev_db.py Normal file
View File

@@ -0,0 +1,109 @@
#!/usr/bin/env python3
"""同步 dev DB — 補齊 prod 有但 dev 沒有的表"""
import asyncio
from sqlalchemy.ext.asyncio import create_async_engine
from sqlalchemy import text
DEV_URL = "postgresql+asyncpg://awoooi:awoooi_prod_2026@192.168.0.188:5432/awoooi_dev"
MIGRATIONS = [
("auto_repair_executions", """
CREATE TABLE IF NOT EXISTS auto_repair_executions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
incident_id UUID,
playbook_name TEXT,
status TEXT NOT NULL DEFAULT 'pending',
started_at TIMESTAMPTZ,
finished_at TIMESTAMPTZ,
result JSONB,
error_message TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
)
"""),
("alert_operation_log", """
CREATE TABLE IF NOT EXISTS alert_operation_log (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
event_type TEXT NOT NULL,
incident_id UUID,
approval_id UUID,
actor TEXT,
action_detail TEXT,
success BOOLEAN DEFAULT true,
context JSONB DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
)
"""),
("playbooks", """
CREATE TABLE IF NOT EXISTS playbooks (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
description TEXT,
steps JSONB NOT NULL DEFAULT '[]',
tags TEXT[] DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
)
"""),
("drift_reports", """
CREATE TABLE IF NOT EXISTS drift_reports (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
host TEXT NOT NULL,
report_type TEXT NOT NULL DEFAULT 'config',
items JSONB NOT NULL DEFAULT '[]',
summary TEXT,
severity TEXT DEFAULT 'low',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
)
"""),
("pr_reviews", """
CREATE TABLE IF NOT EXISTS pr_reviews (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
repo TEXT NOT NULL,
pr_id TEXT NOT NULL,
review_text TEXT,
model TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
)
"""),
("vector_extension", "CREATE EXTENSION IF NOT EXISTS vector"),
("rag_chunks", """
CREATE TABLE IF NOT EXISTS rag_chunks (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
source TEXT NOT NULL,
source_id TEXT NOT NULL,
title TEXT,
chunk_text TEXT NOT NULL,
embedding vector(768),
metadata JSONB DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
)
"""),
]
async def main():
engine = create_async_engine(DEV_URL, echo=False)
async with engine.begin() as conn:
r = await conn.execute(text(
"SELECT table_name FROM information_schema.tables WHERE table_schema='public' ORDER BY table_name"
))
existing = {row[0] for row in r}
print(f"dev 現有: {sorted(existing)}")
for name, sql in MIGRATIONS:
try:
await conn.execute(text(sql))
print(f"{name}")
except Exception as e:
print(f"{name}: {e}")
r2 = await conn.execute(text(
"SELECT table_name FROM information_schema.tables WHERE table_schema='public' ORDER BY table_name"
))
final = [row[0] for row in r2]
print(f"\ndev 最終: {final}")
await engine.dispose()
asyncio.run(main())