test(sql): centralize fault-injection wire frames; convert decode mocks to real servers#32467
test(sql): centralize fault-injection wire frames; convert decode mocks to real servers#32467alii wants to merge 9 commits into
Conversation
…ks to real servers
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughIntroduces ChangesWire-frames library and test migration
Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Comment |
|
This PR may be a duplicate of:
🤖 Generated with Claude Code |
|
@robobun adopt |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
test/js/sql/sql-onconnect-onclose-throw.test.ts (1)
226-240: 🧹 Nitpick | 🔵 Trivial | 💤 Low valueConsider using
test.concurrent.eachfor consistency.Similar to the refused-connection tests above, this loop could be refactored to use
test.concurrent.eachfor cleaner parameterized tests. This would make the test structure more consistent throughout the file.🤖 Prompt for 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. In `@test/js/sql/sql-onconnect-onclose-throw.test.ts` around lines 226 - 240, Replace the for loop that iterates over adapter and closedCode tuples with a test.concurrent.each() call to create parameterized tests. Pass the array of adapter/code pairs (including "postgres"/"ERR_POSTGRES_CONNECTION_CLOSED" and "mysql"/"ERR_MYSQL_CONNECTION_CLOSED") as the parameter to test.concurrent.each(), and update the test callback to receive the adapter and closedCode parameters directly. This will make the test structure consistent with similar parameterized tests in the file and eliminate the manual loop iteration, while keeping the same test logic inside the callback that uses forcedCloseFixture(adapter) and verifies the expected stdout and exitCode.test/js/sql/sql-mysql.test.ts (1)
1165-1189:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winMove the mock timeout matrix out of the Docker suite.
This block uses only
listeningServerand does not depend oncontainer, but it sits underisDockerEnabled()and runs once per image. Move it to a standalone non-Docker MySQL fault-injection test, or otherwise outside the container loop, so it runs once and is not skipped on non-Docker runs.Based on learnings, “keep Docker-gated MySQL container tests and non-Docker MySQL tests in separate files” and standalone
test/js/sql/sql-mysql-*.test.tsfiles are intentional for mock/non-Docker tests.🤖 Prompt for 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. In `@test/js/sql/sql-mysql.test.ts` around lines 1165 - 1189, The "timeouts" describe block with its test.each for connection timeout keys should be moved out of the Docker-gated container loop since it only depends on listeningServer and not on the container configuration. Extract this timeout test block and place it in a separate standalone test file following the pattern of test/js/sql/sql-mysql-*.test.ts for non-Docker MySQL tests, or move it outside the isDockerEnabled() conditional block, so that it runs once in a non-Docker context instead of being repeated for each Docker image.Source: Learnings
test/js/sql/postgres-multi-statement-fields.test.ts (1)
81-119:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winMake the fault-injection server cleanup deterministic.
This mock writes and ends sockets on an error-path test, but the socket has no expected-error handler and
server.close()is not awaited. Add the handler and await close completion so the test does not leak a listener or fail on an unhandled reset. As per coding guidelines, “Tests must be hermetic and leave nothing behind.”Proposed fix
const { port, server } = await listeningServer(socket => { + socket.on("error", () => {}); let startup = true; socket.on("data", data => { if (startup) { @@ try { const result = await sql`select 1 as x; select 2 as y`.simple(); expect(result).toEqual([[{ x: "1" }], [{ y: "2" }]]); } finally { - await sql.close(); - server.close(); + await sql.close().catch(() => {}); + await new Promise<void>(resolve => server.close(() => resolve())); }🤖 Prompt for 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. In `@test/js/sql/postgres-multi-statement-fields.test.ts` around lines 81 - 119, The mock server's socket has no error handler and server.close() is not awaited, which can cause test flakiness and resource leaks when the socket ends unexpectedly. Within the socket.on("data") handler or at the socket setup, add a socket.on("error") handler to gracefully handle expected errors like connection resets so they don't cause unhandled rejections, and change server.close() to await server.close() in the finally block to ensure deterministic cleanup and prevent listener leaks.Source: Coding guidelines
test/js/sql/sql.test.ts (1)
2981-2983:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winRemove the 1s test timeout.
Line 2982 adds a second timing bound even though the query is already bounded by
[key]: 0.2; this can fail under slow debug/ASAN CI for scheduler latency rather than behavior. Let the default test timeout handle true hangs. As per coding guidelines, Bun already has test timeouts and tests should avoid custom timing bounds unless they are load-bearing.Proposed fix
}, - { - timeout: 1000, - }, );🤖 Prompt for 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. In `@test/js/sql/sql.test.ts` around lines 2981 - 2983, Remove the timeout configuration object containing `timeout: 1000` from the test. The query is already bounded by a `[key]: 0.2` timing constraint, so the additional 1-second test timeout creates an unnecessary secondary timing bound that can cause failures in slow CI environments with debug or ASAN builds. Delete the entire configuration object with the timeout property and let the default Bun test timeout handle any actual test hangs instead.Source: Coding guidelines
🤖 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 `@test/js/sql/sql-mysql-columns-realloc-oom.test.ts`:
- Around line 42-45: The test does not verify that the malformed execute path
was actually triggered on the server side. Add a tracking variable (flag) that
is set to true when the mock server receives the COM_STMT_EXECUTE command, then
assert that this flag is true after checking the subprocess exit code in the
test. This ensures that the test passes only when the huge result-set header is
actually sent by the mock, preventing false positives where any clean JSON error
would cause the test to pass. Apply the same pattern to the other test cases
mentioned at lines 65-67 and 107-113.
In `@test/js/sql/sql-onconnect-onclose-throw.test.ts`:
- Around line 126-139: Replace the for loop containing test.concurrent with
test.concurrent.each for a more idiomatic approach. Move the array of test
parameters directly into the test.concurrent.each call, use %s placeholder in
the test name string instead of template literal for the adapter name, and
update the async callback function to receive the array elements (adapter,
refusedCode, url) as direct parameters instead of destructuring from the for
loop. The test body logic remains the same but now uses these parameters from
the function arguments.
In `@test/js/sql/sql.test.ts`:
- Around line 12523-12531: The CREATE TABLE statement at the beginning of this
test fixture creates a permanent table that may not be cleaned up if the process
crashes before reaching the finally block. Replace the CREATE TABLE statement
with CREATE TEMPORARY TABLE to ensure Postgres automatically drops the table
when the fixture connection closes, making the test hermetic and preventing
orphaned tables in the shared database.
---
Outside diff comments:
In `@test/js/sql/postgres-multi-statement-fields.test.ts`:
- Around line 81-119: The mock server's socket has no error handler and
server.close() is not awaited, which can cause test flakiness and resource leaks
when the socket ends unexpectedly. Within the socket.on("data") handler or at
the socket setup, add a socket.on("error") handler to gracefully handle expected
errors like connection resets so they don't cause unhandled rejections, and
change server.close() to await server.close() in the finally block to ensure
deterministic cleanup and prevent listener leaks.
In `@test/js/sql/sql-mysql.test.ts`:
- Around line 1165-1189: The "timeouts" describe block with its test.each for
connection timeout keys should be moved out of the Docker-gated container loop
since it only depends on listeningServer and not on the container configuration.
Extract this timeout test block and place it in a separate standalone test file
following the pattern of test/js/sql/sql-mysql-*.test.ts for non-Docker MySQL
tests, or move it outside the isDockerEnabled() conditional block, so that it
runs once in a non-Docker context instead of being repeated for each Docker
image.
In `@test/js/sql/sql-onconnect-onclose-throw.test.ts`:
- Around line 226-240: Replace the for loop that iterates over adapter and
closedCode tuples with a test.concurrent.each() call to create parameterized
tests. Pass the array of adapter/code pairs (including
"postgres"/"ERR_POSTGRES_CONNECTION_CLOSED" and
"mysql"/"ERR_MYSQL_CONNECTION_CLOSED") as the parameter to
test.concurrent.each(), and update the test callback to receive the adapter and
closedCode parameters directly. This will make the test structure consistent
with similar parameterized tests in the file and eliminate the manual loop
iteration, while keeping the same test logic inside the callback that uses
forcedCloseFixture(adapter) and verifies the expected stdout and exitCode.
In `@test/js/sql/sql.test.ts`:
- Around line 2981-2983: Remove the timeout configuration object containing
`timeout: 1000` from the test. The query is already bounded by a `[key]: 0.2`
timing constraint, so the additional 1-second test timeout creates an
unnecessary secondary timing bound that can cause failures in slow CI
environments with debug or ASAN builds. Delete the entire configuration object
with the timeout property and let the default Bun test timeout handle any actual
test hangs instead.
🪄 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: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: cf86b4a3-4f08-491c-8ae2-48825a96401e
📒 Files selected for processing (24)
test/js/sql/postgres-binary-array-bounds.test.tstest/js/sql/postgres-binary-numeric.test.tstest/js/sql/postgres-multi-statement-fields.test.tstest/js/sql/postgres-tls-ctx-leak.test.tstest/js/sql/sql-close-pending-connection.test.tstest/js/sql/sql-connect-error-reporting.test.tstest/js/sql/sql-mysql-auth-short-nonce.test.tstest/js/sql/sql-mysql-cached-error.test.tstest/js/sql/sql-mysql-clean-reentry.test.tstest/js/sql/sql-mysql-columns-realloc-oom.test.tstest/js/sql/sql-mysql-datetime-roundtrip.test.tstest/js/sql/sql-mysql-datetime-text-mock-fixture.tstest/js/sql/sql-mysql-mediumint.test.tstest/js/sql/sql-mysql-query-string-leak.test.tstest/js/sql/sql-mysql-raw-length-prefix.test.tstest/js/sql/sql-mysql-tls-plaintext-injection.test.tstest/js/sql/sql-mysql.test.tstest/js/sql/sql-onconnect-onclose-throw.test.tstest/js/sql/sql-postgres-json-array-bool-literal.fixture.tstest/js/sql/sql-postgres-short-data-row.fixture.tstest/js/sql/sql.test.tstest/js/sql/tls-sql.test.tstest/js/sql/wire-frames.test.tstest/js/sql/wire-frames.ts
💤 Files with no reviewable changes (2)
- test/js/sql/sql-mysql-datetime-roundtrip.test.ts
- test/js/sql/sql-mysql-datetime-text-mock-fixture.ts
|
✅ Adopted. All review feedback addressed (10 threads resolved); touched CI status: build 63216 and the re-roll build 63233 are red only on unrelated infrastructure:
260/261 non-Windows jobs passed in build 63216, including every Review-feedback changes (82abf88..4458e9a)
|
- sql-mysql-columns-realloc-oom: assert the mock actually reached COM_STMT_EXECUTE so a JSON error before execute cannot satisfy the stdout check - sql.test.ts zero-column fixture: use CREATE TEMPORARY TABLE so a crashed/killed child does not leave a permanent table in the shared container - sql-mysql-datetime-tz-fixture: restore coverage for a non-zero day past its month length (2024-02-31 under ALLOW_INVALID_DATES), which the deleted text-protocol mock previously exercised - postgres-multi-statement-fields: add socket error handler and await server.close() in the fault-injection block for deterministic teardown
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
test/js/sql/sql.test.ts (1)
12549-12560:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winDon’t make SQL fixture subprocesses fail on benign stderr noise.
These subprocess fixtures already assert stable stdout markers and exit code. The
filteredStderrchecks can still flake on ASAN/debug lanes; only surface stderr when the child actually fails.Based on learnings, SQL fixture subprocess tests in
test/js/sql/**should assert stable stdout and exit code while treating stderr-only ASAN/debug noise as acceptable.Suggested cleanup pattern
- const filteredStderr = stderr - .split(/\r?\n/) - .filter(l => l && !l.startsWith("WARNING: ASAN interferes")) - .join("\n"); - // The second result set must expose all three named columns with their // values; reusing the zero-property structure from the first result set // would yield a row object with no own properties. expect(stdout).toContain('SECOND_RESULT_SET [{"a":"1","b":"2","c":"3"}]'); expect(stdout).toContain("SECOND_RESULT_SET_KEYS a,b,c"); expect(stdout).toContain("FIXTURE_DONE"); - expect(filteredStderr).toBe(""); + if (exitCode !== 0) { + expect(stderr).toBe(""); + } expect(exitCode).toBe(0);Apply the same pattern to the other two fixture subprocess blocks.
Also applies to: 12591-12600, 12744-12756
🤖 Prompt for 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. In `@test/js/sql/sql.test.ts` around lines 12549 - 12560, The test is asserting that filteredStderr must be empty which causes flakiness on ASAN/debug lanes due to benign noise. Remove the expect(filteredStderr).toBe("") assertion since the test already validates stable stdout markers and exit code for the fixture subprocess. Only surface stderr when the child process actually fails by checking the exit code. Apply this same pattern to remove stderr-only assertions in the other two fixture subprocess test blocks at lines 12591-12600 and 12744-12756.Source: Learnings
🤖 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.
Outside diff comments:
In `@test/js/sql/sql.test.ts`:
- Around line 12549-12560: The test is asserting that filteredStderr must be
empty which causes flakiness on ASAN/debug lanes due to benign noise. Remove the
expect(filteredStderr).toBe("") assertion since the test already validates
stable stdout markers and exit code for the fixture subprocess. Only surface
stderr when the child process actually fails by checking the exit code. Apply
this same pattern to remove stderr-only assertions in the other two fixture
subprocess test blocks at lines 12591-12600 and 12744-12756.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 6002f792-c4a4-4b60-a11e-a8f23f38c71e
📒 Files selected for processing (4)
test/js/sql/postgres-multi-statement-fields.test.tstest/js/sql/sql-mysql-columns-realloc-oom.test.tstest/js/sql/sql-mysql-datetime-tz-fixture.tstest/js/sql/sql.test.ts
…nverted MySQL tests - sql-mysql-cached-error: bracket the cache-hit query with SHOW SESSION STATUS LIKE 'Com_stmt_prepare' and assert the counter did not increment, restoring the prepareCount===2 precondition the original mock asserted. Without it, a fresh prepare would return an identical error for identical SQL and silently satisfy every assertion without exercising the cached-slice path. - sql-mysql-mediumint, sql-mysql-raw-length-prefix: CREATE TEMPORARY TABLE so a hung/SIGKILLed test does not leak tables into the shared mysql_plain container (same class as the Postgres fixture in 82abf88; these four MySQL sites were missed).
There was a problem hiding this comment.
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 `@test/js/sql/sql-mysql-cached-error.test.ts`:
- Around line 48-50: The test currently only verifies that preparesAfter does
not exceed preparesBefore, which can pass vacuously if prepared statements were
never used at all. Add an assertion immediately after capturing preparesBefore
to verify that it is greater than zero, ensuring that prepared statements were
actually being used before the cache-hit check. This ensures the test fails for
the right reason and proves the strongest invariant that prepared statement
execution actually occurred.
🪄 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: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 9809338c-e4c0-43fd-8394-07bca4e21d59
📒 Files selected for processing (3)
test/js/sql/sql-mysql-cached-error.test.tstest/js/sql/sql-mysql-mediumint.test.tstest/js/sql/sql-mysql-raw-length-prefix.test.ts
…heck Guards the no-increment assertion against being vacuously satisfied if the prepared-statement path were never taken.
Matches the forcedCloseFixture pattern in the same file: import closedPort from ./wire-frames via absolute path inside the spawned subprocess so the bind/close/connect window is not widened by the tempDir write + bunExe spawn + module load, during which the concurrent forcedCloseFixture tests are issuing bind(0). Restores the pre-PR in-process window and drops the now-unused top-level closedPort import.
Matches sql-mysql-bind-oob.test.ts / sql-mysql-bind-blob-borrow.test.ts / sql-mysql-datetime-roundtrip.test.ts. describeWithContainer proceeds when BUN_TEST_SERVICE_mysql_plain is set, but in non-docker environments that points at a MariaDB whose root@localhost uses unix_socket auth, so mysql://root@host:port fails. isDockerEnabled() is the established gate for the mysql://root@ path; CI (docker-compose mysql:8 with MYSQL_ALLOW_EMPTY_PASSWORD) is unaffected.
…Y rollback Restores the same-session precondition the original mock enforced via queries=['select 1'] / desynced=false: capture CONNECTION_ID() before the oversized attempt and after the follow-up query, and assert they match. Without this, a regression that flushed partial bytes (or closed on overflow) could let the pool transparently reconnect, and 'select 1 as ok' would succeed on a fresh session without the write-buffer rollback having worked. Same pattern as the Com_stmt_prepare check in sql-mysql-cached-error.test.ts.
There was a problem hiding this comment.
All prior feedback has been addressed and no new issues found, but this is a 25-file test refactor that converts ~10 mock-server regression tests to Docker-gated container tests and introduces a new shared wire-protocol module — worth a human pass to confirm the coverage equivalence and the new isDockerEnabled() gating is acceptable.
Extended reasoning...
Overview
This PR is a test-only refactor in test/js/sql/ (net −1,064 lines, no src/ changes) with three parts: (1) a new wire-frames.ts module centralizing all hand-rolled Postgres/MySQL protocol-frame builders plus shared listeningServer/closedPort/neverAnsweringServer helpers, with a wire-frames.test.ts self-test that round-trips the builders through Bun's own parsers; (2) ~12 fault-injection tests migrated to import frames from the shared module instead of inlining Buffer.alloc/writeInt32BE sequences, each now carrying a "DO NOT COPY" header; (3) ~8 decode/roundtrip tests converted from mock net.createServer servers to real describeWithContainer("postgres_plain"|"mysql_plain") containers, plus two inline fixtures extracted to *.fixture.ts files and one (the zero-column result-set test) rewritten against a real Postgres table.
Security risks
None. All changes are to test code; no production code paths, auth, crypto, or permissions are touched. The wire-frame builders only encode protocol bytes for local mock servers used in tests.
Level of scrutiny
Medium-high. While test-only, this PR materially changes how several regression tests assert their invariants:
- Mock→container conversions now gate previously-always-on tests behind
isDockerEnabled(), so coverage on non-Docker CI legs drops. - Several converted tests originally instrumented the mock server's wire traffic directly (e.g.
prepareCount,queries: ["select 1"],desynced: false); the real-server equivalents had to reconstruct those preconditions viaSHOW SESSION STATUS/CONNECTION_ID(), which the author added in response to review feedback. - The deleted 197-line
sql-mysql-datetime-text-mock-fixture.tsand the 188-line NEWDECIMAL mock insql-mysql.test.tswere both judged redundant with existing container-backed siblings — that judgment is reasonable but worth a maintainer confirming.
Other factors
All six rounds of inline feedback (mine and CodeRabbit's) have been addressed in commits 82abf88 → 4458e9a, and every inline thread is resolved. The author confirmed 108 pass / 0 fail across the touched files with both containers up. The remaining CI failures are unrelated freebsd/android build-rust infra issues. A human (alii) has engaged via @robobun adopt but not yet reviewed. Given the breadth (25 files), the test-semantics changes, and the new shared module that future fault-injection tests will depend on, I'm deferring rather than approving so a maintainer can sign off on the coverage equivalence and the Docker-gating trade-off.
Three pieces, all in
test/js/sql/. Net −1,064 lines.1.
wire-frames.ts— single source of truth for fault-injection protocol bytesNew shared module with one builder per Postgres/MySQL frame type the fault-injection tests emit (
pgAuthenticationOk,pgReadyForQuery,pgErrorResponse,pgRowDescription,pgDataRow,pgCommandComplete,pgRaw,mysqlHandshakeV10,mysqlOkPacket,mysqlAuthSwitchRequest,mysqlColumnDefinition,mysqlStmtPrepareOk,mysqlLenencInt/Str,mysqlRawPacket,mysqlReadPackets), plus thelisteningServer/closedPort/neverAnsweringServer/pgMinimalReadyServerhelpers every fault-injection file was reimplementing. Each builder carries a one-line spec citation (PostgreSQL FE/BE protocol §55.x / MySQLpage_protocol_*).wire-frames.test.tsis the spec-compliance self-test: it spins anet.createServeremittingpgAuthenticationOk() + pgReadyForQuery()(and the MySQL equivalent), doesawait new Bun.SQL({url}).connect(), and asserts it resolves — so if a builder ever drifts from what Bun's own parser accepts, this file goes red before the fault-injection tests start silently misbehaving.2. Mark + refactor the fault-injection tests
These genuinely need a misbehaving server (refused/dropped/malformed frames) and stay as
net.createServermocks, but now (a) carry a "DO NOT COPY THIS PATTERN — anything a real server can produce belongs indescribeWithContainer" header, and (b) import every frame from./wire-framesinstead of inliningBuffer.alloc/writeInt32BEsequences:sql-connect-error-reporting,sql-close-pending-connection,postgres-tls-ctx-leak,postgres-binary-array-bounds,sql-mysql-auth-short-nonce,sql-mysql-tls-plaintext-injection,sql-mysql-columns-realloc-oom,sql-mysql-clean-reentry,tls-sql.test.ts(the two SSLRequest-spoofing blocks),sql-onconnect-onclose-throw(closedPort/neverAnsweringServer), and the never-responds blocks insql.test.ts/sql-mysql.test.ts.After this,
grep writeInt32BE test/js/sql/*.test.tsfinds exactly one hit —postgres-binary-array-bounds.test.ts:27, building the deliberately-hostile column payload body (the frames around it come from./wire-frames).3. Convert decode/roundtrip mocks to
describeWithContainerThese were testing decode behavior a real server produces — they shouldn't have been mocks. Each now wraps in
describeWithContainer("postgres"|"mysql", { image: ..._plain })and issues real SQL that produces the same data the mock was hand-encoding, with the original assertions preserved:postgres-binary-numeric(SELECT '...'::numeric),postgres-multi-statement-fields(.simple()multi-statement),sql-mysql-mediumint,sql-mysql-raw-length-prefix,sql-mysql-cached-error,sql-mysql-query-string-leak,sql-mysql-datetime-roundtrip(mock half + 197-linesql-mysql-datetime-text-mock-fixture.tsdeleted — the real-server sibling already covered it), the DECIMAL block insql-mysql.test.ts, and three inline mocks insql.test.ts(text-array / multi-result-set / short-DataRow regressions, the latter two extracted to*.fixture.tssince they assert process exit).Verification
postgres_plain+mysql_plaincontainers);sql.test.tsandsql-mysql.test.tsload clean.wire-frames.test.tsself-test green for both adapters.src/changes.