Skip to content

Commit 2bc4872

Browse files
committed
test(stack): couple negative live assertions to internal controls
An assertion of absence proves nothing unless something in the same test shows the row was reachable to begin with. A sibling `it` does not couple them: vitest runs on past a failure, so the control can go red while the negative test stays green in the same run. beforeAll already throws on a failed seed, so an unseeded fixture is not the live risk. The risk it cannot catch is an operator that silently matches nothing — a constant-false predicate, or encryption no-op'ing — which produces exactly the empty result each negative test asserts. - operators-live-pg: fold the intersecting `and` pair into the disjoint block, so [] cannot pass via a constant-false `and`. Add a present-needle control to the `contains` no-match block, whose only proof that contains can match lived in a sibling test. - operators-lock-context-live-pg: assert the same eq WITH the lock context finds ROW_A before asserting it is unreachable without one. - matrix-live-pg: two-sample domains (date, timestamp) have an empty strict interior, so `gt`/`lt` were unproven there. Add one-sided bounds, which are non-empty for every domain. - operators-null-live-pg: guard the [row] destructure so a missing fixture reads as a missing fixture, not a null-handling bug. Found by audit while triaging CodeRabbit's decrypt-denial comment on #607; same defect class, sites it did not reach.
1 parent 14d0e78 commit 2bc4872

4 files changed

Lines changed: 69 additions & 14 deletions

File tree

