Uh oh!
There was an error while loading. Please reload this page.
test(plugin-report): serve the dimension-metadata probe from a double, not the network (#5225) - #5283
Merged
os-support-ai merged 1 commit intoAug 19, 2026
Conversation
…, not the network (#5225) `DatasetReportRenderer.test.tsx` made 8 real TCP connections per run to `http://localhost:3000` (16 ECONNREFUSED lines - two per attempt), while `packages/plugin-report/src` contains no `fetch(` and no literal `3000`. Traced with a `net.Socket.prototype.connect` probe, the escape is: DatasetReportRenderer (tabular / matrix / chart branches) -> useDatasetDimensionLabels re-export of @object-ui/react's hook -> useDatasetDimensionMeta packages/react/src/hooks/useDatasetDimensionLabels.ts:144 -> `const doFetch = apiFetch ?? fetch` -> loadDimensionFieldMeta packages/core/src/utils/chart-series.ts:1178 GET /api/v1/meta/object/:object With no `SchemaRendererProvider` in the tree the hook has no host `apiFetch` and degrades to the GLOBAL fetch - deliberate and pinned (objectui#4121). Under happy-dom that global fetch is a real HTTP client, and vitest's happy-dom environment defaults the document URL to `http://localhost:3000`, so the relative `/api/v1/...` resolved to a live request. Why the test's own mock never intercepted it: the metadata read is a SECOND data channel. `dataSource.queryDataset` serves the report ROWS; the dimension metadata never goes through `dataSource`. Only the 8 cases whose mocked result carries `object` reach it - that field is what the hook keys on. The read is best-effort (`catch {}`), which is why 42 tests stayed green. Answer it from a RECORDING double, the shape objectui#3339 / #4106 settled on and this package's own `DatasetReportRenderer.localSelectI18n.test.tsx` already uses. Not a blanket network stub: it records every URL, `afterEach` fails on any non-metadata route, and the probe's previously-unasserted shape is now pinned by three new tests. The default document declares no option-bearing fields, so `deriveDimensionLabelMaps` resolves nothing and the rows are returned by identity - byte-identical to what the failing request produced. All 42 pre-existing tests still pass unchanged. The double unmounts before restoring the real `fetch`: vitest runs `afterEach` in reverse registration order, so the setup file's RTL cleanup runs after this file's hook, and unstubbing first left the tree mounted with the real global back in place. Measured - one run in six leaked a single attempt that way. Product code is untouched. Same root cause as #4106 (whose fix landed only in `packages/plugin-charts`, before #4389 extracted the call site into the shared hook), not a fourth distinct one. `packages/plugin-dashboard`'s `DatasetWidget.test.tsx` still escapes through the same hook - filed as #5280, out of this card's scope. Fixes#5225 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RV6yuVCxymHYE16PL9vQkE
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
os-support-ai
marked this pull request as ready for review
August 19, 2026 00:51
Uh oh!
There was an error while loading. Please reload this page.
os-support-ai
deleted the
claude/issue-5225-plugin-report-network-escape
branch
August 19, 2026 00:52
os-support-ai pushed a commit
that referenced
this pull request
Aug 19, 2026
…ork escape (#5280) DatasetWidget.test.tsx's dimension-metadata probe (useDatasetDimensionMeta's apiFetch ?? fetch fallback) escaped to the real network — 5 live connection attempts per run, best-effort-swallowed so all 52 tests stayed green. Same root cause as #5225 (packages/plugin-report); ports the same fix shape from its reference PR #5283: a recording double answers the metadata route with an option-free payload, afterEach fails on any non-metadata escape, and three new tests pin the probe's own request shape. cleanup() runs before vi.unstubAllGlobals() in the same afterEach to avoid the reverse-registration flake #5225 measured (1/6 leaked attempts). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RV6yuVCxymHYE16PL9vQkE
os-support-ai pushed a commit
that referenced
this pull request
Aug 19, 2026
…data network escape (#5299) DashboardGridLayout.datasetPath.test.tsx's 'options.data provider widget' negative-control test made a real network call — ECONNREFUSED on port 3000, 1 attempt per isolated run, best-effort-swallowed so all 13 tests stayed green. `-t` bisected across all 13 cases in isolation: only this one test escapes (3/3); the two #4613 legacy-retired-widget candidates the card named do NOT escape alone (0/1 each), so useDatasetDimensionMeta's `!object` guard was never the site, as the card's own re-derived reading suspected. Root cause: the options.data.provider:'object' widget maps to component type `object-chart`, whose ObjectChart.tsx still carries its own inline `apiFetch ?? fetch` category-color probe (the ORIGINAL #4106 defect site, never migrated to the useDatasetDimensionMeta hook #4389 extracted for the dataset-bound path). With no SchemaRendererProvider in this file's render tree, that probe degrades to the real global `fetch`. Ports the same fix shape #5225's reference PR #5283 established and #5280 (PR #5300) ported: a recording double answers the metadata route, afterEach fails on any non-metadata escape, and cleanup() runs before vi.unstubAllGlobals() in the same afterEach to avoid the reverse-registration flake #5280 measured. One new pin test asserts the probe's own request shape — exactly one call to /api/v1/meta/object/invoices. Claude-Session: https://claude.ai/code/session_01RV6yuVCxymHYE16PL9vQkE
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#5225
Test-only. Product code is untouched.
Premise re-checked on current
mainfirstThe card measured on a feature branch that has since merged (PR #5224), so the recheck was re-run on
origin/main@41f498bcbbefore anything else:Still there. The per-file bisect was re-run too — over all 13 files in the package, not the 5 the card listed (the package has grown since): the escape is still confined to
DatasetReportRenderer.test.tsx, every other file is 0.16 is the grep line count; the real figure is 8 connection attempts — each failure prints two matching lines (
Error: connect ECONNREFUSED …andcode: 'ECONNREFUSED',).Which module opens the connection
The card's three negative claims were re-verified here, each with a counter-probe so the zero is a reading rather than a typo:
fetch(inpackages/plugin-report/srcDatasetReportRenderer→ 1113000inpackages/plugin-report/srcdataset→ 13 filesimport→ 4 / 7 / 10The caller was then located with a
net.Socket.prototype.connectpreload probe, which prints the JS stack under the TCP connect:So the path is:
useDatasetDimensionMetareads the host's authenticatedapiFetchoffSchemaRendererContextand, with noSchemaRendererProviderin the tree, degrades to the globalfetchon purpose — objectui#4121 property 1, so a standalone embed keeps rendering instead of crashing. Under happy-dom that globalfetchis a real HTTP client, and vitest's happy-dom environment defaults the document URL tohttp://localhost:3000(vitest/dist/chunks/index.DC7d2Pf8.js:url: happyDOM.url || "http://localhost:3000"), so the relative/api/v1/...resolves to a live request. That is where the3000comes from — no repo file names it.Because the aliases in
vitest.config.mtsresolve@object-ui/reactand@object-ui/coreto theirsrc, the stack above is source, notdist— nothing here can be a stale-build artefact.Why the test's mock did not intercept it
The metadata read is a second data channel.
dataSource.queryDataset— the prop double this file passes — serves the report ROWS; the dimension-label metadata never goes throughdataSourceat all. Only the cases whose mocked result carriesobjectreach it, since that field is what the hook keys on: 8 of the file's renders (taskx4,dealx2,opportunityx2), which is exactly the 8 attempts.The read is best-effort — a failure leaves the rows exactly as the server sent them — which is why 42 tests were green while the request never once succeeded.
The fix
A recording double, the shape objectui#3339 and #4106 settled on and the one this package's own
DatasetReportRenderer.localSelectI18n.test.tsxalready uses. Explicitly not a blanket network stub, not a global error sink:/api/v1/meta/object/:name;afterEachasserts the recorded set contains no non-metadata URL, so an escape to some other endpoint becomes a red test in this file instead of vanishing into the hook'scatch {};deriveDimensionLabelMapsresolves nothing andrelabelDimensionsreturns the rows by identity — byte-identical to what the failing request produced. No pre-existing assertion changes meaning;accept: application/json), that a result declaring noobjectprobes nothing at all, and that a resolved document actually reaches the rendered cells — the success path that had never executed in this suite.One ordering detail worth keeping: the hook unmounts (
cleanup()) beforevi.unstubAllGlobals(). Vitest runsafterEachin reverse registration order, so the setup file's RTL cleanup runs after this file's hook; unstubbing first left the tree mounted with the real global back in place and a settling metadata effect escaped again — measured, one run in six leaked a single attempt.Post-fix status of all 42 tests
All 42 pre-existing tests still pass, unchanged — none of them depended on the swallowed failure. Plus the 3 new pins:
Tests 45 passed (45),ECONNREFUSED 0, stable across 8 consecutive runs. Package-wide:Test Files 13 passed (13) · Tests 152 passed (152) · ECONNREFUSED 0.Reverse verification
Predicted before running, with the pre-fix file restored (
git checkout origin/main -- …): 16 ECONNREFUSED lines, 42 passed, 0 failed — the direction here is not red/green, because the defect is a swallowed side effect rather than a wrong assertion; the observable is the connection count and the missing pins.Observed:
16/Tests 42 passed (42)/0occurrences of the new describe block. Predicted == observed on all three. The fix was then restored from the branch and proved byte-identical (git hash-object==git rev-parse HEAD:…==4e522c49e).Family: same root cause as #4106, not a fourth one
127.0.0.1:3000发起真实网络请求(ECONNREFUSED 噪音,未走 MSW) #3339 (plugin-detail) — different call site:useRecordEditable'sPOST /api/v1/security/explain./api/v1/meta/object/:objectoff the globalfetch. Its fix (ce4b9e8af) landed in fivepackages/plugin-charts/**files only; Analytics label net: the fetch-and-memo glue is written twice (DatasetWidget + plugin-report's useDatasetDimensionLabels) — its home is @object-ui/core #4389 later extracted that exact call site into the shareduseDatasetDimensionMetahook, which is whatplugin-reportreaches today. So this is plugin-charts / plugin-dashboard 测试套件仍有真实网络逃逸(ECONNREFUSED) — 根因与 #3339 不同,需独立定位 #4106's root cause at a consumer the sweep did not cover, not a new one.The shared mechanism across all three is the same and worth stating once: a hook that degrades to the global
fetchoutside a host provider, plus happy-dom's real fetch on alocalhost:3000default origin, plus a best-effortcatch.Why it is not merely noise
In this repo's shared containers a dev server on port 3000 is routine. If one is up, those 8 attempts connect — to whatever another agent happens to be running — and the tests' rendered labels change without a line of them changing (the double's third pin shows exactly how: a resolved document rewrites dimension cells). That is the argument for locating and answering the request rather than muting it.
Out of scope, filed
packages/plugin-dashboard/src/__tests__/DatasetWidget.test.tsxstill escapes through the same hook — 10 ECONNREFUSED lines (5 attempts) forshowcase_taskx3,showcase_dealx2,invoicesx1, with 52 tests green. Different package, outside this card's write surface: filed as #5280, unassigned, not touched here.Verification
At
be8cbe607:pnpm exec vitest run packages/plugin-report— 13 files / 152 tests passed, 0 ECONNREFUSEDpnpm --filter '@object-ui/plugin-report^...' build— dependency closure, exit 0pnpm --filter @object-ui/plugin-report type-check—tsc --noEmit && tsc -p tsconfig.test.json, exit 0pnpm --filter @object-ui/plugin-report lint— 0 errors (85 pre-existing warnings; the two in the edited file are the pre-existingmakeSourceanys at lines 37/41)node scripts/check-control-bytes.mjs— OK (4664 files), plus a direct control-byte grep over the two changed filesnode scripts/check-changeset-presence.mjs— satisfied by an empty-frontmatter changeset (the change is under a guardedsrc/, and the gate's own documented answer for asrc/__tests__/**-only change)node scripts/check-changeset-no-major.mjs— nomajorDocs untouched, so #5136's
content/docs/core/report-schema.mdxis unaffected: this PR changes nothing about what the renderer reads.Generated by Claude Code