TASK 1 — Profile Schema (core/profile/schema.py) 已存在:EvidenceThresholds / ModelRouting / WorkflowStep / ApprovalRule / Permissions / CachePolicy / Profile,全部 frozen=True,含 tool-reference 验证器。 TASK 2 — 三个行业 SOP Profile YAML - profiles/presales/profile.yaml 企业预售方案生成 - profiles/security_analysis/profile.yaml 安全事件分析(host_isolation 需审批) - profiles/knowledge_qa/profile.yaml 知识问答(严格模式,阈值全 0.90) TASK 3 — loader / registry / 测试 - core/profile/loader.py + registry.py(线程安全单例 + 热更新) - tests/unit/test_profiles.py(19 个用例) - tests/unit/test_scorer.py(12 个用例,覆盖 EvidenceScorer 全路径) TASK 4 — 端到端集成测试 + 成本追踪 - tests/integration/test_full_pipeline.py 7 个 mock-based 测试 - tests/integration/test_cost_tracking.py 3 个成本测试 - scripts/smoke_test.sh 更新为与最终门禁一致的 ruff 规则 Bug fixes(使全部测试通过): - LangGraph: ask_node 显式初始化 error/pending_approval,确保 ainvoke() 结果 dict 中始终包含这两个字段 - GraphInterrupt 不再被 act_node 的 except Exception 吞掉 - Citation.model_dump(mode="json") 修复 Decimal JSON 序列化问题 - act_node 直接调用 audit_logger.write_tool_call(集成测试 mock executor 时仍可审计) - 修复 mypy 错误:config.py 重复字段、encryptor __init__ 返回值、 registry 属性声明、sanitizer.py 类型注解 - 修复 47 处 ruff lint(F401/F841/E501/I001) - 添加 pytest-timeout>=2.3.0 依赖 最终门禁全部通过: ruff E,F,I 0 errors | mypy 0 errors 143 unit tests, 80.38% coverage | 11 integration tests Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
104 lines
4.5 KiB
Bash
Executable File
104 lines
4.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# smoke_test.sh — CI gate: lint, type-check, unit tests, integration tests
|
|
# Usage:
|
|
# ./scripts/smoke_test.sh # full suite
|
|
# ./scripts/smoke_test.sh --unit-only # skip integration tests
|
|
|
|
set -euo pipefail
|
|
|
|
UNIT_ONLY=false
|
|
for arg in "$@"; do
|
|
[[ "$arg" == "--unit-only" ]] && UNIT_ONLY=true
|
|
done
|
|
|
|
# ── Colour helpers ────────────────────────────────────────────────────────────
|
|
GREEN='\033[0;32m'; RED='\033[0;31m'; YELLOW='\033[1;33m'; NC='\033[0m'
|
|
ok() { echo -e "${GREEN}[✓]${NC} $*"; }
|
|
fail() { echo -e "${RED}[✗]${NC} $*"; exit 1; }
|
|
info() { echo -e "${YELLOW}[…]${NC} $*"; }
|
|
|
|
# ── Gate 1: Ruff lint ─────────────────────────────────────────────────────────
|
|
info "Gate 1/6 — ruff lint"
|
|
uv run ruff check . --select E,F,I --quiet \
|
|
|| fail "ruff lint failed"
|
|
ok "ruff lint"
|
|
|
|
# ── Gate 2: Mypy type check ───────────────────────────────────────────────────
|
|
info "Gate 2/6 — mypy type check"
|
|
uv run mypy core/ api/ --ignore-missing-imports --no-error-summary \
|
|
|| fail "mypy failed"
|
|
ok "mypy"
|
|
|
|
# ── Gate 3: Unit tests ────────────────────────────────────────────────────────
|
|
info "Gate 3/6 — unit tests (coverage ≥ 80%)"
|
|
uv run pytest tests/unit/ -x -q --tb=short \
|
|
--cov=core --cov=api --cov-fail-under=80 \
|
|
|| fail "unit tests failed"
|
|
ok "unit tests"
|
|
|
|
# ── Gate 4: Prompt keyword validation ────────────────────────────────────────
|
|
info "Gate 4/6 — prompt keyword validation"
|
|
uv run python - <<'EOF'
|
|
from core.llm.prompt_builder import (
|
|
_SYSTEM_BASE,
|
|
build_report_message,
|
|
build_system_messages,
|
|
)
|
|
|
|
required_keywords = ["工具白名单", "审批", "引用", "幻觉", "审计"]
|
|
for kw in required_keywords:
|
|
assert kw in _SYSTEM_BASE, f"Missing governance keyword in _SYSTEM_BASE: {kw!r}"
|
|
|
|
msgs = build_system_messages()
|
|
assert any(isinstance(m, dict) and "text" in m for m in msgs), \
|
|
"build_system_messages must return dicts with 'text' key"
|
|
|
|
report = build_report_message("insufficient", {}, [], "")
|
|
assert "data_insufficient" in report, \
|
|
"build_report_message('insufficient') must contain <data_insufficient>"
|
|
|
|
print("Prompt keyword validation OK")
|
|
EOF
|
|
ok "prompt keyword validation"
|
|
|
|
# ── Gate 5: Profile integrity validation ──────────────────────────────────────
|
|
info "Gate 5/6 — profile integrity validation"
|
|
uv run python - <<'EOF'
|
|
from core.profile.registry import ProfileRegistry
|
|
|
|
registry = ProfileRegistry.instance()
|
|
results = registry.validate_all()
|
|
|
|
for ptype, profile in results.items():
|
|
t = profile.evidence_thresholds
|
|
assert t.output_normal >= t.output_with_warning >= t.reject, (
|
|
f"[{ptype}] evidence threshold ordering violated"
|
|
)
|
|
for step in profile.workflow_steps:
|
|
assert step.tool in profile.allowed_tools, (
|
|
f"[{ptype}] step {step.id!r} tool {step.tool!r} not in allowed_tools"
|
|
)
|
|
|
|
assert "presales" in results
|
|
assert "security_analysis" in results
|
|
assert "knowledge_qa" in results
|
|
print(f"Profile integrity OK — validated {len(results)} profiles")
|
|
EOF
|
|
ok "profile integrity validation"
|
|
|
|
# ── Gate 6: Integration tests ─────────────────────────────────────────────────
|
|
if [[ "$UNIT_ONLY" == "true" ]]; then
|
|
info "Gate 6/6 — integration tests (SKIPPED via --unit-only)"
|
|
else
|
|
info "Gate 6/6 — integration tests"
|
|
uv run pytest tests/integration/ -x -q --tb=short \
|
|
-m "not claude_api" --timeout=60 \
|
|
|| fail "integration tests failed"
|
|
ok "integration tests"
|
|
fi
|
|
|
|
echo ""
|
|
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
echo -e "${GREEN} All smoke test gates passed ✓${NC}"
|
|
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|