test(integration): add scale correctness suite (#1192) - #1229
Conversation
Five tests verify record-level correctness at scale (up to 100K records) against a fixture-deployed Product/Attribute schema: 100K paginated read + indexed search, 10K count + pagination non-overlap, multi-condition indexed search, bulk insert→delete correctness, and 1K EAV entity→attribute join via indexed productId. Uses Harper's `limit(N)` query syntax for REST pagination; full 1M / YCSB coverage remains in nightly CI. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request introduces scale correctness integration tests, including a configuration file, a GraphQL schema, and a test suite (scale.test.ts) to verify record-level correctness at scale (up to 100K records). The review feedback correctly identifies several instances where JSON responses from external API calls are parsed and cast directly without verifying if they are arrays. It is recommended to validate these responses using Array.isArray before iterating or mapping over them to prevent potential runtime errors.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| headers: { Authorization: auth }, | ||
| }); | ||
| strictEqual(pageResp.status, 200, `REST page GET failed: ${pageResp.status}`); | ||
| const page = (await pageResp.json()) as unknown[]; |
There was a problem hiding this comment.
The response from the external API is parsed and mapped directly. Under the repository guidelines, responses from external APIs must be validated with Array.isArray before iterating or mapping to prevent runtime errors if the API returns an unexpected error object.
| const page = (await pageResp.json()) as unknown[]; | |
| const page = await pageResp.json(); | |
| ok(Array.isArray(page), 'Expected page to be an array'); |
References
- When handling responses from peer nodes or external APIs, explicitly verify that expected array fields are actually arrays (e.g., using Array.isArray) before iterating over them, to prevent malformed responses from causing runtime errors.
| headers: { Authorization: auth }, | ||
| }); | ||
| strictEqual(page2Resp.status, 200, `REST page2 GET failed: ${page2Resp.status}`); | ||
| const page2 = (await page2Resp.json()) as Array<{ id: string }>; |
There was a problem hiding this comment.
The response from the external API is parsed and iterated directly. Under the repository guidelines, responses from external APIs must be validated with Array.isArray before iterating or mapping to prevent runtime errors if the API returns an unexpected error object.
| const page2 = (await page2Resp.json()) as Array<{ id: string }>; | |
| const page2 = await page2Resp.json(); | |
| ok(Array.isArray(page2), 'Expected page2 to be an array'); |
References
- When handling responses from peer nodes or external APIs, explicitly verify that expected array fields are actually arrays (e.g., using Array.isArray) before iterating over them, to prevent malformed responses from causing runtime errors.
| const ids1 = new Set(((await p1Resp.json()) as Array<{ id: string }>).map((r) => r.id)); | ||
| const ids2 = ((await p2Resp.json()) as Array<{ id: string }>).map((r) => r.id); |
There was a problem hiding this comment.
The responses from p1Resp and p2Resp are parsed and mapped directly. Under the repository guidelines, responses from external APIs must be validated with Array.isArray before iterating or mapping to prevent runtime errors if the API returns an unexpected error object.
| const ids1 = new Set(((await p1Resp.json()) as Array<{ id: string }>).map((r) => r.id)); | |
| const ids2 = ((await p2Resp.json()) as Array<{ id: string }>).map((r) => r.id); | |
| const p1Data = await p1Resp.json(); | |
| ok(Array.isArray(p1Data), 'Expected page 1 response to be an array'); | |
| const ids1 = new Set((p1Data as Array<{ id: string }>).map((r) => r.id)); | |
| const p2Data = await p2Resp.json(); | |
| ok(Array.isArray(p2Data), 'Expected page 2 response to be an array'); | |
| const ids2 = (p2Data as Array<{ id: string }>).map((r) => r.id); |
References
- When handling responses from peer nodes or external APIs, explicitly verify that expected array fields are actually arrays (e.g., using Array.isArray) before iterating over them, to prevent malformed responses from causing runtime errors.
| // Suite | ||
| // ────────────────────────────────────────────────────────────────────────────── | ||
|
|
||
| suite('Scale correctness', (ctx: ContextWithHarper) => { |
There was a problem hiding this comment.
Blocker: suite callback argument is node:test's TestContext, not ContextWithHarper.
In node:test, the function passed to suite(name, fn) receives a node:test TestContext as its sole argument — not the Harper context. Every other integration test in this repo declares ctx as an outer let/const binding and relies on before() to populate it via setupHarperWithFixture(ctx, …). By naming the suite callback parameter ctx: ContextWithHarper, this code shadows that outer binding, so ctx.harper inside every test() callback resolves to the node:test TestContext object — not the running Harper instance. All five tests will fail at runtime with property-access errors.
The fix is to match the repo's established pattern:
| suite('Scale correctness', (ctx: ContextWithHarper) => { | |
| suite('Scale correctness', () => { | |
| const ctx = {} as ContextWithHarper; |
Then before/after/test bodies close over the outer ctx, exactly as in every other test file in this repo.
| // Verify via total growth instead. | ||
| const totalAfter = await countRows(ctx, 'Product'); | ||
| // Total should have grown by exactly TOTAL (IDs are unique, no overlap with Test 1). | ||
| const totalTest1 = 100_000; |
There was a problem hiding this comment.
Blocker: Test 2 hardcodes Test 1's exact record count.
const totalTest1 = 100_000; makes Test 2 structurally dependent on Test 1 running first and inserting exactly 100 K records in the same table. If Test 1 is skipped, re-ordered, or its TOTAL changes, this assertion silently produces a wrong expected value — or fails with a misleading message that hides the real issue.
Use the actual pre-insert count to compute the delta instead:
| const totalTest1 = 100_000; | |
| // Record the count before this test's inserts so the delta is self-contained. | |
| const countBefore = await countRows(ctx, 'Product'); | |
| for (let start = 0; start < TOTAL; start += BATCH) { |
Then assert strictEqual(totalAfter, countBefore + TOTAL, …) using the locally-measured baseline. This removes the hard coupling to Test 1's implementation.
| headers: { Authorization: auth }, | ||
| }); | ||
| strictEqual(pageResp.status, 200, `REST page GET failed: ${pageResp.status}`); | ||
| const page = (await pageResp.json()) as unknown[]; |
There was a problem hiding this comment.
Unguarded array assumption on REST response.
pageResp.json() is cast directly to unknown[] and then .length is accessed without verifying the response is actually an array. If the server returns an error envelope { error: "..." } the cast silently succeeds and page.length will be undefined, turning the subsequent strictEqual into a confusing "Expected 100, got undefined" rather than a clear failure.
Add a guard before using the value:
| const page = (await pageResp.json()) as unknown[]; | |
| const page = (await pageResp.json()) as unknown[]; | |
| ok(Array.isArray(page), `Expected array response from /Product, got: ${JSON.stringify(page)}`); |
Same pattern applies to page2 (line 113), p1Resp/p2Resp (lines 181–182).
| `category='A' AND inStock=true AND id >= 'prod-${ID_OFFSET}' AND id < 'prod-${ID_OFFSET + TOTAL}'` | ||
| ); | ||
| // Allow a small tolerance for lexicographic edge effects on string-range predicates. | ||
| // The exact count via search_by_conditions is the gold standard; SQL gives us a lower bound. |
There was a problem hiding this comment.
Near-no-op assertion undermines Test 3's value.
The comment on line 212 concedes that the SQL range predicate has "lexicographic edge effects" and then falls back to ok(sqlCount > 0, …). This is effectively a liveness check, not a correctness check — sqlCount = 1 would pass. The main purpose of Test 3 is to verify multi-condition indexed-search correctness, but the SQL side of the verification is so weakened that it adds almost no signal.
Options:
- Drop the SQL COUNT entirely and rely solely on the
search_by_conditionspurity loop below (which is the real assertion). - Use a numeric ID scheme for Test 3's ID_OFFSET so lexicographic order matches numeric order, enabling a precise SQL count assertion.
- Snapshot Product count before Test 2's inserts instead of hardcoding Test 1's 100K, removing the hidden inter-test dependency. - Guard all REST pagination responses with Array.isArray before reading .length / mapping, per repo external-response handling guidelines. - Drop Test 3's weak SQL string-range count (lexicographic ordering made it a near-no-op liveness check); rely on the search_by_conditions purity loop as the real multi-condition correctness assertion. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Ethan-Arrowood
left a comment
There was a problem hiding this comment.
LGTM — solid correctness coverage for the scale gap from #1192. The one red check is just a stale build-artifact/infra failure (not the tests), so a re-run should green it up. Good to go.
sent with Claude Opus 4.8
Summary
integrationTests/server/scale.test.tswith five correctness tests at scale (up to 100 K records), exercising large indexed tables on every PR run — the gap identified in Promote scale tests to PR CI: 100K–1M record subset + 1.44M multi-index table correctness #1192.integrationTests/fixtures/scale-test/fixture withProduct(category indexed) andAttribute(productId + key indexed) tables.setupHarperWithFixture+sendOperation/ SQL COUNT patterns consistent with existing server tests; REST pagination uses Harper'slimit(N)query syntax.Tests
search_by_conditionsresult purity.search_by_conditionsverify category='A' filter purity.search_by_conditionson indexedproductIdreturns exactly 5 per entity.Notes
valueis a reserved field name in Harper's condition schema; theAttribute.attrValuerename avoids the clash.?limit=N&offset=Mis treated as search conditions; correct syntax is?limit(N,end).Test plan
integrationTests/server/scale.test.tsand passes (5/5).Closes #1192
Generated with Claude Code