Skip to content

Add tests for the Bunny CDN domain API#1879

Closed
stefan-burke wants to merge 1 commit into
mainfrom
work/bunny-domain-tests
Closed

Add tests for the Bunny CDN domain API#1879
stefan-burke wants to merge 1 commit into
mainfrom
work/bunny-domain-tests

Conversation

@stefan-burke

@stefan-burke stefan-burke commented Jul 21, 2026

Copy link
Copy Markdown
Member

Adds test/shared/bunny-cdn/domain.test.ts, covering the existing bunnyCdnApi domain contract on main end-to-end:

  • Loading a DNS zone with the configured credential, and labelling provider failures.
  • Building and checking the full subdomain name (buildSubdomainRecordName), including the early-out when the zone lookup itself fails.
  • Registering a custom hostname with forced SSL, tolerating an already-registered hostname, and reporting pull-zone, hostname, certificate, and forced-SSL failures.
  • Registering a CNAME with the exact DNS payload, rejecting a taken subdomain before any provider write, reporting CDN target and DNS write failures, retrying certificate setup with bounded backoff, and cleaning up the DNS record on failure (only when the record id is known).
  • Deleting a DNS record with the exact provider request, and labelling a delete failure.

Each test stubs fetch and the surrounding bunnyCdnApi methods, then asserts the exact URL, method, body, and headers sent to Bunny, plus the labelled error result the caller sees. This locks the provider request shape and the error labelling in place.

The tested APIs already exist on main; this PR adds no production code. It also adds a tiny restoreStubsAfterEach helper to test/test-utils/mocks.ts so each test in the suite can collect its stubs in an array and have them restored in one place — the test uses it.

Scope

  • New: test/shared/bunny-cdn/domain.test.ts (352 lines).
  • Helper: restoreStubsAfterEach in test/test-utils/mocks.ts (6 lines).
  • No production code, existing tests, config, docs, lockfiles, or mutation metadata touched.

Verification

  • nix develop -c deno task test:files test/shared/bunny-cdn/domain.test.ts — 19 tests pass.
  • nix develop -c deno task precommit — passes.

Summary by CodeRabbit

  • Tests
    • Added comprehensive coverage for Bunny CDN DNS, subdomain, custom domain, SSL, and DNS record workflows.
    • Added validation for error handling, request details, retries, cleanup behavior, and already-registered domains.
    • Improved test isolation by automatically restoring and clearing stubs after each test.

Cover the existing bunnyCdnApi domain contract: DNS zone loading, subdomain
availability, custom hostname setup with forced SSL, CNAME registration with
certificate retry and cleanup, and DNS record deletion. Adds a tiny
restoreStubsAfterEach test helper so each test restores its stubs cleanly.
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

The PR adds BDD coverage for Bunny CDN DNS and domain operations, including request wiring, availability checks, custom-domain validation, subdomain registration, certificate retries, DNS cleanup, deletion, and labeled failures. It also adds a helper that restores registered stubs after each test.

Bunny CDN domain tests

Layer / File(s) Summary
Test harness and API behavior
test/shared/bunny-cdn/domain.test.ts, test/test-utils/mocks.ts
Adds shared Bunny API stubs, environment setup, DNS and subdomain availability tests, and automatic per-test stub restoration.
Domain validation and registration
test/shared/bunny-cdn/domain.test.ts
Tests custom-domain provider request sequences, already-registered handling, subdomain availability checks, CNAME payloads, and labeled failures.
Retry and DNS record cleanup
test/shared/bunny-cdn/domain.test.ts
Tests bounded certificate retries, conditional DNS cleanup, successful record deletion, and deletion errors.

Estimated code review effort: 3 (Moderate) | ~20 minutes

