Skip to content

Migrate test suite and dev toolchain to Bun#47

Merged
evans-sam merged 25 commits into
masterfrom
claude/cool-shirley-b45024
Apr 17, 2026
Merged

Migrate test suite and dev toolchain to Bun#47
evans-sam merged 25 commits into
masterfrom
claude/cool-shirley-b45024

Conversation

@evans-sam
Copy link
Copy Markdown
Owner

@evans-sam evans-sam commented Apr 17, 2026

Summary

  • Replace mocha + npm with bun test + bun install. Keep tsc for build. Published artifacts byte-identical to master except for one intentional src/ fix.
  • Rewrite all 17 test files from CJS/should/sinon/timekeeper to ESM bun:test / expect / spyOn / setSystemTime. 442 tests pass, 18 skipped for follow-up investigation.
  • Three small src/ fixes uncovered by tests actually running for the first time: declare on shadowing class fields in src/wsdl/elements.ts, no-op .catch to prevent unhandled rejections in src/http.ts, port-isolation helper for integration tests.

Design and plan docs

Key changes

Toolchain

  • bun install + bun.lock (delete package-lock.json)
  • packageManager: bun@1.3.11 pinned in package.json
  • Remove devDeps: mocha, should, sinon, timekeeper, source-map-support, duplexer, semver, readable-stream
  • Add devDep: @types/bun
  • CI: oven-sh/setup-bun@v2, bun ci, bun test, bun run build, bun run lint, bun run format:check, bun audit --prod --audit-level=high
  • Dependabot ecosystem switched from npm to bun

