Skip to content

test: port missing searchable JSON tests to stack package#328

Merged
coderdan merged 7 commits into
mainfrom
feat/port-searchable-json-tests
May 26, 2026
Merged

test: port missing searchable JSON tests to stack package#328
coderdan merged 7 commits into
mainfrom
feat/port-searchable-json-tests

Conversation

@coderdan
Copy link
Copy Markdown
Contributor

@coderdan coderdan commented Mar 24, 2026

This PR is part of a stack:

  1. feat: delegate credential loading to protect-ffi #326
  2. This PR

Port missing searchable JSON tests

Port 48 integration tests from @cipherstash/protect to @cipherstash/stack covering searchable JSON operations that were previously missing. These tests would have caught the array_index_mode regression where the stack schema diverged from the protect schema.

Test groups added

  • contained-by: <@ term queries (6 tests)
  • jsonb_path_query_first: scalar path queries (6 tests)
  • jsonb_path_exists: boolean path queries (6 tests)
  • jsonb_array_elements + jsonb_array_length: array queries (7 tests)
  • containment: @> with array values (5 tests)
  • contained-by: <@ with array values (4 tests)
  • storage: array round-trips (2 tests)
  • containment: operand and protocol matrix (5 tests)
  • field access: -> operator (4 tests)
  • WHERE comparison: = equality (4 tests)

CI improvements

  • Update @cipherstash/stack engine requirement from >=18 to >=22 to match root
  • Add Node version build matrix (22, 24) so tests run against LTS and latest
  • Add experimental Bun test job (continue-on-error) to assess Bun runtime compatibility
  • Fix .env file paths in protect-dynamodb and drizzle CI steps

Summary by CodeRabbit

Release Notes

  • Tests

    • Expanded PostgreSQL integration test coverage for JSONB query operations, including containment checks, path queries, array operations, and equality comparisons.
  • Chores

    • Updated minimum Node.js version requirement to 22; enhanced CI/CD pipeline to support Node 22 and 24, and added Bun test runner support.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 24, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2d48adf3-818e-4b6f-92d0-0669c58c5579

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds ~1,100 lines of PostgreSQL JSONB integration tests, updates CI to run Node matrix and a new Bun-based test job, and raises the packages/stack Node engine requirement from >=18 to >=22.

Changes

PostgreSQL JSONB Integration Tests