🚥 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 clearly matches the main change: adding tests for the Bunny CDN domain API.
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.
✨ 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 work/bunny-domain-tests

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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/shared/bunny-cdn/domain.test.ts`:
- Around line 88-98: Extract reusable stub helpers for bunnyCdnApi.getDnsZone
and bunnyCdnApi.validateCustomDomain, following the existing stubPullZoneId and
stubAvailableSubdomain pattern. Replace each duplicated inline stub setup in the
affected tests with these helpers while preserving the existing response data
and test behavior.
- Around line 183-218: Update the failure-result assertion in the parameterized
“reports a setup failure” test to use toEqual with the expected { error, ok }
result shape instead of toMatchObject, matching the established assertion
pattern nearby.
🪄 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: ASSERTIVE

Plan: Pro Plus

Run ID: 05f8dc0b-1dc3-44cd-82ca-f235783815f8

📥 Commits

Reviewing files that changed from the base of the PR and between 7563691 and 2400c70.

📒 Files selected for processing (2)
  • test/shared/bunny-cdn/domain.test.ts
  • test/test-utils/mocks.ts

Comment on lines +88 to +98
test("builds and checks the full subdomain name", async () => {
stubs.push(
stub(bunnyCdnApi, "getDnsZone", () =>
Promise.resolve({
ok: true,
zone: zone([
{ Id: 1, Name: "used.tickets", Type: 2, Value: "target" },
]),
}),
),
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract shared stub helpers for getDnsZone/validateCustomDomain to match existing pattern.

The file already extracts stubPullZoneId/stubAvailableSubdomain for repeated stub(bunnyCdnApi, ..., () => Promise.resolve(...)) setups, but the same pattern for getDnsZone (lines 90-98, 115-118) and validateCustomDomain (lines 223-225, 320-322) is duplicated inline instead of reusing a similar helper.

♻️ Proposed helper additions
+const stubDnsZone = (
+  stubs: Restorable[],
+  result: { ok: true; zone: ReturnType<typeof zone> } | { error: string; ok: false },
+): void => {
+  stubs.push(stub(bunnyCdnApi, "getDnsZone", () => Promise.resolve(result)));
+};
+
+const stubValidateCustomDomain = (
+  stubs: Restorable[],
+  result: { ok: true } | { error: string; ok: false } = { ok: true },
+): void => {
+  stubs.push(stub(bunnyCdnApi, "validateCustomDomain", () => Promise.resolve(result)));
+};

As per coding guidelines, **/*.{ts,tsx} files should "Eliminate code duplication with shared helpers or currying; do not evade jscpd with structural changes or jscpd:ignore, except for import blocks or unavoidable boilerplate."

Also applies to: 113-123, 220-226, 320-322

🤖 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/shared/bunny-cdn/domain.test.ts` around lines 88 - 98, Extract reusable
stub helpers for bunnyCdnApi.getDnsZone and bunnyCdnApi.validateCustomDomain,
following the existing stubPullZoneId and stubAvailableSubdomain pattern.
Replace each duplicated inline stub setup in the affected tests with these
helpers while preserving the existing response data and test behavior.

Source: Coding guidelines