packages/stack/__tests__/drizzle-v3/operators-live-pg.test.ts

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,15 @@ describeLivePg('v3 drizzle operators (live pg matrix)', () => {
601601
await ops.contains(matrixColumn(eqlType), needle),
602602
)
603603
expect(rows).toEqual([])
604+
605+
// The control. An over-rejecting guard would have thrown above, and a
606+
// fail-open `contains` would have returned every row — but a
607+
// constant-false `contains` also returns [], and nothing above catches
608+
// it. Prove the same column still answers a needle that IS present.
609+
const present = await selectRowKeys(
610+
await ops.contains(matrixColumn(eqlType), 'ada'),
611+
)
612+
expect(present.length).toBeGreaterThan(0)
604613
},
605614
30000,
606615
)
@@ -661,25 +670,29 @@ describeLivePg('v3 drizzle operators (live pg matrix)', () => {
661670
ops.gte(matrixColumn('public.integer_ord'), 0),
662671
] as const
663672

664-
it('and requires both encrypted predicates, unlike or', async () => {
665-
const rows = await selectRowKeys(await ops.and(...disjointPredicates()))
666-
expect(rows).toEqual([])
667-
}, 30000)
668-
669-
// The disjoint pair above proves `and` is not `or`, but every `and` assertion
670-
// over it expects []. A constant-false `and` would satisfy that too, and the
671-
// `or` tests exercise a different operator. This pins the positive path:
672-
// an intersecting pair that must actually return its row.
673+
// Two assertions, one block, deliberately. The disjoint pair proves `and` is
674+
// not `or`: swapping the operator turns [] into [A,B,C]. But [] is also what
675+
// a constant-false `and` returns, and `beforeAll` cannot catch that — it only
676+
// catches a failed seed. The intersecting pair is the control that can: it
677+
// must return its row, so it dies on a constant-false `and` and on an eq/lt
678+
// term that silently matches nothing. Keeping it in a sibling `it` would not
679+
// couple them, since vitest runs on past a failure and the sibling could go
680+
// red while this stayed green.
673681
// text_eq = 'ada@example.com' -> ROW_B only
682+
// integer_ord >= 0 -> ROW_A (0), ROW_C (2147483647), not ROW_B (-42)
674683
// integer_ord < 0 -> ROW_B (-42) only
675-
it('and returns the rows satisfying both encrypted predicates', async () => {
676-
const rows = await selectRowKeys(
684+
it('and requires both encrypted predicates, unlike or', async () => {
685+
expect(await selectRowKeys(await ops.and(...disjointPredicates()))).toEqual(
686+
[],
687+
)
688+
689+
const intersecting = await selectRowKeys(
677690
await ops.and(
678691
ops.eq(matrixColumn('public.text_eq'), 'ada@example.com'),
679692
ops.lt(matrixColumn('public.integer_ord'), 0),
680693
),
681694
)
682-
expect(rows).toEqual([ROW_B])
695+
expect(intersecting).toEqual([ROW_B])
683696
}, 30000)
684697

685698
it('or requires either encrypted predicate, unlike and', async () => {

packages/stack/__tests__/drizzle-v3/operators-lock-context-live-pg.test.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -256,8 +256,18 @@ describeLivePg('v3 drizzle operators with lock context (live pg)', () => {
256256
// No `USER_JWT_B` exists, so that remains a follow-up.
257257
it('an identity-bound row does not match an eq issued with no lock context', async () => {
258258
if (skipUnlessJwt()) return
259-
const condition = await ops.eq(secretTable.secret, SECRET_A)
260-
expect(await selectRowKeys(condition)).toEqual([])
259+
260+
// The control, in this test rather than a sibling one. `[]` is what a held
261+
// identity boundary and an unseeded fixture both look like, so the empty
262+
// assertion below proves nothing on its own — it only means something
263+
// against a demonstration that the row IS reachable with the context.
264+
const bound = await ops.eq(secretTable.secret, SECRET_A, {
265+
lockContext: IDENTITY_CLAIM as never,
266+
})
267+
expect(await selectRowKeys(bound)).toEqual([ROW_A])
268+
269+
const unbound = await ops.eq(secretTable.secret, SECRET_A)
270+
expect(await selectRowKeys(unbound)).toEqual([])
261271
}, 30000)
262272

263273
it('an identity-bound row does not decrypt without its lock context', async () => {

packages/stack/__tests__/drizzle-v3/operators-null-live-pg.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,9 @@ describeLivePg('v3 drizzle NULL persistence across tiers (live pg)', () => {
159159
WHERE test_run_id = $1 AND row_key = $2`,
160160
[RUN, ROW_A],
161161
)
162+
// Without this, a missing fixture makes `row.value` raise a TypeError and
163+
// the failure reads as a null-handling bug rather than an absent row.
164+
expect(row).toBeDefined()
162165
expect(row.value).toBeNull()
163166
}, 30000)
164167

packages/stack/__tests__/v3-matrix/matrix-live-pg.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,35 @@ describeLivePg('v3 matrix live Postgres coverage (all 35 domains)', () => {
550550
[ORDER_RUN_ID, ids, lo, hi],
551551
)
552552
expect(new Set(exclusive.map((r) => r.id))).toEqual(new Set(interiorIds))
553+
554+
// The controls. Domains with only two distinct samples (date, timestamp)
555+
// have an empty interior, so the assertion above is satisfied by a
556+
// constant-false `gt`/`lt` as well as by a correct one. One-sided strict
557+
// bounds are non-empty for every domain: `gt(lo)` must return everything
558+
// above the minimum, `lt(hi)` everything below the maximum. Both die on a
559+
// constant-false operator, and on a constant-true one (which would drag in
560+
// the excluded endpoint).
561+
const aboveLo = await sql.unsafe<Row[]>(
562+
`SELECT id FROM ${TABLE_NAME}
563+
WHERE test_run_id = $1
564+
AND id = ANY($2)
565+
AND eql_v3.gt("${col}", $3::jsonb)`,
566+
[ORDER_RUN_ID, ids, lo],
567+
)
568+
expect(new Set(aboveLo.map((r) => r.id))).toEqual(
569+
new Set(sortedIdx.slice(1).map((i) => ids[i])),
570+
)
571+
572+
const belowHi = await sql.unsafe<Row[]>(
573+
`SELECT id FROM ${TABLE_NAME}
574+
WHERE test_run_id = $1
575+
AND id = ANY($2)
576+
AND eql_v3.lt("${col}", $3::jsonb)`,
577+
[ORDER_RUN_ID, ids, hi],
578+
)
579+
expect(new Set(belowHi.map((r) => r.id))).toEqual(
580+
new Set(sortedIdx.slice(0, L - 1).map((i) => ids[i])),
581+
)
553582
})
554583

555584
it.each(

0 commit comments

Comments
 (0)