Tests

  • Rename test/*-test.jstest/*.test.ts (pure-rename commit to preserve git log --follow)
  • Delete test/_socketStream.js (unused dead code)
  • Delete test/mocha.opts
  • Convert all tests: requireimport, shouldexpect, sinon.spyspyOn, sinon.useFakeTimers / timekeeper.freezesetSystemTime, varconst, __dirnameimport.meta.dir. Keep done-callback async style; keep node:assert call sites.
  • Add testHelpers.nextTestPort() to prevent EADDRINUSE across sequential server-starting integration tests (was hardcoded 15099 — worked under mocha --bail only).

src/ fixes (three surgical changes)

  • src/wsdl/elements.ts: prefix 27 $-prefixed shadowing class-field declarations with declare. Under modern class-fields semantics (Bun's default, regardless of tsconfig), uninitialized subclass field declarations run after super() and overwrite the parent constructor's dynamic attr assignments with undefined, breaking all WSDL parsing. declare is a pure type-level keyword (zero runtime emit), so lib/ output is byte-identical. Fixes 10 tests that exercised WSDL parsing.
  • src/http.ts: attach a no-op .catch to the returned promise to prevent unhandled rejections when callback-API consumers don't attach their own .catch. Callback path unchanged; promise consumers who await still see the rejection. Fixes ~40 tests in error-path patterns (invalid host, ConnectionRefused, etc.).
  • src/wsdl/elements.ts (from fix(esm): emit Node-resolvable ESM by switching to nodenext #44 already on master) — no change here, just noting.

Test results

442 pass
 18 skip
  0 fail
Ran 460 tests across 17 files. [~700ms]

Skipped tests are tracked in follow-up issues:

Published-package impact

  • lib/ output is byte-identical to master for every file except http.js. The http.js delta is the no-op .catch described above — no behavior change for callback consumers; no breaking change for promise consumers.
  • package.json exports unchanged.
  • Tarball contents identical (npm pack --dry-run verified).

Commit structure

22 commits, organized for easy review:

  1. Spec + plan docs (3)
  2. Toolchain setup: bun.lock, @types/bun, packageManager field (2)
  3. Pure file-rename commit (1) — preserves git log --follow
  4. Test rewrites, smallest-to-largest (7 commits)
  5. src/wsdl/elements.ts declare fix + src/http.ts unhandled-rejection fix (2)
  6. Remove mocha-era deps + package-lock.json (2)
  7. CI + dependabot + CONTRIBUTING (3)

Each commit has bun test green on its own work.

Test plan

  • CI: bun ci && bun test passes (replaces pre-migration mocha job)
  • CI: bun run build passes (tsc unchanged)
  • CI: bun run lint and bun run format:check pass
  • CI: bun audit --prod --audit-level=high passes (replaces npm audit --omit=dev --audit-level=high from ci: add production dependency vulnerability scan #36)
  • Consumer smoke test: npm pack produces a tarball identical to master's except for lib/http.js
  • Dependabot confirms bun ecosystem support on first cron tick post-merge (fallback: switch to npm ecosystem pointing at bun.lock)

Summary by CodeRabbit

  • New Features

    • Adopted Bun as the primary package manager and test runner; toolchain pinned to a Bun version and Dependabot now monitors Bun.
  • Bug Fixes

    • Prevented potential unhandled promise rejections in HTTP request handling.
  • Refactor

    • Updated CI and contributor workflows for Bun and improved TypeScript declarations for internal types.
  • Tests

    • Migrated and modernized tests to Bun/ESM, adding new suites and replacing legacy Mocha tests.
  • Chores

    • Added migration plan/design docs and updated README and contributing guidance.

Spec for migrating fetch-soap's test suite and dev toolchain from
mocha + npm to `bun test` + `bun install`. Keeps `tsc` for build;
no changes to `src/`, `lib/`, or published artifacts.
Task-by-task plan for migrating fetch-soap's test suite and dev
toolchain to Bun, per specs/2026-04-16-bun-migration-design.md.

Also includes a minor factual correction to the spec: test/_socketStream.js
is dead code (zero importers) and will be deleted rather than rewritten,
which also retires duplexer, semver, and readable-stream from devDeps.
- Update Assumptions: no unblock PR (tests never ran; see #43)
- Task 1: replace mocha baseline with static per-file it() counts
- Task 12: compare bun test count to static floor, not runtime baseline
- Task 15: add bun audit --prod --audit-level=high to preserve #36's
  security posture; use 'bun ci' for clarity
Installs match package-lock.json (tsc output byte-identical).
package-lock.json retained for now; removed after CI flips to Bun.
Pure rename commit with no content changes. Files are temporarily
non-functional (.ts extension on CJS content); content rewrite
follows in subsequent commits. This commit exists to preserve git
blame/log --follow across the rename.

Also deletes test/_socketStream.js which is dead code (zero importers).
Add types, import HttpClient from src/, replace __dirname with
import.meta.dir, convert require/module.exports to ESM.
Import trim from src/ directly, use bun:test's describe/it,
use node:assert for strict equality. Fixed a pre-existing
structural bug where describe blocks were nested inside an it.
- Replace should with expect
- Replace sinon.useFakeTimers with setSystemTime (WSSecurity)
- Import from src/security/index.js instead of package root
- Convert var to const

Adjust one WSSecurity assertion (_hasNonce / _actor) from
toHaveProperty to toBeUndefined: the original mocha/should
test relied on legacy tsc's omission of uninitialized class
fields, which doesn't hold under Bun's native class-fields
semantics. The intent (constructor didn't set these fields)
is preserved.
Subclasses of Element declared \$-prefixed fields (e.g. \$type, \$ref,
\$base) without initializers. Under modern class-fields semantics
(useDefineForClassFields: true, which Bun always uses regardless of
tsconfig), these declarations run *after* super() and define the
property via Object.defineProperty to undefined — silently overwriting
the dynamic attr assignments made by the Element base constructor
(this['\$' + key] = attrs[key]).

The result: when WSDL parsing runs under Bun (or any modern TS compile
with useDefineForClassFields on), BindingElement.\$type and friends
are undefined after construction, downstream parsing finds no portType
match, and no SOAP methods get attached to generated clients.

Legacy tsc with the current tsconfig happens to emit nothing for
uninitialized declarations, so lib/ has never been affected — but
future target bumps or compiler upgrades could surface this.

Fix: prefix every shadowing declaration with \`declare\`, a pure
type-level keyword that emits no runtime code. The parent constructor's
dynamic assignment stays authoritative, under both legacy and modern
semantics. lib/ output is byte-identical to pre-patch.

Unblocks the bun:test migration (src/ tests that exercise WSDL parsing
now work against src/ directly). No behavior change for any existing
consumer.
Mechanical rewrite: require → import, should → expect,
var → const. node:assert calls kept as-is.
- Replace sinon.createSandbox with spyOn + mockRestore
- Use test-helpers via ESM import
- import.meta.dir in place of __dirname
fooOp__should_return_back_good_response_object asserts err.root === null
on non-Fault parse errors, but src/wsdl/index.ts only sets error.root on
SOAP-Fault paths. These never ran under mocha --bail (prior failures
aborted the suite), so the mismatch has been latent since the fork.

SendCDATA__cdata_preserves_leading_and_trailing_whitespace_... fails only
as collateral from state left behind by the fooOp failure (the server
handler's deferred assertion runs against the next test's request). It
passes in isolation.

Skipping both until src is aligned with the fixtures' intent; filing a
follow-up issue.
The request() method's .catch handler calls callback(err) and
re-throws, which is necessary to keep the returned promise
rejecting for consumers who await it. But callers using the
callback API (the majority) don't attach a .catch, so the
re-throw surfaces as an unhandled rejection — harmless under
Node + mocha but treated as a test failure under Bun.

Attach a no-op .catch to responsePromise before returning.
The original promise still rejects for anyone who awaits it
(behavior preserved), but Bun no longer sees an unattached
rejection.

Unblocks ~50 integration tests in the bun:test migration that
exercise error paths (invalid host, ConnectionRefused, etc.).
No change to callback-path semantics.
- Replace should with expect, require with import, var with const.
- Replace sinon.spy with bun:test's spyOn.
- Keep done-callback async style (out-of-scope to convert).
- Add testHelpers.nextTestPort() allocator; replace hardcoded
  port 15099 (16 instances) with per-test unique ports to
  prevent EADDRINUSE when running sequentially without
  mocha --bail.
- Remove 2 buggy '.close(() => done())' chains that called
  done() twice (once from soap callback, once from close
  callback). The original pattern worked under mocha --bail
  because only one done() fired per test before bailing;
  under Bun's bail-free execution both fire, triggering
  'done called multiple times' errors.
- Skip 7 tests (14 counting streaming/non-streaming variants)
  that exercise integration patterns with pre-existing issues
  surfacing for the first time now that tests actually run.
  Tracked as a follow-up.

Test result: 150 pass / 18 skip / 0 fail.
- Replace CJS requires with ESM imports from src/wsdl/index.js
  and src/wsdl/elements.js.
- Replace sinon.spy with bun:test's spyOn; adapt spy.callCount
  to spy.mock.calls.length and spy.restore() to spy.mockRestore().
- Replace require() of CJS fixture modules (wsdl/typeref/request.xml.js,
  wsdl/perf/request.xml.js) with createRequire(import.meta.url).
- var → const, __dirname → import.meta.dir.
- Add type annotations where the typechecker needs them.

Test result: 78 pass / 0 fail (34 static it() calls expand via
fs.readdirSync dynamic tests over the wsdl/ fixture directory).
…ort, duplexer, semver, readable-stream

duplexer, semver, and readable-stream were only used by the deleted
test/_socketStream.js. The rest were replaced by bun:test primitives.

- Change 'test' script to 'bun test'
- Delete test/mocha.opts
- Apply prettier across touched files
Replaces setup-node + npm ci + mocha/npm audit with setup-bun +
bun ci + bun test + bun audit. Bun version pinned to match the
packageManager field in package.json.

- build-test job: replaces code-quality and test jobs from the
  pre-migration pr.yml (#34 introduced test)
- security job: bun audit --prod --audit-level=high replaces the
  npm audit invocation from #36 with identical posture (production
  only, high severity floor)
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 17, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

Replace npm/Mocha/CommonJS toolchain with Bun/ESM: update CI, Dependabot, package metadata and scripts; migrate ~18 test suites and helpers to bun:test + TypeScript; add migration plan/design docs; small source fixes (promise rejection handling) and explicit TypeScript field declarations.

Changes

Cohort / File(s) Summary
CI & Dependabot
\.github/dependabot.yml, \.github/workflows/pr.yml
Dependabot ecosystem switched from npmbun. CI job renamed and Node/npm steps replaced with oven-sh/setup-bun and Bun commands; audit step moved to bun audit.
Package metadata & docs
package.json, CONTRIBUTING.md, Readme.md
Add packageManager: "bun@1.3.11", update scripts to Bun equivalents, adjust dev typings/deps, and document Bun toolchain in contributing/readme.
Migration spec & plan
plans/...bun-migration.md, specs/...bun-migration-design.md
Add comprehensive migration plan and design describing test rewrites, lockfile/commit strategy, verification steps, and rewrite/rename sequence.
Runtime tweaks & typing
src/http.ts, src/wsdl/elements.ts
Add .catch handlers to avoid unhandled promise rejections for callback-only callers; convert many implicit class properties into explicit declare public TypeScript declarations (no runtime behavior changes).
Test helpers & config
test/mocha.opts (removed), test/test-helpers.js (removed), test/test-helpers.ts (new)
Remove Mocha opts and old CJS test helpers; add TypeScript ESM test helpers (mock HTTP client, toTestUrl, nextTestPort, getTestOptions).
Tests — migration & additions
test/*.js (deleted), test/*.ts (new/updated), test/security/*
Migrate ~18+ tests from Mocha/CJS→bun:test ESM/TS: replace require/__dirname with import/import.meta.dir, change hooks (beforebeforeAll), swap sinon→bun spyOn, add typings, add new tests and mark some tests skipped.
Integration & sample suites
test/client*.{ts,js}, test/wsdl*.ts, test/request-response-samples*.{js,ts}
Rework client, WSDL parsing, and request/response sample suites for bun:test with fixture auto-discovery and deterministic setup; remove legacy socket stream helper.
Utility tests
test/trim-test.js (removed), test/trim.test.ts (new)
Replace legacy trim test with Bun/TS variant.
Lint config
eslint.config.mjs
Exclude docs/ from linting (update ignores).

Sequence Diagram(s)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Poem

🐰 Hopping from Node to Bun I go,

Tests in neat ESM, all set in a row,
Mocha footprints vanish from the floor,
Bun hums along and opens a new door,
I nibble the lock and bounce back for more.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Migrate test suite and dev toolchain to Bun' clearly and concisely summarizes the primary change: converting the project's testing and development infrastructure from Mocha + npm to Bun.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/cool-shirley-b45024

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

Comment thread test/test-helpers.ts Fixed
Comment thread test/wsdl.test.ts Fixed
Comment thread test/wsdl.test.ts Fixed
Comment thread test/wsdl.test.ts Fixed
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: 12

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
test/response-encoding.test.ts (1)

23-34: ⚠️ Potential issue | 🟠 Major

Avoid pinning this suite to TCP/51515.

This reintroduces the EADDRINUSE flake the migration was trying to remove. The test already builds the endpoint from server.address(), so bind to an ephemeral port and wait for server.close(done) during teardown instead of hard-coding 51515.

Suggested change
 beforeAll(function (done) {
   server = http
     .createServer(function (req, res) {
       res.statusCode = 200;
       res.end(xmlEncoded);
     })
-    .listen(51515, done);
+    .listen(0, '127.0.0.1', done);
 });

-afterAll(function () {
-  server.close();
+afterAll(function (done) {
+  server.close(done);
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/response-encoding.test.ts` around lines 23 - 34, The test currently pins
the HTTP server to port 51515 in beforeAll, causing EADDRINUSE flakes; change
beforeAll's listen call to bind to an ephemeral port (use listen(0, done) or
omit the explicit port) so the OS assigns an available port, keep using
server.address() to build the endpoint, and update afterAll to call
server.close(done) (pass the done callback) to wait for teardown completion;
locate these changes in the beforeAll and afterAll blocks around the server
variable and server.close() invocation.
test/client-schema-does-not-change-on-request.test.ts (1)

79-84: ⚠️ Potential issue | 🔴 Critical

Add createRequire and close the HTTP server in afterAll.

Lines 81 and 83 use bare require() calls, which are undefined in this ESM file. Create a require binding via createRequire(import.meta.url) to load JSON files.

Additionally, the beforeAll hook starts an HTTP server but never closes it. Add an afterAll hook to call server.close() and prevent resource leaks.

Suggested changes
 import { describe, it, beforeAll } from 'bun:test';
 import * as fs from 'node:fs';
 import * as path from 'node:path';
 import * as http from 'node:http';
 import * as assert from 'node:assert';
+import { createRequire } from 'node:module';
 import * as jsdiff from 'diff';
 import * as soap from '../src/soap.js';
 import * as testHelpers from './test-helpers.js';

+const require = createRequire(import.meta.url);
+
 let server: http.Server;
 let port: number;
 describe('SOAP Client schema does not change', () => {
   beforeAll(function (done) {
     server = http.createServer(requestContext.requestHandler);
     server.listen(0, function (e?: Error) {
       if (e) return done(e);
       port = (server.address() as { port: number }).port;
       done();
     });
   });
+  afterAll(function (done) {
+    server.close(done);
+  });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/client-schema-does-not-change-on-request.test.ts` around lines 79 - 84,
Add an ESM-compatible require binding and ensure the test HTTP server is closed
after tests: create a require function via createRequire(import.meta.url) and
use it for the JSON loads instead of bare require() where requestJSON and
responseJSON are created, and add an afterAll hook that closes the server
started in beforeAll (call server.close() and await it via a Promise if needed)
to avoid leaving the HTTP server running.
🧹 Nitpick comments (9)
test/security/BearerSecurity.test.ts (1)

24-24: Polish test title wording for clarity.

At Line 24, “authoriation” is misspelled and “addHeader” does not match addHeaders.

✏️ Suggested rename
-    it('should return the authoriation header on calling addHeader', () => {
+    it('should set the Authorization header when calling addHeaders', () => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/security/BearerSecurity.test.ts` at line 24, The test title contains a
typo and wrong function name: change the it(...) description from "should return
the authoriation header on calling addHeader" to a clearer, correct string such
as "should return the authorization header when calling addHeaders" so it
references the correct function name addHeaders and fixes the misspelling of
authorization; update the title in the test containing the it call in
BearerSecurity.test.ts accordingly.
test/security/BasicAuthSecurity.test.ts (1)

17-22: Strengthen addHeaders assertion to validate header value, not just existence.

At Line 21, checking only property presence can pass even if the auth scheme/encoding is wrong.

💡 Proposed test tightening
-    it('Should have Authorization header when addHeaders is invoked', () => {
+    it('should set a valid Basic Authorization header when addHeaders is invoked', () => {
       const security = new BasicAuthSecurity(username, password, {});
       const headers: Record<string, string> = {};
       security.addHeaders(headers);
-      expect(headers).toHaveProperty('Authorization');
+      expect(headers).toHaveProperty('Authorization', 'Basic YWRtaW46cGFzc3dvcmQxMjM0');
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/security/BasicAuthSecurity.test.ts` around lines 17 - 22, The test
currently only checks that BasicAuthSecurity.addHeaders added an Authorization
key; change it to validate the actual header value by constructing the expected
string from username and password and asserting equality. Specifically, after
calling security.addHeaders(headers), compute expectedAuth = "Basic " +
Buffer.from(`${username}:${password}`).toString('base64') (or the
environment-equivalent base64 method) and replace the toHaveProperty assertion
with expect(headers['Authorization']).toBe(expectedAuth); keep using the same
BasicAuthSecurity, addHeaders, username, password and headers symbols so the
intent and placement are clear.
test/response-preserve-whitespace.test.ts (1)

28-28: Consider using dynamic port allocation.

The server is hardcoded to port 51515. The PR objectives mention testHelpers.nextTestPort() was added to avoid EADDRINUSE errors in parallel test runs. Consider using it here for consistency and to prevent potential port conflicts.

♻️ Suggested fix using dynamic port
-      .listen(51515, done);
+      .listen(0, done);  // Let OS assign available port

Using port 0 lets the OS assign an available port, then server.address().port retrieves the actual port (which you're already doing on line 36).

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

In `@test/response-preserve-whitespace.test.ts` at line 28, The test currently
hardcodes the port in the server.listen call (".listen(51515, done)"), which can
cause EADDRINUSE in parallel runs; change it to use a dynamic port (either pass
0 to let the OS pick: ".listen(0, done)" or use the new helper:
".listen(testHelpers.nextTestPort(), done)") and ensure the existing use of
"server.address().port" to read the chosen port remains unchanged.
test/client-customHttp-xsdinclude.test.ts (1)

13-53: Consider extracting shared mock HttpClient.

This MyHttpClient implementation is nearly identical to the one in test/client-customHttp.test.ts. Both extend HttpClient with mock response capabilities.

You could extract a shared MockableHttpClient to test/test-helpers.ts to reduce duplication. However, since the pattern-matching logic differs slightly (exact match vs. includes), keeping them separate for test clarity is also reasonable.

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

In `@test/client-customHttp-xsdinclude.test.ts` around lines 13 - 53, The
MyHttpClient class duplicates mock HTTP logic found in another test; extract a
shared MockableHttpClient helper (e.g., in test/test-helpers.ts) that
encapsulates mockResponses, setMockResponse and request, but make the matching
strategy pluggable via a constructor option or protected match(url, pattern)
method so callers can choose includes vs exact; then update MyHttpClient in this
file to extend or reuse MockableHttpClient (overriding match if needed) and
import the helper in both tests to remove duplication.
plans/2026-04-16-bun-migration.md (1)

1262-1278: Add a language to this fenced block.

This trips markdownlint MD040. text is enough for the commit summary.

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

In `@plans/2026-04-16-bun-migration.md` around lines 1262 - 1278, The fenced code
block in the commit summary is missing a language tag which triggers
markdownlint MD040; update the opening fence from ``` to ```text in the block
that contains the list of commit lines (the diff block with the commit messages)
so the fenced block becomes ```text ... ``` to satisfy the linter.
specs/2026-04-16-bun-migration-design.md (1)

112-128: Add languages to the architecture diagram fences.

Both diagram fences are unlabeled, which is why markdownlint is reporting MD040 here. text would be sufficient.

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

In `@specs/2026-04-16-bun-migration-design.md` around lines 112 - 128, Add
language labels to the two Markdown code fences that show the architecture
diagrams (the "Before" and "After" blocks that start with "src/*.ts  --tsc--> 
lib/*.js + lib/*.d.ts" and "src/*.ts  --tsc-->  lib/*.js + lib/*.d.ts
(unchanged, published)"); change the opening triple-backtick lines to ```text
(or another appropriate language) for both fenced blocks so markdownlint MD040
is satisfied.
test/test-helpers.ts (2)

94-104: Consider using spread operator for simpler option merging.

The manual key iteration works but is more verbose than necessary.

♻️ Simplified implementation
 export function getTestOptions(baseDir: string, additionalOptions?: Record<string, unknown>): Record<string, unknown> {
-  const options: Record<string, unknown> = {
+  return {
     httpClient: createMockHttpClient(baseDir),
+    ...additionalOptions,
   };
-  if (additionalOptions) {
-    for (const key of Object.keys(additionalOptions)) {
-      options[key] = additionalOptions[key];
-    }
-  }
-  return options;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/test-helpers.ts` around lines 94 - 104, The getTestOptions function
manually copies keys from additionalOptions into options; simplify by using
object spread to merge createMockHttpClient(baseDir) with additionalOptions in
one expression. Replace the current options initialization and manual loop in
getTestOptions with a single merged object (using
...createMockHttpClient(baseDir) as the httpClient entry and
...additionalOptions) while preserving the httpClient produced by
createMockHttpClient(baseDir) and the function signature.

37-70: File is read twice when both callback and .then() are used.

The request method reads the file once for the callback (lines 37-48) and again inside the then() handler (lines 52-64). While test callers likely use one pattern or the other, this could cause inconsistencies if the file changes between reads, and doubles I/O unnecessarily.

Consider reading once and reusing the result:

♻️ Refactor to single file read with cached result
     request(rurl, data, callback, exheaders, exoptions) {
       if (!rurl.startsWith('http://test-files/') && !rurl.startsWith('https://test-files/')) {
         return _realClient.request(rurl, data, callback, exheaders, exoptions);
       }

       let filePath = rurl.replace(/^https?:\/\/test-files/, '');
       filePath = path.normalize(filePath);

+      let resolvedResponse: MockResponse | null = null;
+      let resolvedError: NodeJS.ErrnoException | null = null;
+      let resolvePromise: ((r: MockResponse) => void) | null = null;
+      let rejectPromise: ((e: unknown) => void) | null = null;
+
       fs.readFile(filePath, 'utf8', (err, content) => {
         if (err) {
-          return callback(err);
+          resolvedError = err;
+          callback(err);
+          if (rejectPromise) rejectPromise(err);
+          return;
         }
         const response: MockResponse = {
           status: 200,
           statusCode: 200,
           headers: { 'content-type': 'application/xml' },
           data: content,
         };
+        resolvedResponse = response;
         callback(null, response, content);
+        if (resolvePromise) resolvePromise(response);
       });

       return {
         then(resolve, reject) {
-          fs.readFile(filePath, 'utf8', (err, content) => {
-            if (err) {
-              if (reject) reject(err);
-              return;
-            }
-            const response: MockResponse = {
-              status: 200,
-              statusCode: 200,
-              headers: { 'content-type': 'application/xml' },
-              data: content,
-            };
-            if (resolve) resolve(response);
-          });
+          if (resolvedResponse) {
+            if (resolve) resolve(resolvedResponse);
+          } else if (resolvedError) {
+            if (reject) reject(resolvedError);
+          } else {
+            resolvePromise = resolve;
+            rejectPromise = reject;
+          }
           return this;
         },
-        catch(_reject) {
+        catch(reject) {
+          if (resolvedError && reject) {
+            reject(resolvedError);
+          } else if (!resolvedResponse) {
+            rejectPromise = reject;
+          }
           return this;
         },
       };
     },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/test-helpers.ts` around lines 37 - 70, The request helper currently
calls fs.readFile twice—once for the callback and again inside the returned
then()—so change it to perform a single fs.readFile call, cache its
error/content/response object, call the provided callback with those values, and
make the returned then(resolve,reject) use the cached result (resolving or
rejecting based on the cached error) so both callback and Promise-style
consumers receive the same response without duplicate I/O; update the code paths
around the existing fs.readFile callback and the returned then()/catch()
handlers to reuse the cached result (response, content, and error).
test/client.test.ts (1)

229-319: Server cleanup in skipped test could leak if test is unskipped.

In the skipped test "should send binary attachments using XOP + MTOM" (lines 254-319), the server is created inline within the test and closed at line 310. If this test is unskipped in the future, consider moving server lifecycle to before/after hooks like other describe blocks in this file for consistency and to ensure cleanup on test failure.

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

In `@test/client.test.ts` around lines 229 - 319, The skipped test "should send
binary attachments using XOP + MTOM" creates an HTTP server inline and closes it
only on the success path, which can leak if the test is unskipped or fails;
refactor by moving server lifecycle into the describe block's before/after hooks
(use the existing server variable) so the server is created in before() and
always closed in after() (also ensure any per-test overrides or errors call
server.close()), and update the test to only start the client call against the
already-running server rather than creating/closing the server inside the it
block.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@package.json`:
- Line 4: The packageManager field is pinned to "bun@1.3.11"; update the
packageManager value to "bun@1.3.12" in package.json to use the latest stable
Bun release (unless you have a specific compatibility requirement keeping
1.3.11), i.e., change the packageManager entry string to reference bun@1.3.12 so
package tooling will use the newer version.

In `@plans/2026-04-16-bun-migration.md`:
- Around line 1175-1183: Remove the stray fenced code delimiter after the
Toolchain example by deleting the extraneous "````" line that follows the bash
code block containing "bun install / bun test / bun run build" so the Markdown
closes properly and the rest of the document no longer renders as a code block.

In `@specs/2026-04-16-bun-migration-design.md`:
- Around line 93-99: Update the "D7. Sequence with PR `#9`" section to reflect the
actual migration path used (Option B) instead of the outdated "User-chosen
option A" flow: remove or replace the three-step unblock/mocha baseline
description and explicitly document that the PR used option B with a static it(
count because mocha never executed on master, and adjust any mentions of "PR `#9`
/ fetch-soap#34" to note that CI used the static it( baseline rather than a
runtime mocha baseline; also apply the same edit to the other occurrences
referenced around the same passage (the paragraphs corresponding to the content
noted as also applies to: 134-137).

In `@src/http.ts`:
- Around line 319-326: The NTLM early-return path currently does "return
Promise.reject(error)" and escapes the existing unhandled-rejection mitigation
because responsePromise.catch(...) only attaches to responsePromise; locate the
NTLM branch that returns Promise.reject(error) (the NTLM early-return) and
change it to create the rejected promise, attach the same no-op catch handler,
then return it (e.g. const rejected = Promise.reject(error); rejected.catch(()
=> {/* handled by callback */}); return rejected;), or alternatively route that
branch to set responsePromise and let the existing responsePromise.catch handle
it; reference responsePromise and the NTLM early-return/NTLM branch when making
the change.