Comment on lines +183 to +218
for (const [label, responses, expected] of [
[
"hostname",
[new Response("add failed", { status: 500 })],
"Add hostname failed (500): add failed",
],
[
"certificate",
[
new Response(null, { status: 204 }),
new Response("cert failed", { status: 500 }),
],
"Load free certificate failed (500): cert failed",
],
[
"forced SSL",
[
new Response(null, { status: 204 }),
new Response(null, { status: 204 }),
new Response("ssl failed", { status: 500 }),
],
"Set force SSL failed (500): ssl failed",
],
] as const) {
test(`reports a ${label} setup failure`, async () => {
stubPullZoneId(stubs);
using _fetch = stubFetch(responses[0], ...responses.slice(1));

const result = await bunnyCdnApi.validateCustomDomain(
"custom.example.test",
);

expect(result).toMatchObject({ error: expected, ok: false });
expect(errors.contains(expected)).toBe(true);
});
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Prefer toEqual over toMatchObject for the failure-result assertion.

Line 215 uses toMatchObject({ error: expected, ok: false }), which passes even if the result contains extra unexpected fields. The equivalent check at line 179 for the same result shape uses toEqual, so this is a weaker, less mutation-resistant assertion than the established pattern in this file.

🐛 Proposed fix
-      expect(result).toMatchObject({ error: expected, ok: false });
+      expect(result).toEqual({ error: expected, ok: false });

As per coding guidelines, test/**/*.ts files should "use strong mutation-resistant assertions."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for (const [label, responses, expected] of [
[
"hostname",
[new Response("add failed", { status: 500 })],
"Add hostname failed (500): add failed",
],
[
"certificate",
[
new Response(null, { status: 204 }),
new Response("cert failed", { status: 500 }),
],
"Load free certificate failed (500): cert failed",
],
[
"forced SSL",
[
new Response(null, { status: 204 }),
new Response(null, { status: 204 }),
new Response("ssl failed", { status: 500 }),
],
"Set force SSL failed (500): ssl failed",
],
] as const) {
test(`reports a ${label} setup failure`, async () => {
stubPullZoneId(stubs);
using _fetch = stubFetch(responses[0], ...responses.slice(1));
const result = await bunnyCdnApi.validateCustomDomain(
"custom.example.test",
);
expect(result).toMatchObject({ error: expected, ok: false });
expect(errors.contains(expected)).toBe(true);
});
}
for (const [label, responses, expected] of [
[
"hostname",
[new Response("add failed", { status: 500 })],
"Add hostname failed (500): add failed",
],
[
"certificate",
[
new Response(null, { status: 204 }),
new Response("cert failed", { status: 500 }),
],
"Load free certificate failed (500): cert failed",
],
[
"forced SSL",
[
new Response(null, { status: 204 }),
new Response(null, { status: 204 }),
new Response("ssl failed", { status: 500 }),
],
"Set force SSL failed (500): ssl failed",
],
] as const) {
test(`reports a ${label} setup failure`, async () => {
stubPullZoneId(stubs);
using _fetch = stubFetch(responses[0], ...responses.slice(1));
const result = await bunnyCdnApi.validateCustomDomain(
"custom.example.test",
);
expect(result).toEqual({ error: expected, ok: false });
expect(errors.contains(expected)).toBe(true);
});
}
🤖 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/shared/bunny-cdn/domain.test.ts` around lines 183 - 218, Update the
failure-result assertion in the parameterized “reports a setup failure” test to
use toEqual with the expected { error, ok } result shape instead of
toMatchObject, matching the established assertion pattern nearby.

Source: Coding guidelines

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2400c700fb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


test("continues when the custom hostname is already registered", async () => {
stubPullZoneId(stubs);
using _fetch = stubCustomDomainRequests(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Assert the already-registered path still finishes setup

Because this fetch stub handle is discarded, the test only checks { ok: true }; if validateCustomDomain started treating pullzone.hostname_already_registered as a terminal success and skipped the certificate and forced-SSL requests, this test would still pass because the queued responses do not have to be consumed. Keep the stub handle and assert the three provider calls so the "continues" behavior is actually locked in.

Useful? React with 👍 / 👎.

);
using _fetch = stubFetch(new Response("not json"));

await bunnyCdnApi.registerBunnySubdomain("child");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Assert the unknown-id path returns the CDN failure

Since the result is ignored here, this test still passes if registerBunnySubdomain starts returning success, or skips validateCustomDomain, whenever Bunny's DNS response has no parseable record id; deleted would remain empty either way. Assert the returned { error: "certificate pending", ok: false } before checking no cleanup so this case stays tied to the failing certificate path.

Useful? React with 👍 / 👎.

first = noContent(),
): ReturnType<typeof stubFetch> => stubFetch(first, noContent(), noContent());

describeWithEnv("Bunny domain API", { env: ENV }, () => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Deduplicate the parallel Bunny domain test suite

This new suite largely retests the same Bunny domain behavior already covered in test/lib/bunny-cdn/domain.test.ts (for example the DNS-zone, availability, custom-hostname, CNAME, retry, and cleanup paths), so every future domain change now has to keep two parallel test files in sync instead of one shared set of cases. Please move or refine the existing coverage under the mirrored test/shared/bunny-cdn/ location rather than adding a second suite beside it.

Useful? React with 👍 / 👎.

Comment thread test/test-utils/mocks.ts

export const restoreStubsAfterEach = (stubs: { restore(): void }[]): void => {
afterEach(() => {
for (const active of stubs.splice(0)) active.restore();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Restore all queued stubs before surfacing cleanup errors

Because splice(0) empties the tracking array before any restore runs, a single restore() failure (for example from a stub that was already manually restored) aborts the loop and leaves the later stubs installed with no way for a later afterEach to find them. That can make following tests run under stale API replacements, so attempt every queued restore before rethrowing or remove entries only after each restore succeeds.

Useful? React with 👍 / 👎.

@stefan-burke

Copy link
Copy Markdown
Member Author

Closing as superseded: #1862 merged the same domain test and helper into main while this extraction was under review. No separate merge is needed.

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.

1 participant