Compare commits

...

2 Commits

Author SHA1 Message Date
xiaoxia 9e8a0cee7a ci: 覆盖率门禁加固 + CI 失败通知
- unit tests 增加 60% 覆盖率门禁
- integration tests 覆盖率门槛从 50% 提升到 65%
- 新增覆盖率汇总 step,输出行覆盖率/分支覆盖率/状态
- 新增 CI 失败通知 step,支持 webhook 推送到飞书/群聊
- 配置 CI_NOTIFY_WEBHOOK secret 即可启用通知
2026-07-11 15:04:17 +08:00
xiaoxia 2ffe1c466c ci: 统一 coverage 配置到 pyproject.toml 2026-07-11 15:03:52 +08:00
2 changed files with 137 additions and 2 deletions
+105 -2
View File
@@ -165,7 +165,8 @@ jobs:
run: |
set -eu
PYTHONPATH="$PWD/apps/api:$PWD" python3 -m pytest tests/unit -q \
--cov=apps --cov-report=term --cov-report=xml --cov-fail-under=50
--cov=apps --cov-report=term --cov-report=term-missing --cov-report=xml \
--cov-fail-under=60
- name: Start PostgreSQL for integration tests
shell: sh
@@ -211,7 +212,7 @@ jobs:
set -eu
pip install -q pytest-rerunfailures
PYTHONPATH="$PWD/apps/api:$PWD" python3 -m pytest tests/integration -q --timeout=60 -x --reruns 2 --reruns-delay 1 -m "not performance" \
--cov=apps --cov-append --cov-report=term --cov-report=xml --cov-fail-under=50
--cov=apps --cov-append --cov-report=term --cov-report=term-missing --cov-report=xml --cov-fail-under=65
- name: Run API performance baseline tests
shell: sh
@@ -262,6 +263,98 @@ jobs:
docker rm -f "${PG_CONTAINER:-ci-pg-validate}" 2>/dev/null || true
echo "PostgreSQL container cleaned up"
- name: Coverage summary
if: always()
shell: sh
run: |
set +e
echo "=== 覆盖率汇总 ==="
if [ -f coverage.xml ]; then
python3 -c "
import xml.etree.ElementTree as ET
tree = ET.parse('coverage.xml')
root = tree.getroot()
line_rate = float(root.get('line-rate', 0)) * 100
branch_rate = float(root.get('branch-rate', 0)) * 100
lines_covered = int(root.get('lines-covered', 0))
lines_valid = int(root.get('lines-valid', 0))
print(f'行覆盖率: {line_rate:.2f}% ({lines_covered}/{lines_valid})')
print(f'分支覆盖率: {branch_rate:.2f}%')
print(f'门槛: 65%')
print(f'状态: {"PASS ✅" if line_rate >= 65 else "FAIL ❌"}')
"
else
echo "coverage.xml 不存在,跳过汇总"
fi
- name: Notify CI failure
if: failure()
shell: sh
run: |
set +e
echo "=== CI 失败通知 ==="
# 收集失败信息
FAILED_JOB="Validate Code Quality And Tests"
BRANCH="${GITHUB_REF_NAME:-unknown}"
COMMIT="${GITHUB_SHA:0:8}"
ACTOR="${GITHUB_ACTOR:-unknown}"
RUN_ID="${GITHUB_RUN_ID:-unknown}"
REPO="${GITHUB_REPOSITORY:-unknown}"
RUN_URL="https://git.xiaoxiajianji.com/${REPO}/actions/runs/${RUN_ID}"
# 构造通知消息
PAYLOAD=$(cat <<EOF
{
"msg_type": "interactive",
"card": {
"header": {
"title": {
"tag": "plain_text",
"content": "❌ CI 构建失败"
},
"status": "red"
},
"elements": [
{
"tag": "div",
"text": {
"tag": "lark_md",
"content": "**任务**: ${FAILED_JOB}
**分支**: ${BRANCH}
**提交**: ${COMMIT}
**提交者**: ${ACTOR}
**Run ID**: ${RUN_ID}"
}
},
{
"tag": "action",
"actions": [
{
"tag": "button",
"text": {
"tag": "plain_text",
"content": "查看失败日志"
},
"url": "${RUN_URL}",
"type": "danger"
}
]
}
]
}
}
EOF
)
# 如果配置了通知 webhook 就发送
if [ -n "${CI_NOTIFY_WEBHOOK:-}" ]; then
curl -s -X POST -H "Content-Type: application/json" "${CI_NOTIFY_WEBHOOK}" -d "$PAYLOAD" > /dev/null 2>&1 && echo "通知已发送" || echo "通知发送失败"
else
echo "未配置 CI_NOTIFY_WEBHOOK,跳过通知"
echo "如需启用,请在仓库 Settings -> Secrets and variables -> Actions 中添加 CI_NOTIFY_WEBHOOK"
fi
- name: Build summary
if: github.ref == 'refs/heads/develop' || github.ref == 'refs/heads/main'
shell: sh
@@ -270,6 +363,16 @@ jobs:
echo "Build completed successfully!"
echo "Branch: ${GITHUB_REF_NAME}"
echo "Commit: ${GITHUB_SHA}"
# 输出最终覆盖率
if [ -f coverage.xml ]; then
python3 -c "
import xml.etree.ElementTree as ET
tree = ET.parse('coverage.xml')
root = tree.getroot()
line_rate = float(root.get('line-rate', 0)) * 100
print(f'Total coverage: {line_rate:.2f}%')
"
fi
frontend-lint:
name: Frontend Lint
+32
View File
@@ -37,3 +37,35 @@ extend_skip_glob = [
"out/**",
"coverage/**",
]
[tool.coverage.run]
source = ["apps", "packages"]
omit = [
"*/migrations/*",
"*/tests/*",
"*/test_*.py",
"*/site-packages/*",
"*/.cache/*",
]
branch = true
[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"def __repr__",
"if __name__ == .__main__.:",
"raise NotImplementedError",
"pass",
"if TYPE_CHECKING:",
"class .*Protocol",
"@abstractmethod",
"raise AssertionError",
"raise RuntimeError",
"if 0:",
"if __debug__:",
]
show_missing = true
skip_covered = false
[tool.coverage.xml]
output = "coverage.xml"