In `@test/client-schema-does-not-change-on-request.test.ts`:
- Around line 69-77: The test starts an HTTP server in the beforeAll hook
(server = http.createServer(requestContext.requestHandler); server.listen(...))
but never closes it; add an afterAll hook that calls server.close() (and waits
for its callback or returns a Promise) to ensure the server is properly torn
down after the 'SOAP Client schema does not change' suite, preventing open
handles from leaking into other tests.

In `@test/header-rely-on-xml.test.ts`:
- Around line 9-12: Replace the hard-coded port and the premature done() call:
start the test server with port 0 (ephemeral), then read the actual assigned
port from the running server (e.g., server.address().port) and rebuild baseUrl
from that value instead of the fixed port variable; ensure teardown waits for
server.close() to complete (use the close callback, await a Promise wrapper
around server.close(), or return the server-close promise) before calling done()
or finishing the test. Update references to the variables port and baseUrl and
ensure all teardown code that calls server.close() waits for completion (affects
the setup/teardown around lines 19-33).
- Around line 73-78: In the test callback for registerUser, short-circuit on
errors by checking the err parameter and calling done(err) before performing
header assertions; update the callback passed to (client as any).registerUser to
immediately return done(err) when err is truthy so the test fails on SOAP call
errors instead of continuing to assert capturedHeaders, testHeaderKey and
testHeaderValue.

