From 647bac5b210a97621680a02300812021763b43fc Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Thu, 30 Jul 2026 05:33:00 -0700 Subject: [PATCH] =?UTF-8?q?test(dashboard):=20lowercase=20header=20keys=20?= =?UTF-8?q?+=20the=20client=20stamp=20(1=20red=20=E2=86=92=200)=20?= =?UTF-8?q?=E2=80=94=20and=2088=20dashboard=20failures=20CI=20never=20runs?= =?UTF-8?q?=20(#2732)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## The fix `app/api/client.ts` builds request headers through a `Headers` object — which lower-cases every key — and stamps `x-fusion-client: dashboard-ui` on every dashboard-originated request so the server can attribute the caller (the FN-8609 delete-attribution surface). This test asserted `"Content-Type"`, and `expect.objectContaining` compares keys **case-sensitively**, so it failed on casing alone. Rather than only lower-casing the key, it now pins the **client stamp** too — that header is the point of the feature and nothing else in this file covered it. | Check | Result | |---|---| | `plugin-setup-api.test.ts` | 1 failed → **3 passed** | | client stamp neutered in `client.ts` | **1 failed** / 2 passed — load-bearing | | `pnpm lint`, dashboard app `tsc` | clean | ## Method correction — I nearly filed 40 phantom failures Measuring this package with a raw `vitest run` reports **~40 failing files**. That number is worthless: `@fusion/dashboard`'s own `test` script is `node scripts/run-quality-tests.mjs`, which runs the quality projects as **separate invocations** with per-group heap sizes and exclusions. Running every project in one process fails en masse for reasons unrelated to the code. Correct command — `pnpm --filter @fusion/dashboard test` — gives **9 failing files / 88 failing tests**, exit 1. ## The finding: those 88 failures are never executed in CI I first concluded "CI shows no dashboard failures, so these are local-only." **That was wrong, and the reason matters.** CI does schedule the dashboard quality groups — they are distributed across all four shards as individual `test:quality:*` invocations. Shard 2's plan, for example: ``` [ci-test-shard] shard 2/4: @fusion/core [2/2], @fusion/dashboard run test:quality:app:components-b, @fusion/dashboard run test:quality:app:backfill-1, ... ``` But only two invocations ever get a `(watchdog budget 1500s)` start line: the plugins group and `@fusion/core [2/2]`. `components-b` never starts, because the shard aborts on the first failing package — `ERR_PNPM_RECURSIVE_RUN_FIRST_FAIL`, present in shards 1, 2 and 4. **So the dashboard quality groups are not passing — they are unrun**, behind a package that fails first. Consequences: 1. **Fixing core/engine/CLI will unmask 88 dashboard failures.** My merged PRs move shards 1/2/4 toward green; as each earlier package stops failing, these groups begin executing for the first time. Expect the shard counts to *rise* before they fall — that is progress, not regression. 2. **Reading shard conclusions is misleading.** A shard says "core failed"; it does not say "and everything scheduled after core never ran." Where the 88 live (all files currently unowned): | File | Failures | Lane / shard | |---|---:|---| | `TaskDetailModal.inline-editing-and-integrations` | 50 | `components-b` / shard 2 | | `auto-merge-toggle-blank.mobile-integration` | 13 | `components-a` / shard 3 | | `auto-merge-toggle-blank.mobile` | 8 | `components-a` / shard 3 | | `TaskDetailModal` | 6 | `components-b` / shard 2 | | `SecretsView` | 4 | `components-b` / shard 2 | | `WorkflowNodeEditor` | 3 | `components-b` / shard 2 | | `TaskCard` + `TaskCard.badge-wrap` | 3 | `components-b` / shard 2 | | `board-mobile` | 1 | `components-a` / shard 3 | Dominant symptoms: `Unable to fire a "click" event - please provide a DOM element` (29), `Unable to find an accessible element with the role "checkbox" and name "Auto-merge"` (13), `expected null to be truthy` (7) — consistent with a small number of shared render/affordance causes rather than 88 independent bugs, but I have not isolated them. **I am not starting that repair in this PR.** It is a 9-file, 88-test area needing per-cluster diagnosis, and bundling it behind a one-line header fix would produce exactly the shallow work this program keeps rejecting. Filed here with the correct measurement command, the lane/shard mapping, and the reason CI has been silent about it. --- .../app/api/__tests__/plugin-setup-api.test.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/dashboard/app/api/__tests__/plugin-setup-api.test.ts b/packages/dashboard/app/api/__tests__/plugin-setup-api.test.ts index 0cc6483e6d..76359cdb35 100644 --- a/packages/dashboard/app/api/__tests__/plugin-setup-api.test.ts +++ b/packages/dashboard/app/api/__tests__/plugin-setup-api.test.ts @@ -17,10 +17,24 @@ describe("plugin setup API helpers", () => { const result = await fetchPluginSetupStatus("plugin/id"); expect(result).toEqual({ hasSetup: true, status: "installed" }); + /* + FNXC:TaskDeleteAttribution 2026-07-30-06:20: + Header keys are LOWERCASE and now include the client stamp. `app/api/client.ts` builds request + headers through a `Headers` object (which lower-cases every key) and sets + `x-fusion-client: dashboard-ui` on every dashboard-originated request so the server can attribute + the caller — the FN-8609 delete-attribution surface. + + This asserted `"Content-Type"`, and `objectContaining` compares keys case-SENSITIVELY, so it broke + on the casing alone. Rather than just lower-casing the key, this now pins the client stamp too: + that header is the point of the feature, and nothing else in this file covered it. + */ expect(fetchMock).toHaveBeenCalledWith( "/api/plugins/plugin%2Fid/setup-status", expect.objectContaining({ - headers: expect.objectContaining({ "Content-Type": "application/json" }), + headers: expect.objectContaining({ + "content-type": "application/json", + "x-fusion-client": "dashboard-ui", + }), }), ); });