From 85c04335c63a20ace61ee66955ab55d31e2bf0b4 Mon Sep 17 00:00:00 2001 From: AAEE86 Date: Thu, 24 Sep 2026 16:50:33 +0800 Subject: [PATCH] ci: guard rust scope detection against false green MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - changes 脚本加 set -euo pipefail,git fetch/diff 失败即中止,避免写出 rust=false/shell=false 让下游误判为“无需测试”。 - changed_paths 为空(异常事件)时保守置 rust=true/shell=true,宁可多跑不漏测。 - check 与 data_db_smoke 增加 needs.changes.result 兜底。 - 恢复 push/pull_request 的 paths 白名单,并补齐 rust-toolchain.toml、 .cargo/**、*.sql,workflow 触发规则与分类脚本对齐,避免“分类正确但 workflow 未启动”的漏测。 - nextest 删除硬编码 test-threads,改用默认 num-cpus,避免在大规格 runner 上主动压低并发;保留 slow-timeout 卡死保护。 - 补真实 TCP smoke test,覆盖管理员安全接口的监听端口与 HTTP/JSON 链路。 --- .config/nextest.toml | 10 +-- .github/workflows/rust-ci.yml | 75 +++++++++++++++++++ .../src/tests/control/admin/security.rs | 36 +++++++++ 3 files changed, 115 insertions(+), 6 deletions(-) diff --git a/.config/nextest.toml b/.config/nextest.toml index 0abc58828..ea686cd7c 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -1,8 +1,6 @@ -# GitHub-hosted ubuntu runner 当前按 4 vCPU 配置;固定线程数可避免 runner -# 规格变化时测试并发和内存峰值随之漂移。 +# 不固定 test-threads:nextest 默认按 num-cpus 并发,固定值会在更大规格的 +# runner 或本地开发机上主动压低并发、反而变慢,且无法表达 min(4, num-cpus)。 +# 这里只保留卡死保护,避免单个挂起用例拖满整个 job。 [profile.default] -test-threads = 4 - -# 60 秒后标记慢测试,连续两轮仍未结束则终止,避免单个卡死用例拖满整个 job。 -# 真实连接/数据库测试仍有足够时间完成;超时结果保持失败,不隐藏回归。 +# 60 秒后标记慢测试,连续两轮仍未结束则终止;超时结果保持失败,不隐藏回归。 slow-timeout = { period = "60s", terminate-after = 2, grace-period = "10s" } diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index efa410462..babdea7d7 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -12,7 +12,61 @@ on: branches: - master - main + paths: + - "Cargo.toml" + - "Cargo.lock" + - "rust-toolchain.toml" + - ".cargo/**" + - "crates/**" + - "apps/**" + - "*.sql" + - "install.sh" + - "deploy.sh" + - "update.sh" + - "generate_keys.sh" + - ".env.example" + - "README.md" + - "Dockerfile.app" + - "docker-compose.yml" + - "docker-compose.single-node.yml" + - "docker-compose.local.yml" + - "docker-compose.release-local.yml" + - "tests/compose_database_config_test.py" + - "tests/install_*_test.sh" + - "tests/deploy_*_test.sh" + - "tests/update_*_test.sh" + - "tests/release_supply_chain_test.sh" + - "tests/tunnel_installer_config_security_test.sh" + - ".github/workflows/*.yml" + - ".github/workflows/*.yaml" pull_request: + paths: + - "Cargo.toml" + - "Cargo.lock" + - "rust-toolchain.toml" + - ".cargo/**" + - "crates/**" + - "apps/**" + - "*.sql" + - "install.sh" + - "deploy.sh" + - "update.sh" + - "generate_keys.sh" + - ".env.example" + - "README.md" + - "Dockerfile.app" + - "docker-compose.yml" + - "docker-compose.single-node.yml" + - "docker-compose.local.yml" + - "docker-compose.release-local.yml" + - "tests/compose_database_config_test.py" + - "tests/install_*_test.sh" + - "tests/deploy_*_test.sh" + - "tests/update_*_test.sh" + - "tests/release_supply_chain_test.sh" + - "tests/tunnel_installer_config_security_test.sh" + - ".github/workflows/*.yml" + - ".github/workflows/*.yaml" concurrency: group: rust-ci-${{ github.event_name }}-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} @@ -45,6 +99,10 @@ jobs: env: RUST_CI_FULL_SCOPE: ${{ inputs.full_scope || false }} run: | + # 任何命令失败都必须让本 job 失败,否则 git fetch/diff 出错后仍会写出 + # rust=false/shell=false,下游会误判为“无需测试”而假绿放行。 + set -euo pipefail + # Nightly 通过 workflow_call 显式传入 full_scope;普通 push/PR 只按源码和构建 # 指纹触发 Rust jobs,安装脚本、Compose、README 等由 shell scope 覆盖。 if [ "$RUST_CI_FULL_SCOPE" = "true" ]; then @@ -67,6 +125,14 @@ jobs: changed_paths=$(git ls-files) fi + # 防御性兜底:diff 结果为空(异常事件或比较失败)时按全量运行, + # 宁可多跑也不能漏测。 + if [ -z "$changed_paths" ]; then + echo "rust=true" >> "$GITHUB_OUTPUT" + echo "shell=true" >> "$GITHUB_OUTPUT" + exit 0 + fi + rust=false shell=false while IFS= read -r path; do @@ -652,6 +718,10 @@ jobs: steps: - name: Verify database smoke jobs run: | + if [ "${{ needs.changes.result }}" != "success" ]; then + echo "Scope detection failed" + exit 1 + fi if [ "${{ needs.changes.outputs.rust }}" != "true" ]; then echo "Rust scope unchanged; database smoke jobs skipped" exit 0 @@ -675,6 +745,11 @@ jobs: steps: - name: Verify required jobs run: | + # changes 失败或未产出 scope 时不允许直接放行,避免假绿。 + if [ "${{ needs.changes.result }}" != "success" ]; then + echo "Scope detection failed" + exit 1 + fi rust="${{ needs.changes.outputs.rust }}" shell="${{ needs.changes.outputs.shell }}" diff --git a/apps/aether-gateway/src/tests/control/admin/security.rs b/apps/aether-gateway/src/tests/control/admin/security.rs index 71b469cf3..aa96d2d2d 100644 --- a/apps/aether-gateway/src/tests/control/admin/security.rs +++ b/apps/aether-gateway/src/tests/control/admin/security.rs @@ -387,6 +387,42 @@ async fn gateway_handles_admin_security_blacklist_add_locally_with_trusted_admin assert_eq!(upstream_count, 0); } +/// 真实 TCP 冒烟测试:其余安全用例已改为进程内 Router 调用以提速,这里保留一条 +/// 覆盖网络层装配(真实监听端口、HTTP 请求头传递、JSON 收发)的端到端路径。 +/// +/// `/api/admin/security/*` 在路由分类中是本地管理端点 +/// (`execution_runtime_candidate: false`),架构上不经过任何可注入 base_url 的上游, +/// 因此这里不构造无意义的“上游计数器”,只验证真实链路下本地处理结果正确。 +#[tokio::test] +async fn gateway_serves_admin_security_blacklist_over_real_tcp() { + let gateway = build_router_with_state(AppState::new().expect("gateway should build")); + let (gateway_url, gateway_handle) = start_server(gateway).await; + + let response = reqwest::Client::new() + .post(format!("{gateway_url}/api/admin/security/ip/blacklist")) + .header(GATEWAY_HEADER, "rust-phase3b") + .header(TRUSTED_ADMIN_USER_ID_HEADER, "admin-user-123") + .header(TRUSTED_ADMIN_USER_ROLE_HEADER, "admin") + .header(TRUSTED_ADMIN_SESSION_ID_HEADER, "session-123") + .json(&json!({ "ip_address": "1.2.3.4", "reason": "manual", "ttl": 60 })) + .send() + .await + .expect("request should reach the gateway over TCP"); + + let status = response.status(); + let payload: serde_json::Value = response + .json() + .await + .expect("gateway response should be json"); + assert_eq!(status, StatusCode::OK); + assert_eq!(payload["success"], true); + assert_eq!(payload["message"], "IP 1.2.3.4 已加入黑名单"); + assert_eq!(payload["reason"], "manual"); + assert_eq!(payload["ttl"], 60); + + gateway_handle.abort(); +} + #[tokio::test] async fn gateway_rejects_invalid_admin_security_blacklist_ip() { let gateway = build_router_with_state(AppState::new().expect("gateway should build"));