In `@test/request-response-samples.test.ts`:
- Around line 282-305: The test currently always calls done() in the .finally()
block which hides failures; update the promise chain so that done() is called
only when assertions succeed (inside the .then() success path after all checks)
and call done(err) from the .catch() handler to propagate errors. Specifically,
remove or stop using the unconditional .finally(done), move the done() call into
the .then callback after the request/response and attachment assertions, and
ensure the .catch callback calls done(err) (or wraps assertion failures and
passes them to done) so failures surface to the test runner; refer to the
existing .then, .catch, .finally handlers and the done variable when making the
change.
- Around line 280-281: The promisified call helper promiseCaller drops per-call
options causing tests to exercise different behavior than the callback path;
modify promiseCaller so it forwards the options argument to the client
invocation (same way cbCaller does) by calling client[methodName](requestJSON,
options) (and include any other expected trailing args like attachmentParts or a
callback/promise handling wrapper) so the promisified and callback paths receive
identical per-call options.

In `@test/test-helpers.ts`:
- Around line 24-26: createMockHttpClient currently assigns baseDir but never
uses it; update the file-path extraction logic inside createMockHttpClient so
that when the request URL yields a relative path you resolve it against baseDir
(defaulting to import.meta.dir) before reading the file, i.e., use baseDir to
join/resolve relative paths rather than relying solely on the URL-derived path;
keep the function signature and the _realClient usage unchanged.