Layer / File(s) Summary
New JSONB/EQL test suites
packages/stack/__tests__/searchable-json-pg.test.ts
Adds nine new test suites covering <@/@> containment, jsonb_path_query_first, jsonb_path_exists, array operations (jsonb_array_elements, jsonb_array_length), array round-trip storage, operand/protocol matrix checks, -> field access, and equality comparisons. Tests run both Extended (tagged sql\``) and Simple (sql.unsafe`) variants.

CI workflow

Layer / File(s) Summary
Run-tests Node matrix & setup change
.github/workflows/tests.yml
run-tests job now uses a Node version matrix (22, 24), updates the job name to include the selected Node version, and uses matrix.node-version in actions/setup-node.
Run-tests Bun job
.github/workflows/tests.yml
Adds run-tests-bun job that installs Bun and pnpm, writes package .env files from secrets, builds packages with turbo, and runs vitest via bunx across selected packages; per-directory failures are suppressed and the job uses continue-on-error: true.

Supply-chain e2e

Layer / File(s) Summary
Workflow type and description updates
e2e/tests/supply-chain.e2e.test.ts
Expand workflow parsing types to optionally include strategy.matrix and rename the test to cover literal or matrix node-version references.
Matrix-aware node-version assertion
e2e/tests/supply-chain.e2e.test.ts
Replace strict node-version === '22' check with logic that accepts direct '22' or resolves matrix expressions to ensure the referenced matrix array includes '22'.

Package engine requirement

Layer / File(s) Summary
Engine bump
packages/stack/package.json
Bumps engines.node from >=18 to >=22.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant GH as GitHub Actions
    participant Runner as Runner
    participant Node as Node Setup (matrix)
    participant Bun as Bun Setup
    participant PNPM as pnpm
    participant Build as Turbo Build
    participant Vitest as vitest/bunx

    GH->>Runner: push / PR triggers workflow
    alt Node matrix job (22,24)
        Runner->>Node: setup node (matrix.node-version)
        Runner->>PNPM: install deps (pnpm)
        Runner->>Build: pnpm turbo build --filter './packages/*'
        Runner->>Vitest: run vitest (node)
    end
    alt Bun job
        Runner->>Bun: install/setup Bun
        Runner->>PNPM: install deps (pnpm)
        Runner->>Build: pnpm turbo build --filter './packages/*'
        Runner->>Vitest: bunx --bun vitest run (selected packages) || true
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • cipherstash/stack#488: Modifies the same Postgres JSONB/EQL test file and may overlap with the new query-mode test cases.

Suggested reviewers

  • freshtonic
  • yujiyokoo
  • tobyhede

Poem

🐰 I hopped through tests both deep and wide,
JSONB tunnels where secrets hide,
Node and Bun both join the race,
Engines raised to a newer place.
Round-trips checked — now on we glide! 🥕

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely summarizes the main objective: porting missing searchable JSON tests to the stack package. It matches the primary focus of the changeset and is clear and specific.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/port-searchable-json-tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Mar 24, 2026

⚠️ No Changeset found

Latest commit: db3563b

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Base automatically changed from feat/update-ffi to main March 24, 2026 10:25
@coderdan coderdan force-pushed the feat/port-searchable-json-tests branch from a0849a4 to aa5a2d0 Compare March 24, 2026 10:28
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
packages/stack/__tests__/searchable-json-pg.test.ts (2)

1356-1417: Inconsistent SQL query pattern in Simple variants.

These Simple tests use string interpolation ('${selectorTerm}', '${TEST_RUN_ID}') while other Simple tests in this PR (e.g., contained-by at lines 1229-1284) use parameterized queries ($1, $2). Consider aligning to parameterized queries for consistency within the new tests.

Example refactor for line 1362-1367
-      const rows = await sql.unsafe(
-        `SELECT id, (metadata).data as metadata
-         FROM "protect-ci-jsonb" t
-         WHERE eql_v2.jsonb_path_query_first(t.metadata, '${selectorTerm}'::eql_v2_encrypted) IS NOT NULL
-         AND t.test_run_id = '${TEST_RUN_ID}'`,
-      )
+      const rows = await sql.unsafe(
+        `SELECT id, (metadata).data as metadata
+         FROM "protect-ci-jsonb" t
+         WHERE eql_v2.jsonb_path_query_first(t.metadata, $1::eql_v2_encrypted) IS NOT NULL
+         AND t.test_run_id = $2`,
+        [selectorTerm, TEST_RUN_ID],
+      )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/stack/__tests__/searchable-json-pg.test.ts` around lines 1356 -
1417, The new "Simple" tests (e.g., the it blocks titled 'finds row by string
field (Simple)', 'finds row by nested path (Simple)', and 'returns no rows for
unknown path (Simple)') interpolate selectorTerm and TEST_RUN_ID directly into
sql.unsafe instead of using parameterized placeholders like other tests; update
those sql.unsafe calls to use parameterized queries ($1, $2) and pass
selectorTerm and TEST_RUN_ID as parameters to avoid SQL injection and match the
pattern used in the contained-by tests, keeping encryptQueryTerm, selectorTerm,
sql.unsafe, and TEST_RUN_ID as the referenced symbols to change.

1580-1601: Testing implementation internals.

This test reaches into internal STE vec structure (eql_v2.selector(), eql_v2.is_ste_vec_array(), eql_v2.ste_vec()) to verify [@] selector behavior. While this may be necessary to detect the array_index_mode divergence mentioned in the PR objectives, consider whether this internal structure is stable or if there's a public API approach.

As per coding guidelines: "Prefer testing via the public API and avoid reaching into private internals".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/stack/__tests__/searchable-json-pg.test.ts` around lines 1580 -
1601, The test is inspecting private STE internals (eql_v2.selector,
eql_v2.is_ste_vec_array, eql_v2.ste_vec) to assert the `[@]` selector; instead,
rewrite the test to exercise the public API that consumes encrypted selectors
(use the existing helper encryptQueryTerm and the public search/query endpoint
or SQL function that takes the encrypted selector) so you assert behavior via
the public search result rather than by unnesting STE internals. Concretely:
remove the LATERAL unnest of eql_v2.ste_vec and the calls to
eql_v2.selector/is_ste_vec_array, call encryptQueryTerm('$.colors[@]',
'steVecSelector') (as done with selectorAt), then use the public query function
or endpoint that accepts that encrypted selector (instead of selecting
eql_v2.selector(selectorAt)) and assert the returned row(s) include the inserted
id/marker; keep helper names (encryptQueryTerm, steVecSelector, selectorAt,
hashAt) but route assertions through the public search API.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/stack/__tests__/searchable-json-pg.test.ts`:
- Around line 1356-1417: The new "Simple" tests (e.g., the it blocks titled
'finds row by string field (Simple)', 'finds row by nested path (Simple)', and
'returns no rows for unknown path (Simple)') interpolate selectorTerm and
TEST_RUN_ID directly into sql.unsafe instead of using parameterized placeholders
like other tests; update those sql.unsafe calls to use parameterized queries
($1, $2) and pass selectorTerm and TEST_RUN_ID as parameters to avoid SQL
injection and match the pattern used in the contained-by tests, keeping
encryptQueryTerm, selectorTerm, sql.unsafe, and TEST_RUN_ID as the referenced
symbols to change.
- Around line 1580-1601: The test is inspecting private STE internals
(eql_v2.selector, eql_v2.is_ste_vec_array, eql_v2.ste_vec) to assert the `[@]`
selector; instead, rewrite the test to exercise the public API that consumes
encrypted selectors (use the existing helper encryptQueryTerm and the public
search/query endpoint or SQL function that takes the encrypted selector) so you
assert behavior via the public search result rather than by unnesting STE
internals. Concretely: remove the LATERAL unnest of eql_v2.ste_vec and the calls
to eql_v2.selector/is_ste_vec_array, call encryptQueryTerm('$.colors[@]',
'steVecSelector') (as done with selectorAt), then use the public query function
or endpoint that accepts that encrypted selector (instead of selecting
eql_v2.selector(selectorAt)) and assert the returned row(s) include the inserted
id/marker; keep helper names (encryptQueryTerm, steVecSelector, selectorAt,
hashAt) but route assertions through the public search API.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a6f6143c-8af9-4231-a6bb-f3e7a8de4bae

📥 Commits

Reviewing files that changed from the base of the PR and between 913dbbb and aa5a2d0.

📒 Files selected for processing (1)
  • packages/stack/__tests__/searchable-json-pg.test.ts

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
.github/workflows/tests.yml (3)

15-17: Consider setting fail-fast: false for the matrix.

By default, GitHub Actions cancels remaining matrix jobs when one fails. If you want to see test results for all Node versions regardless of individual failures, add fail-fast: false.

♻️ Suggested change
     strategy:
+      fail-fast: false
       matrix:
         node-version: [22, 24]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/tests.yml around lines 15 - 17, Add fail-fast: false to
the workflow strategy so matrix jobs for node-version (the
strategy.matrix.node-version matrix) won't cancel remaining runs when one job
fails; update the strategy block to include fail-fast: false under the existing
strategy configuration.

164-171: Improve failure visibility in the test loop.

Using || true silently swallows all failures, making it hard to distinguish passed tests from failed ones or missing vitest configs. Consider capturing exit codes and reporting a summary, or letting individual test runs fail while relying on continue-on-error: true at the job level.

♻️ Suggested improvement
       - name: Run tests with Bun
         run: |
+          failed=""
           for dir in packages/schema packages/protect packages/stack packages/protect-dynamodb packages/drizzle packages/stack-forge; do
-            if [ -f "$dir/vitest.config.ts" ] || [ -f "$dir/package.json" ]; then
+            if [ -f "$dir/vitest.config.ts" ]; then
               echo "--- Testing $dir ---"
-              (cd "$dir" && bunx --bun vitest run) || true
+              (cd "$dir" && bunx --bun vitest run) || failed="$failed $dir"
             fi
           done
+          if [ -n "$failed" ]; then
+            echo "::warning::Tests failed in:$failed"
+          fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/tests.yml around lines 164 - 171, The test loop currently
silences all failures with "|| true" which hides failing tests; update the loop
around the bunx --bun vitest run invocation to capture each run's exit code
(e.g., check the exit status after the bunx --bun vitest run in the for-loop),
record failing directories (refer to the loop iterating over packages/schema
packages/protect ... and the bunx --bun vitest run command), print an explicit
per-directory pass/fail message, and at the end print a summary and exit
non-zero if any tests failed (or rely on the job-level continue-on-error but
still surface per-run failures). Ensure you remove "|| true" and add logic to
collect and report exit codes for visibility.

113-158: Consider extracting .env setup to a composite action.

The .env file creation is duplicated across both jobs. Extracting to a composite action would reduce duplication and maintenance overhead. This is optional and can be deferred.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/tests.yml around lines 113 - 158, Create a reusable
composite action to centralize the repeated .env creation logic (extract the
echo/touch sequence into a composite action, e.g., .github/actions/create-env
with inputs like package_path and optional env keys) and then replace each
duplicated step named "Create .env file in ./packages/protect/", "Create .env
file in ./packages/stack/", "Create .env file in ./packages/protect-dynamodb/",
and "Create .env file in ./packages/drizzle/" with a single uses: call to that
composite action passing package_path (packages/protect, packages/stack,
packages/protect-dynamodb, packages/drizzle) and any environment-specific
inputs; ensure the composite action writes the same variables (CS_WORKSPACE_CRN,
CS_CLIENT_ID, CS_CLIENT_KEY, CS_CLIENT_ACCESS_KEY, SUPABASE_URL,
SUPABASE_ANON_KEY, DATABASE_URL, CS_ZEROKMS_HOST, CS_CTS_HOST) conditionally
where applicable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/tests.yml:
- Around line 94-95: Update the GitHub Actions checkout step to use the latest
action version: replace the uses value "actions/checkout@v3" in the step named
"Checkout Repo" with "actions/checkout@v4" so the workflow uses the v4 checkout
action.

---

Nitpick comments:
In @.github/workflows/tests.yml:
- Around line 15-17: Add fail-fast: false to the workflow strategy so matrix
jobs for node-version (the strategy.matrix.node-version matrix) won't cancel
remaining runs when one job fails; update the strategy block to include
fail-fast: false under the existing strategy configuration.
- Around line 164-171: The test loop currently silences all failures with "||
true" which hides failing tests; update the loop around the bunx --bun vitest
run invocation to capture each run's exit code (e.g., check the exit status
after the bunx --bun vitest run in the for-loop), record failing directories
(refer to the loop iterating over packages/schema packages/protect ... and the
bunx --bun vitest run command), print an explicit per-directory pass/fail
message, and at the end print a summary and exit non-zero if any tests failed
(or rely on the job-level continue-on-error but still surface per-run failures).
Ensure you remove "|| true" and add logic to collect and report exit codes for
visibility.
- Around line 113-158: Create a reusable composite action to centralize the
repeated .env creation logic (extract the echo/touch sequence into a composite
action, e.g., .github/actions/create-env with inputs like package_path and
optional env keys) and then replace each duplicated step named "Create .env file
in ./packages/protect/", "Create .env file in ./packages/stack/", "Create .env
file in ./packages/protect-dynamodb/", and "Create .env file in
./packages/drizzle/" with a single uses: call to that composite action passing
package_path (packages/protect, packages/stack, packages/protect-dynamodb,
packages/drizzle) and any environment-specific inputs; ensure the composite
action writes the same variables (CS_WORKSPACE_CRN, CS_CLIENT_ID, CS_CLIENT_KEY,
CS_CLIENT_ACCESS_KEY, SUPABASE_URL, SUPABASE_ANON_KEY, DATABASE_URL,
CS_ZEROKMS_HOST, CS_CTS_HOST) conditionally where applicable.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5965125b-b17c-40eb-8994-f802569ddb4f

📥 Commits

Reviewing files that changed from the base of the PR and between aa5a2d0 and 3fc7654.

📒 Files selected for processing (2)
  • .github/workflows/tests.yml
  • packages/stack/package.json
✅ Files skipped from review due to trivial changes (1)
  • packages/stack/package.json

Comment thread .github/workflows/tests.yml Outdated
@coderdan coderdan force-pushed the feat/port-searchable-json-tests branch from 3fc7654 to b4b8e09 Compare May 26, 2026 03:07
@coderdan coderdan requested a review from a team as a code owner May 26, 2026 03:07
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/tests.yml:
- Line 162: The workflow currently masks test failures by setting
continue-on-error: true on the test job and by appending "|| true" to the test
step commands; remove continue-on-error: true from the test job and delete any
"|| true" postfixes in the test-running steps (the step that runs the Bun test
command, e.g., the step invoking "bun test" or similar) so the job fails on test
failures and surfaces regressions; apply the same change to the other
occurrences around the 241-247 region.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1d6c458e-3fd8-4b1a-a577-235cb23731d6

📥 Commits

Reviewing files that changed from the base of the PR and between 3fc7654 and 217f5fc.

📒 Files selected for processing (2)
  • .github/workflows/tests.yml
  • e2e/tests/supply-chain.e2e.test.ts

Comment thread .github/workflows/tests.yml
@coderdan coderdan force-pushed the feat/port-searchable-json-tests branch from 2fd8f92 to ca012d8 Compare May 26, 2026 04:06
@coderdan coderdan changed the base branch from main to fix/restore-null-guards-in-encryption-ops May 26, 2026 04:07
@coderdan
Copy link
Copy Markdown
Contributor Author

Rebased on top of #493 — the diff for this PR now shows only the test changes. The re-enabled round-trips null values test depends on the runtime null guard restored in #493. When #493 merges, retarget this PR to main.

Copy link
Copy Markdown
Contributor

@auxesis auxesis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good expansion of coverage — thanks for this @coderdan. ❤️

Comment thread .github/workflows/tests.yml
coderdan added 7 commits May 26, 2026 15:18
Port 48 integration tests covering array operations, containment,
field access, path queries, and equality comparisons. These tests
were only in @cipherstash/protect but not in @cipherstash/stack,
which allowed the array_index_mode regression to go undetected.
Update @cipherstash/stack engine requirement from >=18 to >=22 to
match the root package.json. Add a Node version matrix (22, 24)
to CI so tests run against both current LTS and latest.
Add a non-blocking CI job that runs tests under Bun's runtime
using bunx --bun vitest. Uses continue-on-error so failures
don't block merges while we assess Bun compatibility.
The original port (commit 643785c) was based on the pre-0.23.0 protect
test file. #473 substantially rewrote protect's searchable-json-pg
tests to match protect-ffi 0.23.0's stricter HMAC/shape validation
and changed selector/queryType semantics; 37 of the ported tests
were left running on the old EQL contract and failed.

Re-port from the current protect file and re-apply the stack-specific
adjustments: imports from @/identity, @/index, @/schema; encryptedTable
/ encryptedColumn instead of csTable / csColumn; protect-ci-jsonb-stack
table name to avoid collision with protect's suite; cast null in the
null round-trip test since stack's encrypt() drops null from its
JsPlaintext input by design.
The supply-chain hardening test required actions/setup-node's
node-version to be the literal '22', which blocked broadening test
coverage via a Node version matrix. Relax to also accept
\${{ matrix.<key> }} when that matrix key resolves to an array that
includes '22', so the Node 22 baseline is still enforced while
additional versions can be added alongside.
stack's encrypted column type intentionally excludes null (see prior
feat(stack): remove null from Encrypted type). encrypt(null) returns
an encrypted SteVec rather than null, so the null pass-through scenario
inherited from the protect port doesn't apply. Skip it explicitly
rather than carry a forever-failing assertion.
@coderdan coderdan force-pushed the feat/port-searchable-json-tests branch from 04a5d64 to db3563b Compare May 26, 2026 05:18
Base automatically changed from fix/restore-null-guards-in-encryption-ops to main May 26, 2026 05:20
@coderdan coderdan merged commit b9c4fe5 into main May 26, 2026
5 checks passed
@coderdan coderdan deleted the feat/port-searchable-json-tests branch May 26, 2026 05:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants