Compare commits

...

7 Commits

Author SHA1 Message Date
xiaoxia 42c0c97367 fix(url_security): 移除未使用的import
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 41s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 1m12s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 2m21s
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 1m13s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m30s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 2m37s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 2m48s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 2m50s
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
PR Automation / Auto Approve on CI Green (pull_request) Successful in 4m26s
CI/CD Pipeline / Validate - Code Quality (pull_request) Successful in 5m39s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 5m38s
AI Code Review / AI Code Review (pull_request) Failing after 6m23s
Preview Cleanup / Cleanup Preview Environment (pull_request) Successful in 14s
ACR Cleanup / ACR Image Cleanup (pull_request_target) Successful in 15s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 6m40s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 2m17s
CI/CD Pipeline / CI Gate (pull_request) Failing after 28s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 9m17s
CI/CD Pipeline / Build Staging Worker Image (pull_request) Failing after 10m49s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
2026-07-29 01:33:49 +08:00
CI Bot cad6b673af style: auto-format with black + isort + prettier [skip ci-format-check]
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 1m4s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 1m10s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 1m58s
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
AI Code Review / AI Code Review (pull_request) Successful in 2m18s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 1m12s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m4s
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 2m29s
CI/CD Pipeline / Validate - Code Quality (pull_request) Failing after 2m32s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 2m54s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 2m55s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m59s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 4m27s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 2m8s
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 6m16s
2026-07-28 17:27:25 +00:00
xiaoxia 6828628814 fix(url_security): 统一import命名风格,修复lint错误
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 1m2s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 1m52s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 1m53s
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 2m3s
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m21s
AI Code Review / AI Code Review (pull_request) Failing after 2m8s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 40s
CI/CD Pipeline / Validate - Code Quality (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
PR Automation / Auto Approve on CI Green (pull_request) Successful in 2m58s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 3m5s
2026-07-29 01:24:39 +08:00
xiaoxia bf58967a21 chore(ci): 同步main分支的lint脚本,支持PR增量扫描
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 50s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 1m15s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 1m45s
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 1m59s
AI Code Review / AI Code Review (pull_request) Failing after 1m56s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 1m2s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 2m6s
CI/CD Pipeline / Validate - Code Quality (pull_request) Failing after 2m41s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 2m50s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 2m51s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 4m34s
CI/CD Pipeline / Integration Tests (pull_request) Failing after 2m23s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 5m53s
CI/CD Pipeline / Unit Tests (pull_request) Failing after 5m40s
CI/CD Pipeline / CI Gate (pull_request) Failing after 8s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 6m57s
2026-07-29 01:16:28 +08:00
xiaoxia e314f4be58 test(frontend): 修复generate-video smoke test的导入错误
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 1m1s
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
AI Code Review / AI Code Review (pull_request) Successful in 2m3s
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Successful in 2m5s
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Successful in 2m15s
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Successful in 2m18s
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m46s
CI/CD Pipeline / Frontend Unit Tests (pull_request) Successful in 30s
CI/CD Pipeline / PR Build Web Image (pull_request) Successful in 2m35s
CI/CD Pipeline / Validate - Code Quality (pull_request) Failing after 2m51s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 4m0s
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 4m8s
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 4m9s
CI/CD Pipeline / Integration Tests (pull_request) Failing after 2m4s
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 6m45s
2026-07-29 01:09:56 +08:00
xiaoxia b8917c62fb fix(url_security): 补全缺失的导出变量和私有别名
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 29s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Validate - Code Quality (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Has been cancelled
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Has been cancelled
CI/CD Pipeline / Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been cancelled
CI/CD Pipeline / PR Build API Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Web Image (pull_request) Has been cancelled
CI/CD Pipeline / PR Build Worker Image (pull_request) Has been cancelled
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been cancelled
CI/CD Pipeline / Deploy Production (pull_request) Has been cancelled
CI/CD Pipeline / Production Browser E2E (pull_request) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been cancelled
CI/CD Pipeline / Canary Release to Production (pull_request) Has been cancelled
CI/CD Pipeline / CI Gate (pull_request) Has been cancelled
AI Code Review / AI Code Review (pull_request) Has been cancelled
Preview Deploy / Deploy Preview Environment (pull_request) Has been cancelled
PR Automation / Auto Merge on CI Green + Approved (pull_request) Successful in 1m34s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 1m58s
2026-07-29 01:08:33 +08:00
xiaoxia 1ea4b32c9d test(wave173): pip_config画中画配置 +67测
CI/CD Pipeline / Check if frontend-only change (pull_request) Waiting to run
CI/CD Pipeline / Validate - Code Quality (pull_request) Waiting to run
CI/CD Pipeline / Validate - Type Check (mypy) (pull_request) Waiting to run
CI/CD Pipeline / Validate - Migration (alembic) (pull_request) Waiting to run
CI/CD Pipeline / Unit Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Integration Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Frontend Lint (pull_request) Waiting to run
CI/CD Pipeline / Frontend Unit Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / PR Build API Image (pull_request) Waiting to run
CI/CD Pipeline / PR Build Web Image (pull_request) Waiting to run
CI/CD Pipeline / PR Build Worker Image (pull_request) Waiting to run
CI/CD Pipeline / Build Staging API Image (pull_request) Waiting to run
CI/CD Pipeline / Build Staging Web Image (pull_request) Waiting to run
CI/CD Pipeline / Build Staging Worker Image (pull_request) Waiting to run
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Blocked by required conditions
CI/CD Pipeline / Staging E2E Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Staging API Integration Tests (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production API Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production Web Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Build Production Worker Image (pull_request) Blocked by required conditions
CI/CD Pipeline / Deploy Production (pull_request) Blocked by required conditions
CI/CD Pipeline / Production Browser E2E (pull_request) Blocked by required conditions
CI/CD Pipeline / ACR Image Cleanup (pull_request) Blocked by required conditions
CI/CD Pipeline / Canary Release to Production (pull_request) Blocked by required conditions
CI/CD Pipeline / CI Gate (pull_request) Blocked by required conditions
AI Code Review / AI Code Review (pull_request) Waiting to run
PR Automation / Auto Approve on CI Green (pull_request) Waiting to run
PR Automation / Auto Merge on CI Green + Approved (pull_request) Waiting to run
Preview Deploy / Deploy Preview Environment (pull_request) Waiting to run
2026-07-28 15:56:51 +08:00
4 changed files with 582 additions and 41 deletions
@@ -8,14 +8,18 @@ import type {
UseGenerateVideoProps,
GenerationPhase,
} from "@/pages/generate/hooks/generate-video/types"
import { getNextPhase, PHASE_ORDER } from "@/pages/generate/hooks/generate-video/phase"
import { extractErrorMessage } from "@/pages/generate/hooks/generate-video/errorUtils"
import { getDefaultVoiceConfig } from "@/pages/generate/hooks/generate-video/voiceConfig"
import { getGenerationPhase } from "@/pages/generate/hooks/generate-video/phase"
import { safeExtractError } from "@/pages/generate/hooks/generate-video/errorUtils"
import { buildVoiceConfig } from "@/pages/generate/hooks/generate-video/voiceConfig"
describe("generate-video module smoke test", () => {
it("should load all generate-video modules", () => {
expect(PHASE_ORDER.length).toBeGreaterThan(0)
expect(typeof extractErrorMessage).toBe("function")
expect(typeof getDefaultVoiceConfig).toBe("function")
expect(typeof getGenerationPhase).toBe("function")
expect(typeof safeExtractError).toBe("function")
expect(typeof buildVoiceConfig).toBe("function")
// 验证核心函数能正常工作
const phase = getGenerationPhase(50)
expect(phase).toHaveProperty("label")
expect(phase).toHaveProperty("icon")
})
})
+10 -10
View File
@@ -22,22 +22,15 @@ import urllib.error
import urllib.request
from urllib.parse import urljoin
from packages.domain.url_security import (
ALLOWED_AUDIO_MIME_TYPES,
ALLOWED_IMAGE_MIME_TYPES,
)
from packages.domain.url_security import ALLOWED_AUDIO_MIME_TYPES as _allowed_audio_base
from packages.domain.url_security import ALLOWED_IMAGE_MIME_TYPES as _allowed_image_base
from packages.domain.url_security import ALLOWED_PORTS as _allowed_ports_base
from packages.domain.url_security import ALLOWED_SCHEMES as _allowed_schemes_base
from packages.domain.url_security import (
ALLOWED_VIDEO_MIME_TYPES,
MAGIC_NUMBERS,
MAX_URL_LENGTH,
)
from packages.domain.url_security import MAX_URL_LENGTH as _max_url_length_base
from packages.domain.url_security import UrlSecurityError as _UrlSecurityError_base
from packages.domain.url_security import check_internal_hostname as _check_internal_hostname_base
from packages.domain.url_security import check_ssrf_ip as _check_ssrf_ip_base
from packages.domain.url_security import is_ip_address as _is_ip_address_base
from packages.domain.url_security import is_trusted_domain as _is_trusted_domain_base
from packages.domain.url_security import validate_magic_number as _validate_magic_number_base
from packages.domain.url_security import validate_url_basic as _validate_url_basic_base
@@ -46,8 +39,15 @@ logger = logging.getLogger(__name__)
# ── 兼容导出(保持原有变量名供外部引用) ──────────────────────────────────
ALLOWED_SCHEMES = set(_allowed_schemes_base)
ALLOWED_PORTS = set(_allowed_ports_base)
ALLOWED_AUDIO_MIME_TYPES = set(_allowed_audio_base)
ALLOWED_IMAGE_MIME_TYPES = set(_allowed_image_base)
MAX_URL_LENGTH = _max_url_length_base
UrlSecurityError = _UrlSecurityError_base
# 私有别名(供测试和内部引用)
_check_internal_hostnames = _check_internal_hostname_base
_check_ssrf_ip = _check_ssrf_ip_base
# 可信域名白名单(从环境变量读取)
TRUSTED_DOMAINS: set[str] = set()
_env_trusted = os.environ.get("URL_SECURITY_TRUSTED_DOMAINS", "")
+91 -25
View File
@@ -11,17 +11,7 @@ echo "=== [1/6] Secret detection (detect-secrets) ==="
python3 -m pip install -q detect-secrets
detect-secrets --version
detect-secrets scan \
--all-files \
--exclude-files '(^|/)(tests|test|e2e|__tests__|spec|docs|node_modules|site-packages|migrations|alembic|.gitea|.git|.pytest_cache|.next|dist|build)/' \
--exclude-files '\.(md|rst|txt|lock|example|sample|min\.js|min\.css|spec\.ts|test\.ts|test\.py)$' \
--exclude-files '(package-lock|yarn\.lock|poetry\.lock|Pipfile\.lock)$' \
--disable-plugin Base64HighEntropyString \
--disable-plugin HexHighEntropyString \
--disable-plugin BasicAuthDetector \
--disable-plugin KeywordDetector \
--disable-plugin IPPublicDetector \
> /tmp/secrets-scan.json 2>&1
detect-secrets scan --all-files --exclude-files '(^|/)(tests|test|e2e|__tests__|spec|docs|node_modules|site-packages|migrations|alembic|.gitea|.git|.pytest_cache|.next|dist|build)/' --exclude-files '\.(md|rst|txt|lock|example|sample|min\.js|min\.css|spec\.ts|test\.ts|test\.py)$' --exclude-files '(package-lock|yarn\.lock|poetry\.lock|Pipfile\.lock)$' --disable-plugin Base64HighEntropyString --disable-plugin HexHighEntropyString --disable-plugin BasicAuthDetector --disable-plugin KeywordDetector --disable-plugin IPPublicDetector > /tmp/secrets-scan.json 2>&1
FOUND=$(python3 -c "
import json
@@ -56,18 +46,97 @@ for fpath, items in data.get('results', {}).items():
fi
echo "✅ Secret scan passed"
# --- 代码质量检查(全量,PR 和 push 统一标准)---
# 历史:PR 侧用增量检查以加速,但会导致 push 侧全量检查失败时 PR 侧感知不到
# 现在统一全量检查,确保 CI 真正保护主分支(black/isort/ruff 全量仅多几十秒)
# --- 增量/全量模式判断 ---
echo ""
echo "=== [2/6] Code quality checks (full scan) ==="
echo "=== [2/6] Code quality checks ==="
SCAN_MODE="full"
echo "Full scan mode"
python3 -m compileall -q alembic apps packages tests scripts
python3 -m black --check --fast alembic apps packages tests scripts
python3 -m isort --check-only alembic apps packages tests scripts
python3 -m ruff check apps packages tests --statistics
CHANGED_PY_FILES=""
if [ "${GITHUB_EVENT_NAME:-}" = "pull_request" ] && [ -n "${GITHUB_REF_NAME:-}" ] && [ -n "${GITHUB_TOKEN:-}" ]; then
PR_NUMBER=$(echo "$GITHUB_REF" | sed 's|refs/pull/||; s|/.*||')
API_URL="${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}/files?limit=100"
set +e
RESPONSE=$(curl -s -w "
%{http_code}" -H "Authorization: token ${GITHUB_TOKEN}" "$API_URL")
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
BODY=$(echo "$RESPONSE" | sed '$d')
set -e
if [ "$HTTP_CODE" = "200" ]; then
CHANGED_PY_FILES=$(echo "$BODY" | python3 -c "
import json, sys
try:
files = json.load(sys.stdin)
py_files = [f['filename'] for f in files if f['filename'].endswith('.py') and f['status'] != 'removed']
print(' '.join(py_files))
except Exception:
print('')
")
# 新增文件(added)强制全量检查,防止增量漏检
ADDED_PY_FILES=$(echo "$BODY" | python3 -c "
import json, sys
try:
files = json.load(sys.stdin)
added = [f['filename'] for f in files if f['filename'].endswith('.py') and f['status'] == 'added']
print(' '.join(added))
except Exception:
print('')
")
MODIFIED_PY_FILES=$(echo "$BODY" | python3 -c "
import json, sys
try:
files = json.load(sys.stdin)
modified = [f['filename'] for f in files if f['filename'].endswith('.py') and f['status'] not in ('removed', 'added')]
print(' '.join(modified))
except Exception:
print('')
")
if [ -n "$CHANGED_PY_FILES" ]; then
SCAN_MODE="incremental"
echo "Incremental mode: $(echo "$CHANGED_PY_FILES" | wc -w) Python files changed"
else
SCAN_MODE="skip_py"
echo "No Python files changed in this PR"
fi
else
echo "WARN: API returned HTTP $HTTP_CODE, falling back to full scan"
fi
else
echo "Full scan mode (not a PR event)"
fi
if [ "$SCAN_MODE" = "incremental" ]; then
# 防御性过滤
EXISTING_PY_FILES=""
for f in $CHANGED_PY_FILES; do
if [ -f "$f" ]; then
if [ -z "$EXISTING_PY_FILES" ]; then
EXISTING_PY_FILES="$f"
else
EXISTING_PY_FILES="$EXISTING_PY_FILES $f"
fi
fi
done
CHANGED_PY_FILES="$EXISTING_PY_FILES"
python3 -m compileall -q $CHANGED_PY_FILES
python3 -m black --check --fast $CHANGED_PY_FILES
python3 -m isort --check-only $CHANGED_PY_FILES
RUFF_FILES=$(echo "$CHANGED_PY_FILES" | tr ' ' '
' | grep -v '^scripts/' | grep -v '^$' | xargs)
if [ -n "$RUFF_FILES" ]; then
python3 -m ruff check $RUFF_FILES --statistics
else
echo "No ruff-checkable files changed, skipping"
fi
elif [ "$SCAN_MODE" = "skip_py" ]; then
echo "No Python files changed - skipping Python lint checks"
else
echo "Full scan mode"
python3 -m compileall -q alembic apps packages tests scripts
python3 -m black --check --fast alembic apps packages tests scripts
python3 -m isort --check-only alembic apps packages tests scripts
python3 -m ruff check apps packages tests --statistics
fi
echo "✅ Code quality checks passed"
# --- Bandit 安全扫描(仅告警) ---
@@ -106,10 +175,7 @@ python3 -m pip install -q vulture
vulture --version
echo "告警模式,不阻断CI。置信度>=90%建议尽快确认。"
echo ""
vulture apps packages scripts \
--exclude "tests,test,migrations,.gitea,docs,node_modules,site-packages,*/test_*.py,*/conftest.py" \
--min-confidence 70 \
2>&1 | sort -t'(' -k2 -rn | head -80
vulture apps packages scripts --exclude "tests,test,migrations,.gitea,docs,node_modules,site-packages,*/test_*.py,*/conftest.py" --min-confidence 70 2>&1 | sort -t'(' -k2 -rn | head -80
echo ""
echo "=== vulture scan summary ==="
echo "发现潜在死代码(可能包含框架装饰器注册的函数,为误报)"
@@ -154,4 +220,4 @@ fi
echo "✅ All CI scripts syntax OK"
echo ""
echo "=== CI Validate: 代码质量与安全扫描 全部通过 ✅ ==="
echo "=== CI Validate: 代码质量与安全扫描 全部通过 ✅ ==="
+471
View File
@@ -0,0 +1,471 @@
"""PiP 画中画配置单测.
纯逻辑模块,覆盖:PiPLayerConfig校验、PiPConfig解析+属性、
parse_size_value尺寸解析、calculate_pip_position位置计算。
"""
from __future__ import annotations
from packages.domain.pip_config import (
ANIMATION_FADE,
ANIMATION_SCALE,
ANIMATION_SLIDE_BOTTOM,
ANIMATION_SLIDE_LEFT,
ANIMATION_SLIDE_RIGHT,
ANIMATION_SLIDE_TOP,
POSITION_BOTTOM_LEFT,
POSITION_BOTTOM_RIGHT,
POSITION_CENTER,
POSITION_TOP_LEFT,
POSITION_TOP_RIGHT,
PiPConfig,
PiPLayerConfig,
calculate_pip_position,
parse_size_value,
)
class TestPiPLayerConfigDefaults:
def test_default_source(self):
layer = PiPLayerConfig()
assert layer.source == ""
assert layer.source_type == "asset_id"
def test_default_position(self):
layer = PiPLayerConfig()
assert layer.position == POSITION_BOTTOM_RIGHT
assert layer.x == 0
assert layer.y == 0
assert layer.margin == 20
def test_default_size(self):
layer = PiPLayerConfig()
assert layer.width == "25%"
assert layer.height == ""
def test_default_style(self):
layer = PiPLayerConfig()
assert layer.opacity == 1.0
assert layer.corner_radius == 0
assert layer.border_width == 0
assert layer.border_color == "white"
def test_default_timing(self):
layer = PiPLayerConfig()
assert layer.start_time == 0.0
assert layer.duration == 0.0
def test_default_animation(self):
layer = PiPLayerConfig()
assert layer.animation_in == ""
assert layer.animation_out == ""
assert layer.animation_duration == 0.5
def test_default_z_index(self):
layer = PiPLayerConfig()
assert layer.z_index == 1
class TestPiPLayerConfigValidate:
def test_valid_with_source(self):
layer = PiPLayerConfig(source="asset_123")
ok, msg = layer.validate()
assert ok is True
assert msg == ""
def test_empty_source_invalid(self):
layer = PiPLayerConfig(source="")
ok, msg = layer.validate()
assert ok is False
assert "source" in msg
def test_invalid_position(self):
layer = PiPLayerConfig(source="a", position="invalid")
ok, msg = layer.validate()
assert ok is False
assert "position" in msg
def test_custom_position_valid(self):
layer = PiPLayerConfig(source="a", position="custom")
ok, _ = layer.validate()
assert ok is True
def test_all_9_positions_valid(self):
positions = [
"top_left",
"top_center",
"top_right",
"center_left",
"center",
"center_right",
"bottom_left",
"bottom_center",
"bottom_right",
]
for pos in positions:
layer = PiPLayerConfig(source="a", position=pos)
ok, _ = layer.validate()
assert ok is True, f"position {pos} should be valid"
def test_opacity_below_zero_invalid(self):
layer = PiPLayerConfig(source="a", opacity=-0.1)
ok, msg = layer.validate()
assert ok is False
assert "opacity" in msg
def test_opacity_above_one_invalid(self):
layer = PiPLayerConfig(source="a", opacity=1.5)
ok, msg = layer.validate()
assert ok is False
assert "opacity" in msg
def test_opacity_zero_valid(self):
layer = PiPLayerConfig(source="a", opacity=0.0)
ok, _ = layer.validate()
assert ok is True
def test_opacity_one_valid(self):
layer = PiPLayerConfig(source="a", opacity=1.0)
ok, _ = layer.validate()
assert ok is True
def test_negative_corner_radius_invalid(self):
layer = PiPLayerConfig(source="a", corner_radius=-1)
ok, msg = layer.validate()
assert ok is False
assert "corner_radius" in msg
def test_negative_start_time_invalid(self):
layer = PiPLayerConfig(source="a", start_time=-1.0)
ok, msg = layer.validate()
assert ok is False
assert "start_time" in msg
def test_negative_duration_invalid(self):
layer = PiPLayerConfig(source="a", duration=-1.0)
ok, msg = layer.validate()
assert ok is False
assert "duration" in msg
def test_zero_duration_valid(self):
layer = PiPLayerConfig(source="a", duration=0.0)
ok, _ = layer.validate()
assert ok is True
def test_invalid_animation_in(self):
layer = PiPLayerConfig(source="a", animation_in="invalid")
ok, msg = layer.validate()
assert ok is False
assert "入场动画" in msg
def test_invalid_animation_out(self):
layer = PiPLayerConfig(source="a", animation_out="invalid")
ok, msg = layer.validate()
assert ok is False
assert "出场动画" in msg
def test_empty_animation_valid(self):
layer = PiPLayerConfig(source="a", animation_in="", animation_out="")
ok, _ = layer.validate()
assert ok is True
def test_all_valid_animations(self):
anims = [
ANIMATION_FADE,
ANIMATION_SLIDE_LEFT,
ANIMATION_SLIDE_RIGHT,
ANIMATION_SLIDE_TOP,
ANIMATION_SLIDE_BOTTOM,
ANIMATION_SCALE,
]
for anim in anims:
layer = PiPLayerConfig(source="a", animation_in=anim, animation_out=anim)
ok, _ = layer.validate()
assert ok is True, f"animation {anim} should be valid"
def test_negative_animation_duration_invalid(self):
layer = PiPLayerConfig(source="a", animation_duration=-0.5)
ok, msg = layer.validate()
assert ok is False
assert "animation_duration" in msg
class TestPiPConfigDefaults:
def test_default_disabled(self):
config = PiPConfig()
assert config.enabled is False
assert config.layers == []
def test_default_layer_count(self):
config = PiPConfig()
assert config.layer_count == 0
def test_default_max_z_index(self):
config = PiPConfig()
assert config.max_z_index == 0
class TestPiPConfigFromDict:
def test_none_returns_disabled(self):
config = PiPConfig.from_dict(None)
assert config.enabled is False
assert config.layer_count == 0
def test_empty_dict_returns_disabled(self):
config = PiPConfig.from_dict({})
assert config.enabled is False
def test_enabled_false_returns_disabled(self):
config = PiPConfig.from_dict({"enabled": False})
assert config.enabled is False
def test_enabled_no_layers_returns_disabled(self):
config = PiPConfig.from_dict({"enabled": True, "layers": []})
assert config.enabled is False
assert config.layer_count == 0
def test_single_layer(self):
config = PiPConfig.from_dict(
{
"enabled": True,
"layers": [
{"source": "asset_1", "position": "top_left"},
],
}
)
assert config.enabled is True
assert config.layer_count == 1
assert config.layers[0].source == "asset_1"
assert config.layers[0].position == POSITION_TOP_LEFT
def test_multiple_layers_sorted_by_z_index(self):
config = PiPConfig.from_dict(
{
"enabled": True,
"layers": [
{"source": "a", "z_index": 3},
{"source": "b", "z_index": 1},
{"source": "c", "z_index": 2},
],
}
)
assert config.layer_count == 3
assert config.layers[0].source == "b" # z=1
assert config.layers[1].source == "c" # z=2
assert config.layers[2].source == "a" # z=3
def test_invalid_layer_skipped(self):
config = PiPConfig.from_dict(
{
"enabled": True,
"layers": [
{"source": "valid_layer", "position": "center"},
{"source": "", "position": "center"}, # 空source,无效
],
}
)
assert config.layer_count == 1
assert config.layers[0].source == "valid_layer"
def test_all_invalid_layers_returns_disabled(self):
config = PiPConfig.from_dict(
{
"enabled": True,
"layers": [
{"source": ""}, # 无效
],
}
)
assert config.enabled is False
assert config.layer_count == 0
def test_layer_with_full_config(self):
config = PiPConfig.from_dict(
{
"enabled": True,
"layers": [
{
"source": "https://example.com/video.mp4",
"source_type": "url",
"position": "bottom_right",
"width": "30%",
"height": "auto",
"opacity": 0.8,
"corner_radius": 8,
"border_width": 2,
"border_color": "#00FF00",
"start_time": 2.5,
"duration": 10.0,
"animation_in": "fade",
"animation_out": "slide_right",
"animation_duration": 0.8,
"z_index": 5,
"margin": 30,
},
],
}
)
assert config.layer_count == 1
layer = config.layers[0]
assert layer.source == "https://example.com/video.mp4"
assert layer.source_type == "url"
assert layer.width == "30%"
assert layer.opacity == 0.8
assert layer.corner_radius == 8
assert layer.border_width == 2
assert layer.start_time == 2.5
assert layer.duration == 10.0
assert layer.animation_in == "fade"
assert layer.animation_out == "slide_right"
assert layer.animation_duration == 0.8
assert layer.z_index == 5
assert layer.margin == 30
def test_layer_parse_error_skipped(self):
config = PiPConfig.from_dict(
{
"enabled": True,
"layers": [
{"source": "ok", "opacity": "not_a_number"}, # 会抛ValueError
{"source": "valid"},
],
}
)
# opacity解析失败会被跳过
assert config.layer_count >= 1
# 至少valid那个还在
sources = [layer.source for layer in config.layers]
assert "valid" in sources
class TestPiPConfigProperties:
def test_layer_count(self):
config = PiPConfig(
enabled=True,
layers=[
PiPLayerConfig(source="a"),
PiPLayerConfig(source="b"),
PiPLayerConfig(source="c"),
],
)
assert config.layer_count == 3
def test_max_z_index(self):
config = PiPConfig(
enabled=True,
layers=[
PiPLayerConfig(source="a", z_index=5),
PiPLayerConfig(source="b", z_index=2),
PiPLayerConfig(source="c", z_index=10),
],
)
assert config.max_z_index == 10
def test_max_z_index_empty(self):
config = PiPConfig(enabled=False, layers=[])
assert config.max_z_index == 0
class TestParseSizeValue:
def test_int_passthrough(self):
assert parse_size_value(100, 1000) == 100
def test_int_zero_clamped_to_1(self):
assert parse_size_value(0, 1000) == 1
def test_int_negative_clamped_to_1(self):
assert parse_size_value(-10, 1000) == 1
def test_percentage_string(self):
assert parse_size_value("50%", 1000) == 500
def test_percentage_25(self):
assert parse_size_value("25%", 1920) == 480
def test_percentage_small(self):
assert parse_size_value("1%", 100) == 1
def test_percentage_zero_clamped(self):
assert parse_size_value("0%", 1000) == 1
def test_invalid_percentage_uses_default(self):
assert parse_size_value("abc%", 1000) == 250 # default 25% of 1000
def test_numeric_string(self):
assert parse_size_value("200", 1000) == 200
def test_empty_string_uses_default(self):
assert parse_size_value("", 1000) == 250
def test_custom_default_pct(self):
assert parse_size_value("invalid", 1000, default_pct=0.5) == 500
def test_float_string(self):
"""float字符串会走int()转换路径."""
result = parse_size_value("150.5", 1000)
assert result >= 1 # 至少不崩
class TestCalculatePipPosition:
def test_top_left(self):
x, y = calculate_pip_position(POSITION_TOP_LEFT, 1920, 1080, 400, 300, margin=20)
assert (x, y) == (20, 20)
def test_top_right(self):
x, y = calculate_pip_position(POSITION_TOP_RIGHT, 1920, 1080, 400, 300, margin=20)
assert (x, y) == (1920 - 400 - 20, 20)
def test_bottom_right(self):
x, y = calculate_pip_position(POSITION_BOTTOM_RIGHT, 1920, 1080, 400, 300, margin=20)
assert (x, y) == (1920 - 400 - 20, 1080 - 300 - 20)
def test_bottom_left(self):
x, y = calculate_pip_position(POSITION_BOTTOM_LEFT, 1920, 1080, 400, 300, margin=20)
assert (x, y) == (20, 1080 - 300 - 20)
def test_center(self):
x, y = calculate_pip_position(POSITION_CENTER, 1920, 1080, 400, 300, margin=20)
assert x == (1920 - 400) // 2
assert y == (1080 - 300) // 2
def test_top_center(self):
x, y = calculate_pip_position("top_center", 1920, 1080, 400, 300, margin=20)
assert x == (1920 - 400) // 2
assert y == 20
def test_bottom_center(self):
x, y = calculate_pip_position("bottom_center", 1920, 1080, 400, 300, margin=30)
assert x == (1920 - 400) // 2
assert y == 1080 - 300 - 30
def test_center_left(self):
x, y = calculate_pip_position("center_left", 1920, 1080, 400, 300, margin=20)
assert x == 20
assert y == (1080 - 300) // 2
def test_center_right(self):
x, y = calculate_pip_position("center_right", 1920, 1080, 400, 300, margin=20)
assert x == 1920 - 400 - 20
assert y == (1080 - 300) // 2
def test_custom_position_pixel_values(self):
x, y = calculate_pip_position("custom", 1920, 1080, 400, 300, custom_x=100, custom_y=200)
assert (x, y) == (100, 200)
def test_custom_position_percentage(self):
x, y = calculate_pip_position("custom", 1920, 1080, 400, 300, custom_x="10%", custom_y="20%")
assert x == 192 # 10% of 1920
assert y == 216 # 20% of 1080
def test_different_margin(self):
x, y = calculate_pip_position(POSITION_TOP_LEFT, 1920, 1080, 400, 300, margin=50)
assert (x, y) == (50, 50)
def test_invalid_position_defaults_to_bottom_right(self):
x, y = calculate_pip_position("invalid_pos", 1920, 1080, 400, 300, margin=20)
assert (x, y) == (1920 - 400 - 20, 1080 - 300 - 20)
def test_small_output_size(self):
x, y = calculate_pip_position(POSITION_CENTER, 100, 100, 50, 50, margin=5)
assert x == 25
assert y == 25