In `@test/wsdl.test.ts`:
- Line 1: The import list from 'bun:test' includes an unused symbol expect;
remove expect from the named import so the line imports only describe, it, and
spyOn (i.e., change the import to exclude expect) and run tests/lint to confirm
no remaining references to expect in this file (wsdl.test.ts) before committing.
- Line 3: Remove the unused import of the 'path' symbol from the top of
test/wsdl.test.ts by deleting the line "import * as path from 'node:path';" (the
file already uses import.meta.dir for path construction), ensuring no other
references to the identifier path remain in the file; run the tests/linter to
confirm the unused-import warning is resolved.

---

Outside diff comments:
In `@test/client-schema-does-not-change-on-request.test.ts`:
- Around line 79-84: Add an ESM-compatible require binding and ensure the test
HTTP server is closed after tests: create a require function via
createRequire(import.meta.url) and use it for the JSON loads instead of bare
require() where requestJSON and responseJSON are created, and add an afterAll
hook that closes the server started in beforeAll (call server.close() and await
it via a Promise if needed) to avoid leaving the HTTP server running.

In `@test/response-encoding.test.ts`:
- Around line 23-34: The test currently pins the HTTP server to port 51515 in
beforeAll, causing EADDRINUSE flakes; change beforeAll's listen call to bind to
an ephemeral port (use listen(0, done) or omit the explicit port) so the OS
assigns an available port, keep using server.address() to build the endpoint,
and update afterAll to call server.close(done) (pass the done callback) to wait
for teardown completion; locate these changes in the beforeAll and afterAll
blocks around the server variable and server.close() invocation.

