From c08497c963caf92991951f77904a21349c20af81 Mon Sep 17 00:00:00 2001 From: AAEE86 Date: Thu, 24 Sep 2026 13:37:25 +0800 Subject: [PATCH 1/2] ci: make nightly rust scope explicit --- .github/workflows/nightly.yml | 2 ++ .github/workflows/rust-ci.yml | 21 +++++++++++++++++---- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 109a38e6c..802663651 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -63,6 +63,8 @@ jobs: name: Rust CI needs: source uses: ./.github/workflows/rust-ci.yml + with: + full_scope: true rust_extended: name: Rust extended checks diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml index c8d6dfdc2..efa410462 100644 --- a/.github/workflows/rust-ci.yml +++ b/.github/workflows/rust-ci.yml @@ -2,6 +2,12 @@ name: Rust CI on: workflow_call: + inputs: + full_scope: + description: "Run all Rust and shell scopes, used by Nightly" + required: false + type: boolean + default: false push: branches: - master @@ -36,19 +42,26 @@ jobs: - name: Classify changed paths id: scope shell: bash + env: + RUST_CI_FULL_SCOPE: ${{ inputs.full_scope || false }} run: | - # workflow_call(Nightly)仍须完整执行;普通 push/PR 只按源码和构建 + # Nightly 通过 workflow_call 显式传入 full_scope;普通 push/PR 只按源码和构建 # 指纹触发 Rust jobs,安装脚本、Compose、README 等由 shell scope 覆盖。 - if [ "$GITHUB_EVENT_NAME" = "workflow_call" ]; then + if [ "$RUST_CI_FULL_SCOPE" = "true" ]; then echo "rust=true" >> "$GITHUB_OUTPUT" echo "shell=true" >> "$GITHUB_OUTPUT" exit 0 fi - if [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then + if [ "$GITHUB_EVENT_NAME" = "pull_request" ] \ + && [ -n "${GITHUB_BASE_REF:-}" ] \ + && [ -n "${GITHUB_SHA:-}" ]; then git fetch --no-tags origin "$GITHUB_BASE_REF" --depth=1 changed_paths=$(git diff --name-only "origin/$GITHUB_BASE_REF...$GITHUB_SHA") - elif [ "$GITHUB_EVENT_NAME" = "push" ] && [ "$GITHUB_EVENT_BEFORE" != "0000000000000000000000000000000000000000" ]; then + elif [ "$GITHUB_EVENT_NAME" = "push" ] \ + && [ -n "${GITHUB_EVENT_BEFORE:-}" ] \ + && [ "$GITHUB_EVENT_BEFORE" != "0000000000000000000000000000000000000000" ] \ + && [ -n "${GITHUB_SHA:-}" ]; then changed_paths=$(git diff --name-only "$GITHUB_EVENT_BEFORE" "$GITHUB_SHA") else changed_paths=$(git ls-files) From 85c04335c63a20ace61ee66955ab55d31e2bf0b4 Mon Sep 17 00:00:00 2001 From: AAEE86 Date: Thu, 24 Sep 2026 16:50:33 +0800 Subject: [PATCH 2/2] 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"));