test(ci): quarantine concurrency-sensitive flakes into a serial pass (#6347)

* test(ci): quarantine concurrency-sensitive flakes into a serial pass (tests/unit/serial/)

glm-coding-plan-monthly-3580, quota-division-blocks and provider-health-autopilot
fail under --test-concurrency>1 CPU contention but pass isolated (v3.8.45 release
benchmark: this class cost ~28min re-runs per CI round). They now live in
tests/unit/serial/ and run in a dedicated --test-concurrency=1 step appended to
every runner (test:unit, :ci, :ci:shard, :fast, :shard:1/2, coverage:runner —
sharded variants shard the serial pass too, so concurrent shard jobs never
self-collide). Discovery + TIA gates know the new glob; stryker.conf.json path
updated. Guard: tests/unit/test-serial-quarantine.test.ts (4 tests).

* test(ci): quarantine combo-health-autopilot too (async logger writes after test end under load)

Fresh evidence from this PR's own CI: all 3 subtests pass but an async log write
lands after the test ends (ENOENT app.log -> uncaughtException) — same
concurrency-flake family. Moved to tests/unit/serial/, imports adjusted,
stryker path updated, guard test now asserts 4 quarantined files.
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-07-06 10:41:43 -03:00
committed by GitHub
parent 5d07bdd76f
commit c0430342c6
9 changed files with 114 additions and 34 deletions

View File

@@ -97,13 +97,13 @@
"electron:build:linux": "npm run build && cd electron && npm run build:linux",
"electron:smoke:packaged": "node scripts/dev/smoke-electron-packaged.mjs",
"test": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-concurrency=20 tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-concurrency=20 \"tests/unit/dashboard/**/*.test.ts\"",
"test:unit": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=20 tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=20 \"tests/unit/dashboard/**/*.test.ts\"",
"test:unit:ci": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 \"tests/unit/dashboard/**/*.test.ts\"",
"test:unit:ci:shard": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=4096 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 --test-shard=$TEST_SHARD tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=4096 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 --test-shard=$TEST_SHARD \"tests/unit/dashboard/**/*.test.ts\"",
"test:unit:fast": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-isolation=none tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-isolation=none \"tests/unit/dashboard/**/*.test.ts\"",
"test:unit": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=20 tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=20 \"tests/unit/dashboard/**/*.test.ts\" && npm run test:unit:serial",
"test:unit:ci": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 \"tests/unit/dashboard/**/*.test.ts\" && npm run test:unit:serial",
"test:unit:ci:shard": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=4096 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 --test-shard=$TEST_SHARD tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=4096 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=4 --test-shard=$TEST_SHARD \"tests/unit/dashboard/**/*.test.ts\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=4096 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=1 --test-shard=$TEST_SHARD \"tests/unit/serial/**/*.test.ts\"",
"test:unit:fast": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-isolation=none tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-isolation=none \"tests/unit/dashboard/**/*.test.ts\" && npm run test:unit:serial",
"test:unit:shard": "concurrently --kill-others-on-fail -n s1,s2 \"npm:test:unit:shard:1\" \"npm:test:unit:shard:2\"",
"test:unit:shard:1": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=10 --test-shard=1/2 tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=10 --test-shard=1/2 \"tests/unit/dashboard/**/*.test.ts\"",
"test:unit:shard:2": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=10 --test-shard=2/2 tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=10 --test-shard=2/2 \"tests/unit/dashboard/**/*.test.ts\"",
"test:unit:shard:1": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=10 --test-shard=1/2 tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=10 --test-shard=1/2 \"tests/unit/dashboard/**/*.test.ts\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=1 --test-shard=1/2 \"tests/unit/serial/**/*.test.ts\"",
"test:unit:shard:2": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=10 --test-shard=2/2 tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=10 --test-shard=2/2 \"tests/unit/dashboard/**/*.test.ts\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=1 --test-shard=2/2 \"tests/unit/serial/**/*.test.ts\"",
"test:plan3": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test tests/unit/plan3-p0.test.ts",
"test:fixes": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test tests/unit/fixes-p1.test.ts",
"test:security": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test tests/unit/security-fase01.test.ts",
@@ -212,7 +212,8 @@
"build:cli-api": "node --import tsx/esm scripts/cli/generate-api-commands.mjs",
"release:contributors": "node scripts/release/gen-contributors.mjs",
"release:uncovered": "node scripts/release/list-uncovered-commits.mjs",
"test:coverage:runner": "node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=8 tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true NODE_OPTIONS=--max-old-space-size=8192 c8 --merge-async --output-dir coverage --exclude=tests/** --exclude=**/*.test.* --reporter=text-summary --reporter=html --reporter=json-summary --reporter=lcov --check-coverage --statements 60 --lines 60 --functions 60 --branches 60 node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=8 \"tests/unit/dashboard/**/*.test.ts\""
"test:coverage:runner": "node --max-old-space-size=8192 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=8 tests/unit/*.test.ts \"tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,db,db-adapters,docs,gamification,guardrails,lib,mcp,memory,runtime,security,services,settings,shared,ui,usage}/**/*.test.ts\" \"tests/unit/**/*.test.mjs\" && cross-env DISABLE_SQLITE_AUTO_BACKUP=true NODE_OPTIONS=--max-old-space-size=8192 c8 --merge-async --output-dir coverage --exclude=tests/** --exclude=**/*.test.* --reporter=text-summary --reporter=html --reporter=json-summary --reporter=lcov --check-coverage --statements 60 --lines 60 --functions 60 --branches 60 node --max-old-space-size=8192 --import tsx --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=8 \"tests/unit/dashboard/**/*.test.ts\" && npm run test:unit:serial",
"test:unit:serial": "cross-env DISABLE_SQLITE_AUTO_BACKUP=true node --max-old-space-size=4096 --import tsx/esm --import ./open-sse/utils/setupPolyfill.ts --import ./tests/_setup/isolateDataDir.ts --test --test-force-exit --test-concurrency=1 \"tests/unit/serial/**/*.test.ts\""
},
"dependencies": {
"@aws-sdk/client-bedrock-runtime": "^3.1073.0",

View File

@@ -67,6 +67,11 @@ export const COLLECTORS = [
// 24.18 do CI; no 24.16 local vira um crawl de ~60s/arquivo). O resto da suíte roda
// sob tsx/esm (~-50% de bootstrap por processo). Plano mestre testes+CI, QW-b.
{ glob: "tests/unit/dashboard/**/*.test.ts", sources: ["package.json"] },
// Quarentena de flakes de concorrência (plano melhorias v3.8.46, P0.3): arquivos
// sensíveis a contenção de CPU/timing (classe glm-3580 / quota-division /
// provider-health-autopilot) rodam num passo dedicado --test-concurrency=1 ao FIM
// de cada runner. Fora dos globs paralelos acima por diretório próprio.
{ glob: "tests/unit/serial/**/*.test.ts", sources: ["package.json"] },
// Órfãos religados (plano mestre QW-c): arquivos .test.mjs (top-level + db/ + feature-triage/) — fora do glob
// *.test.ts histórico, nunca rodava em job nenhum (53 casos recuperados).
{ glob: "tests/unit/**/*.test.mjs", sources: ["package.json"] },

View File

@@ -61,6 +61,8 @@ const testFiles = globSync(
[
"tests/unit/*.test.ts",
"tests/unit/{api,auth,authz,build,cli,cli-helper,combo,compression,correctness,cors,dashboard,db,db-adapters,docs,executors,gamification,guardrails,lib,mcp,runtime,security,services,settings,shared,ui}/**/*.test.ts",
// Quarentena serial (P0.3): também são node:test — a TIA precisa mapeá-los.
"tests/unit/serial/**/*.test.ts",
],
{ cwd: ROOT, absolute: true }
);

View File

@@ -118,7 +118,7 @@
"tests/unit/combo-config.test.ts",
"tests/unit/combo-context-relay.test.ts",
"tests/unit/combo-headroom-strategy.test.ts",
"tests/unit/combo-health-autopilot.test.ts",
"tests/unit/serial/combo-health-autopilot.test.ts",
"tests/unit/combo-health-dashboard.test.ts",
"tests/unit/combo-health-route.test.ts",
"tests/unit/combo-hedging.test.ts",
@@ -189,7 +189,7 @@
"tests/unit/plugin-sandbox-permissions.test.ts",
"tests/unit/plugins-route-error-sanitization.test.ts",
"tests/unit/provider-error-rules.test.ts",
"tests/unit/provider-health-autopilot.test.ts",
"tests/unit/serial/provider-health-autopilot.test.ts",
"tests/unit/provider-health-matrix.test.ts",
"tests/unit/provider-request-failure-pipeline.test.ts",
"tests/unit/public-client-ids-3493.test.ts",

View File

@@ -4,7 +4,7 @@ import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { makeManagementSessionRequest } from "../helpers/managementSession.ts";
import { makeManagementSessionRequest } from "../../helpers/managementSession.ts";
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-combo-autopilot-"));
const ORIGINAL_DATA_DIR = process.env.DATA_DIR;
@@ -13,15 +13,15 @@ const ORIGINAL_JWT_SECRET = process.env.JWT_SECRET;
process.env.DATA_DIR = TEST_DATA_DIR;
const core = await import("../../src/lib/db/core.ts");
const combosDb = await import("../../src/lib/db/combos.ts");
const settingsDb = await import("../../src/lib/db/settings.ts");
const quotaSnapshotsDb = await import("../../src/lib/db/quotaSnapshots.ts");
const callLogs = await import("../../src/lib/usage/callLogs.ts");
const comboMetrics = await import("../../open-sse/services/comboMetrics.ts");
const comboAutopilot = await import("../../src/lib/monitoring/comboHealthAutopilot.ts");
const route = await import("../../src/app/api/usage/combo-health-autopilot/route.ts");
const { normalizeComboStep } = await import("../../src/lib/combos/steps.ts");
const core = await import("../../../src/lib/db/core.ts");
const combosDb = await import("../../../src/lib/db/combos.ts");
const settingsDb = await import("../../../src/lib/db/settings.ts");
const quotaSnapshotsDb = await import("../../../src/lib/db/quotaSnapshots.ts");
const callLogs = await import("../../../src/lib/usage/callLogs.ts");
const comboMetrics = await import("../../../open-sse/services/comboMetrics.ts");
const comboAutopilot = await import("../../../src/lib/monitoring/comboHealthAutopilot.ts");
const route = await import("../../../src/app/api/usage/combo-health-autopilot/route.ts");
const { normalizeComboStep } = await import("../../../src/lib/combos/steps.ts");
async function resetStorage() {
comboMetrics.resetAllComboMetrics();

View File

@@ -1,7 +1,7 @@
import test from "node:test";
import assert from "node:assert/strict";
import { glmMonthlyRemainingPercentage } from "../../open-sse/services/usage.ts";
import { glmMonthlyRemainingPercentage } from "../../../open-sse/services/usage.ts";
// #3580 — z.ai/GLM coding plans have no monthly cap (only 5-hour windows), so the
// quota API reports the TIME_LIMIT ("Monthly") entry with total=0. The previous

View File

@@ -4,7 +4,7 @@ import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { NextRequest } from "next/server";
import { makeManagementSessionRequest } from "../helpers/managementSession.ts";
import { makeManagementSessionRequest } from "../../helpers/managementSession.ts";
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-health-autopilot-"));
const ORIGINAL_DATA_DIR = process.env.DATA_DIR;
@@ -13,14 +13,14 @@ const ORIGINAL_JWT_SECRET = process.env.JWT_SECRET;
process.env.DATA_DIR = TEST_DATA_DIR;
const core = await import("../../src/lib/db/core.ts");
const settingsDb = await import("../../src/lib/db/settings.ts");
const providersDb = await import("../../src/lib/db/providers.ts");
const autopilot = await import("../../src/lib/monitoring/providerHealthAutopilot.ts");
const actionsRoute = await import("../../src/app/api/providers/health-autopilot/actions/route.ts");
const reportRoute = await import("../../src/app/api/providers/health-autopilot/route.ts");
const routeGuard = await import("../../src/server/authz/routeGuard.ts");
const authzPipeline = await import("../../src/server/authz/pipeline.ts");
const core = await import("../../../src/lib/db/core.ts");
const settingsDb = await import("../../../src/lib/db/settings.ts");
const providersDb = await import("../../../src/lib/db/providers.ts");
const autopilot = await import("../../../src/lib/monitoring/providerHealthAutopilot.ts");
const actionsRoute = await import("../../../src/app/api/providers/health-autopilot/actions/route.ts");
const reportRoute = await import("../../../src/app/api/providers/health-autopilot/route.ts");
const routeGuard = await import("../../../src/server/authz/routeGuard.ts");
const authzPipeline = await import("../../../src/server/authz/pipeline.ts");
const accountFallback = await import("@omniroute/open-sse/services/accountFallback");
const PROVIDER = "autopilot-test-provider";

View File

@@ -42,11 +42,11 @@ const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-division-
process.env.DATA_DIR = TEST_DATA_DIR;
// ── Imports ──────────────────────────────────────────────────────────────────
const providerPlans = await import("../../src/lib/db/providerPlans.ts");
const quotaPools = await import("../../src/lib/db/quotaPools.ts");
const { SqliteQuotaStore } = await import("../../src/lib/quota/sqliteQuotaStore.ts");
const { enforceQuotaShare } = await import("../../src/lib/quota/enforce.ts");
const core = await import("../../src/lib/db/core.ts");
const providerPlans = await import("../../../src/lib/db/providerPlans.ts");
const quotaPools = await import("../../../src/lib/db/quotaPools.ts");
const { SqliteQuotaStore } = await import("../../../src/lib/quota/sqliteQuotaStore.ts");
const { enforceQuotaShare } = await import("../../../src/lib/quota/enforce.ts");
const core = await import("../../../src/lib/db/core.ts");
// ── Suite cleanup ─────────────────────────────────────────────────────────────
test.after(() => {

View File

@@ -0,0 +1,72 @@
import { test } from "node:test";
import assert from "node:assert/strict";
import { readFileSync, readdirSync } from "node:fs";
import { fileURLToPath } from "node:url";
import path from "node:path";
// Quarentena de flakes de concorrência (plano melhorias v3.8.46, P0.3).
// Os arquivos em tests/unit/serial/ falham sob contenção de CPU (--test-concurrency>1
// com a suíte inteira) mas passam isolados — classe glm-3580 / quota-division /
// provider-health-autopilot, que custava re-runs de ~28min por rodada de CI.
// Este guard garante que a quarentena continua ligada em TODOS os runners.
const ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), "../..");
const pkg = JSON.parse(readFileSync(path.join(ROOT, "package.json"), "utf8"));
const scripts: Record<string, string> = pkg.scripts;
const SERIAL_GLOB = 'tests/unit/serial/**/*.test.ts';
test("test:unit:serial existe e roda o diretório de quarentena com concurrency=1", () => {
const s = scripts["test:unit:serial"];
assert.ok(s, "script test:unit:serial deve existir");
assert.ok(s.includes("--test-concurrency=1"), "quarentena deve rodar serial");
assert.ok(s.includes(SERIAL_GLOB), "quarentena deve apontar para tests/unit/serial/");
});
test("todos os runners paralelos terminam com o passo serial", () => {
for (const key of ["test:unit", "test:unit:ci", "test:unit:fast", "test:coverage:runner"]) {
assert.ok(
scripts[key].endsWith("&& npm run test:unit:serial"),
`${key} deve encadear o passo serial no fim`
);
}
for (const [key, shard] of [
["test:unit:ci:shard", "$TEST_SHARD"],
["test:unit:shard:1", "1/2"],
["test:unit:shard:2", "2/2"],
] as const) {
const s = scripts[key];
const serialPart = s.slice(s.lastIndexOf("&&"));
assert.ok(serialPart.includes("--test-concurrency=1"), `${key}: passo serial ausente`);
assert.ok(
serialPart.includes(`--test-shard=${shard}`),
`${key}: o passo serial deve ser SHARDADO (${shard}) — sem isso os dois shards rodam ` +
`os mesmos arquivos ao mesmo tempo e recriam a colisão que a quarentena elimina`
);
assert.ok(serialPart.includes(SERIAL_GLOB), `${key}: glob da quarentena ausente`);
}
});
test("os globs paralelos NÃO capturam tests/unit/serial/ (sem dupla execução)", () => {
const parallel = scripts["test:unit"].split("&& npm run test:unit:serial")[0];
assert.ok(!parallel.includes("serial"), "parte paralela não deve referenciar serial/");
// o glob de subdiretórios é uma brace-list explícita — 'serial' não pode entrar nela
const braceList = parallel.match(/tests\/unit\/\{([^}]+)\}/)?.[1] ?? "";
assert.ok(!braceList.split(",").includes("serial"), "brace-list não deve conter 'serial'");
});
test("a quarentena contém os 3 flakes conhecidos e os gates de discovery/TIA a conhecem", () => {
const files = readdirSync(path.join(ROOT, "tests/unit/serial"));
for (const f of [
"glm-coding-plan-monthly-3580.test.ts",
"quota-division-blocks.test.ts",
"provider-health-autopilot.test.ts",
"combo-health-autopilot.test.ts",
]) {
assert.ok(files.includes(f), `${f} deve estar na quarentena`);
}
const discovery = readFileSync(path.join(ROOT, "scripts/check/check-test-discovery.mjs"), "utf8");
assert.ok(discovery.includes(SERIAL_GLOB), "check-test-discovery deve listar o glob serial");
const tia = readFileSync(path.join(ROOT, "scripts/quality/build-test-impact-map.mjs"), "utf8");
assert.ok(tia.includes(SERIAL_GLOB), "build-test-impact-map deve listar o glob serial");
});