---

Nitpick comments:
In `@plans/2026-04-16-bun-migration.md`:
- Around line 1262-1278: The fenced code block in the commit summary is missing
a language tag which triggers markdownlint MD040; update the opening fence from
``` to ```text in the block that contains the list of commit lines (the diff
block with the commit messages) so the fenced block becomes ```text ... ``` to
satisfy the linter.

In `@specs/2026-04-16-bun-migration-design.md`:
- Around line 112-128: Add language labels to the two Markdown code fences that
show the architecture diagrams (the "Before" and "After" blocks that start with
"src/*.ts  --tsc-->  lib/*.js + lib/*.d.ts" and "src/*.ts  --tsc-->  lib/*.js +
lib/*.d.ts (unchanged, published)"); change the opening triple-backtick lines to
```text (or another appropriate language) for both fenced blocks so markdownlint
MD040 is satisfied.

In `@test/client-customHttp-xsdinclude.test.ts`:
- Around line 13-53: The MyHttpClient class duplicates mock HTTP logic found in
another test; extract a shared MockableHttpClient helper (e.g., in
test/test-helpers.ts) that encapsulates mockResponses, setMockResponse and
request, but make the matching strategy pluggable via a constructor option or
protected match(url, pattern) method so callers can choose includes vs exact;
then update MyHttpClient in this file to extend or reuse MockableHttpClient
(overriding match if needed) and import the helper in both tests to remove
duplication.

In `@test/client.test.ts`:
- Around line 229-319: The skipped test "should send binary attachments using
XOP + MTOM" creates an HTTP server inline and closes it only on the success
path, which can leak if the test is unskipped or fails; refactor by moving
server lifecycle into the describe block's before/after hooks (use the existing
server variable) so the server is created in before() and always closed in
after() (also ensure any per-test overrides or errors call server.close()), and
update the test to only start the client call against the already-running server
rather than creating/closing the server inside the it block.

In `@test/response-preserve-whitespace.test.ts`:
- Line 28: The test currently hardcodes the port in the server.listen call
(".listen(51515, done)"), which can cause EADDRINUSE in parallel runs; change it
to use a dynamic port (either pass 0 to let the OS pick: ".listen(0, done)" or
use the new helper: ".listen(testHelpers.nextTestPort(), done)") and ensure the
existing use of "server.address().port" to read the chosen port remains
unchanged.

In `@test/security/BasicAuthSecurity.test.ts`:
- Around line 17-22: The test currently only checks that
BasicAuthSecurity.addHeaders added an Authorization key; change it to validate
the actual header value by constructing the expected string from username and
password and asserting equality. Specifically, after calling
security.addHeaders(headers), compute expectedAuth = "Basic " +
Buffer.from(`${username}:${password}`).toString('base64') (or the
environment-equivalent base64 method) and replace the toHaveProperty assertion
with expect(headers['Authorization']).toBe(expectedAuth); keep using the same
BasicAuthSecurity, addHeaders, username, password and headers symbols so the
intent and placement are clear.

In `@test/security/BearerSecurity.test.ts`:
- Line 24: The test title contains a typo and wrong function name: change the
it(...) description from "should return the authoriation header on calling
addHeader" to a clearer, correct string such as "should return the authorization
header when calling addHeaders" so it references the correct function name
addHeaders and fixes the misspelling of authorization; update the title in the
test containing the it call in BearerSecurity.test.ts accordingly.

In `@test/test-helpers.ts`:
- Around line 94-104: The getTestOptions function manually copies keys from
additionalOptions into options; simplify by using object spread to merge
createMockHttpClient(baseDir) with additionalOptions in one expression. Replace
the current options initialization and manual loop in getTestOptions with a
single merged object (using ...createMockHttpClient(baseDir) as the httpClient
entry and ...additionalOptions) while preserving the httpClient produced by
createMockHttpClient(baseDir) and the function signature.
- Around line 37-70: The request helper currently calls fs.readFile twice—once
for the callback and again inside the returned then()—so change it to perform a
single fs.readFile call, cache its error/content/response object, call the
provided callback with those values, and make the returned then(resolve,reject)
use the cached result (resolving or rejecting based on the cached error) so both
callback and Promise-style consumers receive the same response without duplicate
I/O; update the code paths around the existing fs.readFile callback and the
returned then()/catch() handlers to reuse the cached result (response, content,
and error).
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1ea1b86c-9900-44c0-814e-11c34a6098b0

📥 Commits

Reviewing files that changed from the base of the PR and between 79b58e2 and a905ee7.

⛔ Files ignored due to path filters (2)
  • bun.lock is excluded by !**/*.lock
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (36)
  • .github/dependabot.yml
  • .github/workflows/pr.yml
  • CONTRIBUTING.md
  • package.json
  • plans/2026-04-16-bun-migration.md
  • specs/2026-04-16-bun-migration-design.md
  • src/http.ts
  • src/wsdl/elements.ts
  • test/_socketStream.js
  • test/client-customHttp-xsdinclude.test.ts
  • test/client-customHttp.test.ts
  • test/client-options-wsdlcache-test.js
  • test/client-options-wsdlcache.test.ts
  • test/client-options.test.ts
  • test/client-schema-does-not-change-on-request.test.ts
  • test/client.test.ts
  • test/header-rely-on-xml.test.ts
  • test/mocha.opts
  • test/request-response-samples-test.js
  • test/request-response-samples.test.ts
  • test/response-encoding.test.ts
  • test/response-preserve-whitespace.test.ts
  • test/security/BasicAuthSecurity.js
  • test/security/BasicAuthSecurity.test.ts
  • test/security/BearerSecurity.js
  • test/security/BearerSecurity.test.ts
  • test/security/PasswordDigest.js
  • test/security/PasswordDigest.test.ts
  • test/security/WSSecurity.js
  • test/security/WSSecurity.test.ts
  • test/test-helpers.js
  • test/test-helpers.ts
  • test/trim-test.js
  • test/trim.test.ts
  • test/wsdl-parse.test.ts
  • test/wsdl.test.ts
💤 Files with no reviewable changes (10)
  • test/mocha.opts
  • test/security/BasicAuthSecurity.js
  • test/security/PasswordDigest.js
  • test/trim-test.js
  • test/security/BearerSecurity.js
  • test/_socketStream.js
  • test/request-response-samples-test.js
  • test/security/WSSecurity.js
  • test/test-helpers.js
  • test/client-options-wsdlcache-test.js

Comment thread package.json
Comment thread plans/2026-04-16-bun-migration.md
Comment thread specs/2026-04-16-bun-migration-design.md Outdated
Comment thread src/http.ts
Comment thread test/client-schema-does-not-change-on-request.test.ts
Comment thread test/request-response-samples.test.ts Outdated
Comment thread test/request-response-samples.test.ts
Comment thread test/test-helpers.ts
Comment thread test/wsdl.test.ts Outdated
Comment thread test/wsdl.test.ts Outdated
- src/http.ts: attach .catch to NTLM early-return rejection (same
  pattern as main responsePromise, prevents unhandled rejection on
  callback-only callers)
- test/test-helpers.ts: drop useless baseDir reassignment (parameter
  retained for API compat; toTestUrl embeds the absolute path)
- test/wsdl.test.ts: remove unused expect + path imports; replace
  'undefined as string' cast with explicit 'undefined' string key
- test/header-rely-on-xml.test.ts: replace hardcoded port with
  testHelpers.nextTestPort(); wait on server.close() in afterAll;
  document intentional err-ignore (mock response schema mismatch
  is orthogonal to the request-side assertion under test)
- test/client-schema-does-not-change-on-request.test.ts: add
  afterAll to close the HTTP server (was leaking)
- test/request-response-samples.test.ts: forward options through
  promiseCaller to match cbCaller; propagate assertion failures
  to done(err) instead of swallowing in .finally
- eslint.config.mjs: ignore docs/ (typedoc output, gitignored)
- specs/2026-04-16-bun-migration-design.md: update D7 to reflect
  realized option B (we skipped the unblock; PR #44 fixed #43,
  then this Bun PR superseded the mocha-based test job)

Not addressed (declined):
- CodeRabbit suggested bumping bun@1.3.11 → 1.3.12. Tests ran
  against 1.3.11; Dependabot will propose a bump through the
  normal path once the 1.3.12 schema is validated.
- CodeRabbit flagged a stray fence at plans/...:1183. That's
  inside a markdown doc describing how to write CONTRIBUTING
  instructions; the nested fences are intentional (the inner
  block is the target file's content).
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: 2

♻️ Duplicate comments (1)
specs/2026-04-16-bun-migration-design.md (1)

95-102: ⚠️ Potential issue | 🟡 Minor

Baseline strategy is still contradictory across sections.

D7 correctly says there is no green mocha runtime baseline, but Verification still requires one from an unblock branch. Keep the static per-file it( baseline consistently in both places.

Suggested doc fix
-1. **`bun test` passes locally.** Test count pre- and post-migration must match (catches silent skips). Mocha test count captured from a one-off run of the small unblock branch.
+1. **`bun test` passes locally.** Test count pre- and post-migration must match (catches silent skips). Since mocha never ran green on master, use static per-file `it(` counts as the floor baseline.

Also applies to: 138-139

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

In `@specs/2026-04-16-bun-migration-design.md` around lines 95 - 102, Update the
doc so the baseline strategy is consistent: change the Verification section (and
the other occurrence around lines 138-139) to remove any requirement for a green
mocha runtime baseline and instead require the static per-file "it(" counts as
the floor check referenced in D7; mention D7 and the "it(" floor check
explicitly so readers know to use the static per-file it( counts rather than an
unblock branch/mocha baseline.
🧹 Nitpick comments (1)
specs/2026-04-16-bun-migration-design.md (1)

116-121: Add languages to fenced code blocks for markdownlint compatibility.

Use a fence language (e.g., text) for the architecture diagrams to satisfy MD040.

Also applies to: 125-130

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

In `@specs/2026-04-16-bun-migration-design.md` around lines 116 - 121, The fenced
architecture diagrams use plain ``` fences without a language token which
triggers markdownlint MD040; update each fenced code block (e.g., the blocks
shown around the diagram text like the snippet starting with ``` and the later
block at lines referenced in the comment) to include a fence language such as
"text" (i.e., change ``` to ```text) so all diagram/code fences declare a
language and satisfy MD040; apply the same change to the other diagram blocks
noted (lines 125-130).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@specs/2026-04-16-bun-migration-design.md`:
- Around line 82-87: Update the documented CI flow to match the implemented
workflow by replacing the run step that uses "bun install --frozen-lockfile"
with a "bun ci" step; keep the existing "uses: oven-sh/setup-bun@v2" and its
"bun-version" pin (matching package.json's packageManager field) and ensure the
subsequent test step remains "bun test" so the sequence is setup → bun ci → bun
test.

In `@test/test-helpers.ts`:
- Around line 14-22: The mock MockHttpClient.request currently returns a custom
thenable; change it to return a real Promise that resolves with a MockResponse
and supports reject via Promise.reject so callers can use .then/.catch semantics
correctly. Update the MockHttpClient.request implementation (and any helpers
that produce MockResponse) to read files once and resolve or reject the Promise
accordingly instead of duplicating reads, and ensure the returned Promise calls
the provided RequestCallback on success/failure to preserve the original
callback contract.

---

Duplicate comments:
In `@specs/2026-04-16-bun-migration-design.md`:
- Around line 95-102: Update the doc so the baseline strategy is consistent:
change the Verification section (and the other occurrence around lines 138-139)
to remove any requirement for a green mocha runtime baseline and instead require
the static per-file "it(" counts as the floor check referenced in D7; mention D7
and the "it(" floor check explicitly so readers know to use the static per-file
it( counts rather than an unblock branch/mocha baseline.

---

Nitpick comments:
In `@specs/2026-04-16-bun-migration-design.md`:
- Around line 116-121: The fenced architecture diagrams use plain ``` fences
without a language token which triggers markdownlint MD040; update each fenced
code block (e.g., the blocks shown around the diagram text like the snippet
starting with ``` and the later block at lines referenced in the comment) to
include a fence language such as "text" (i.e., change ``` to ```text) so all
diagram/code fences declare a language and satisfy MD040; apply the same change
to the other diagram blocks noted (lines 125-130).
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 69b26272-b5fe-48b1-a01a-bb39c35a8998

📥 Commits

Reviewing files that changed from the base of the PR and between db20bd6 and b074cdb.

📒 Files selected for processing (8)
  • eslint.config.mjs
  • specs/2026-04-16-bun-migration-design.md
  • src/http.ts
  • test/client-schema-does-not-change-on-request.test.ts
  • test/header-rely-on-xml.test.ts
  • test/request-response-samples.test.ts
  • test/test-helpers.ts
  • test/wsdl.test.ts
✅ Files skipped from review due to trivial changes (2)
  • eslint.config.mjs
  • test/request-response-samples.test.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • test/header-rely-on-xml.test.ts
  • src/http.ts
  • test/wsdl.test.ts
  • test/client-schema-does-not-change-on-request.test.ts

Comment thread specs/2026-04-16-bun-migration-design.md
Comment thread test/test-helpers.ts
- specs/2026-04-16-bun-migration-design.md (D6): swap `bun install
  --frozen-lockfile` for `bun ci` to match the implemented workflow.
- test/test-helpers.ts: return a real Promise from the mock request
  instead of a custom thenable. The previous implementation called
  fs.readFile twice (once for the callback path, once inside then())
  and the catch handler was a no-op — meaning promise consumers
  couldn't observe errors. Single read now feeds both paths, matching
  the real HttpClient contract. No-op .catch on responsePromise
  preserves the "callback is the primary error channel" invariant.
@evans-sam evans-sam merged commit 54afe17 into master Apr 17, 2026
4 of 5 checks passed
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