Skip to content

Iter 12: High-value API coverage gaps#16

Merged
ractive merged 4 commits into
mainfrom
iter-12/api-coverage-gaps
Mar 20, 2026
Merged

Iter 12: High-value API coverage gaps#16
ractive merged 4 commits into
mainfrom
iter-12/api-coverage-gaps

Conversation

@ractive

@ractive ractive commented Mar 20, 2026

Copy link
Copy Markdown
Owner

Summary

  • URL Purge: hoppy purge --url <url> — purge individual URLs from CDN cache (POST /purge)
  • Pull Zone Hostnames/SSL: add/remove custom hostnames, load free Let's Encrypt certs, force SSL, add/remove custom certificates (6 new API methods, 6 CLI subcommands under hoppy pull-zone hostname)
  • DNS Export/Import: hoppy dns zone export outputs BIND zone file to stdout; hoppy dns zone import reads from file or stdin (GET /dnszone/{id}/export, POST /dnszone/{id}/import)
  • Stream Captions: hoppy stream video caption add/delete — manage SRT caption tracks on videos
  • Shield Metrics: hoppy shield metrics overview — security metrics overview for Shield Zones

Adds 16 new API client methods, 16 new CLI commands, 29 test fixtures, and 30+ new wiremock/E2E tests (428 total tests passing).

Test plan

  • cargo test --workspace — 428 tests passing
  • cargo clippy --workspace --tests -- -D warnings — clean
  • cargo fmt --all — clean
  • BUNNY_API_KEY=xxx cargo test --features live-api -- --test-threads=1 live_ — run live E2E tests against real API

🤖 Generated with Claude Code

ractive and others added 2 commits March 20, 2026 09:10
…d metrics

Close the highest-value API coverage gaps between hoppy and bunny.net:

- URL Purge: `hoppy purge --url <url>` (POST /purge)
- Pull Zone Hostnames: add/remove hostname, load free cert, force SSL,
  add/remove custom certificate (6 new API methods, 6 CLI commands)
- DNS Export/Import: export zone as BIND file, import from file/stdin
- Stream Captions: add/delete SRT caption tracks on videos
- Shield Metrics: overview metrics for Shield Zones

Adds 16 new API client methods, 16 new CLI commands, 29 fixtures,
and 30+ new wiremock/E2E tests (428 total tests passing).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add iteration plan files for iterations 0-11, reorganize test plan
into testing/ subfolder, add release checklist and bug tracker docs.
Update CLAUDE.md with expanded documentation instructions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Copilot AI 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.

Pull request overview

This PR closes several “high-value” bunny.net API coverage gaps by adding new Core/Stream/Shield client methods and wiring corresponding CLI subcommands, with accompanying wiremock-based CLI E2E tests and snapshots.

Changes:

  • Add hoppy purge --url <url> and Core client support for POST /purge.
  • Add pull zone hostname/SSL management subcommands and Core client endpoints.
  • Add DNS zone export/import, stream caption add/delete, and Shield metrics overview (client + CLI + tests/snapshots).

Reviewed changes

Copilot reviewed 82 out of 83 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
tests/snapshots/cli_stream__stream_video_caption_add_json.snap Adds snapshot for stream caption add JSON output.
tests/snapshots/cli_shield__shield_metrics_overview_table.snap Adds snapshot for shield metrics overview table output.
tests/snapshots/cli_shield__shield_metrics_overview_json.snap Adds snapshot for shield metrics overview JSON output.
tests/cli_stream.rs Adds CLI E2E tests for stream caption add/delete.
tests/cli_shield.rs Adds CLI E2E tests for shield metrics overview (json/table).
tests/cli_pull_zone.rs Adds CLI E2E tests for URL purge and pull-zone hostname/SSL operations.
tests/cli_dns.rs Adds CLI E2E tests for DNS export/import.
src/main.rs Wires new top-level purge command into the command dispatcher.
src/commands/stream.rs Adds stream video caption add/delete handling.
src/commands/shield.rs Adds shield metrics overview handling and table output mapping.
src/commands/purge.rs Implements the purge command handler.
src/commands/pull_zone.rs Adds pull-zone hostname ... handler wiring.
src/commands/mod.rs Exposes new purge command module.
src/commands/dns.rs Adds dns zone export/import handlers (stdout export, stdin/file import).
src/cli.rs Adds clap subcommands/flags for purge, pull-zone hostname, dns import/export, stream captions, shield metrics.
hoppy-knowledgebase/testing/test-plan-v0.1.0.md Adds “Related” links section.
hoppy-knowledgebase/testing/test-environment-needed.md New doc: requirements for a safe live-test environment.
hoppy-knowledgebase/testing/e2e-test-report.md New generated E2E test report document.
hoppy-knowledgebase/research/rust-code-review-skill-research.md Adds “Related” links section.
hoppy-knowledgebase/research/rust-cli-best-practices.md Adds “Related” links section.
hoppy-knowledgebase/research/release-engineering-research.md Adds “Related” links section.
hoppy-knowledgebase/research/openapi-codegen-rust.md Adds “Related” links section.
hoppy-knowledgebase/research/hand-written-experiment-results.md Adds “Related” links section.
hoppy-knowledgebase/research/cli-e2e-testing-research.md Adds “Related” links section.
hoppy-knowledgebase/research/cli-design-patterns.md Adds “Related” links section.
hoppy-knowledgebase/release/release-setup-checklist.md New release setup checklist doc.
hoppy-knowledgebase/iterations/rust-e2e-rewrite-plan.md New detailed plan doc for Rust-based E2E tests rewrite.
hoppy-knowledgebase/iterations/iteration-9-gap-analysis.md New iteration doc (gap analysis).
hoppy-knowledgebase/iterations/iteration-8-release.md New iteration doc (release readiness).
hoppy-knowledgebase/iterations/iteration-7-cleanup.md New iteration doc (cleanup).
hoppy-knowledgebase/iterations/iteration-6-scripting-containers.md New iteration doc (scripting + containers).
hoppy-knowledgebase/iterations/iteration-5-shield.md New iteration doc (Shield).
hoppy-knowledgebase/iterations/iteration-4-stream.md New iteration doc (Stream).
hoppy-knowledgebase/iterations/iteration-3-dns.md New iteration doc (DNS).
hoppy-knowledgebase/iterations/iteration-2-storage.md New iteration doc (Storage).
hoppy-knowledgebase/iterations/iteration-12-api-coverage.md New iteration doc describing the PR scope and deliverables.
hoppy-knowledgebase/iterations/iteration-11-e2e-lifecycle.md New iteration doc (live lifecycle tests).
hoppy-knowledgebase/iterations/iteration-10-e2e-test-harness.md New iteration doc (mock-based harness).
hoppy-knowledgebase/iterations/iteration-1-pull-zones.md New iteration doc (pull zones).
hoppy-knowledgebase/iterations/iteration-1-code-review.md New iteration doc capturing code review findings.
hoppy-knowledgebase/iterations/iteration-0.5-codegen-experiment.md New iteration doc (codegen experiment).
hoppy-knowledgebase/iterations/iteration-0-skeleton.md New iteration doc (project skeleton).
hoppy-knowledgebase/iterations/e2e-test-harness-plan.md New E2E harness plan doc.
hoppy-knowledgebase/development-roadmap.md Updates roadmap to link iteration docs and include iteration 12.
hoppy-knowledgebase/decision-log.md Adds API-specific decisions section and additional entries.
hoppy-knowledgebase/api/magic-containers/magic-containers-applications-api.md Adds “Related” links section.
hoppy-knowledgebase/api/bunny-stream-api-research.md Adds “Related” links section.
hoppy-knowledgebase/api/bunny-api-quirks.md Adds “Related” links section.
hoppy-knowledgebase/api/bunny-api-overview.md Adds “Related” links section.
hoppy-knowledgebase/api/bunny-api-client-patterns.md Adds “Related” links section.
hoppy-knowledgebase/adding-a-feature.md Adds “Related” links section.
hoppy-knowledgebase/Seed.md Adds frontmatter + “Related” links section and fixes formatting.
hoppy-knowledgebase/Bugs.md New bug doc capturing a containers deserialization issue.
fixtures/stream/video_caption_delete.json Adds fixture for stream caption delete response.
fixtures/stream/video_caption_add.json Adds fixture for stream caption add response.
fixtures/shield/metrics_waf_rule.json Adds fixture for Shield WAF rule metrics.
fixtures/shield/metrics_rate_limits.json Adds fixture for Shield rate limit metrics.
fixtures/shield/metrics_overview_detailed.json Adds fixture for Shield overview metrics (detailed).
fixtures/shield/metrics_overview.json Adds fixture for Shield metrics overview.
fixtures/shield/metrics_bot_detection.json Adds fixture for Shield bot detection metrics.
fixtures/core/purge_url.json Adds fixture/documentation stub for URL purge response semantics.
fixtures/core/pullzone_setforcessl.json Adds fixture comment for empty-body success for force SSL endpoint.
fixtures/core/pullzone_removehostname.json Adds fixture for pull zone remove-hostname response.
fixtures/core/pullzone_removecertificate.json Adds fixture comment for empty-body success for remove-certificate endpoint.
fixtures/core/pullzone_loadfreecertificate.json Adds fixture comment for empty-body success for load-free-certificate endpoint.
fixtures/core/pullzone_addhostname.json Adds fixture for pull zone add-hostname response.
fixtures/core/pullzone_addcertificate.json Adds fixture comment for empty-body success for add-certificate endpoint.
fixtures/core/dnszone_import.json Adds fixture for DNS zone import result.
fixtures/core/dnszone_export.txt Adds fixture for DNS zone export BIND file.
crates/bunny-api-stream/tests/stream_api.rs Adds Stream client wiremock tests for caption add/delete.
crates/bunny-api-stream/src/client.rs Adds Stream client methods for caption add/delete.
crates/bunny-api-shield/tests/shield_api.rs Adds Shield client wiremock test for metrics overview.
crates/bunny-api-shield/src/types.rs Adds Shield metrics response types.
crates/bunny-api-shield/src/lib.rs Re-exports Shield metrics types.
crates/bunny-api-shield/src/client.rs Adds Shield client method for metrics overview.
crates/bunny-api-core/tests/pullzone_api.rs Adds Core client wiremock tests for purge URL and hostname/SSL endpoints.
crates/bunny-api-core/tests/dns_api.rs Adds Core client wiremock tests for DNS export/import.
crates/bunny-api-core/src/types.rs Adds DnsImportResult type.
crates/bunny-api-core/src/lib.rs Re-exports DnsImportResult.
crates/bunny-api-core/src/client.rs Adds Core client methods for purge URL, pull-zone hostname/SSL, and DNS export/import.
Cargo.toml Adds tempfile dev-dependency for tests.
Cargo.lock Locks tempfile dependency addition.
CLAUDE.md Updates repo documentation guidance for Obsidian knowledgebase usage.

Comment thread src/commands/dns.rs
Comment on lines +251 to +261
None => {
let mut buf = String::new();
io::stdin().lock().read_to_string(&mut buf)?;
buf
}
};
let result = client.import_dns_zone(*id, &zone_data).await?;
if let OutputFormat::Json = format {
let json =
serde_json::to_string_pretty(&result).expect("failed to serialize to JSON");
println!("{json}");

Copilot AI Mar 20, 2026

Copy link

Choose a reason for hiding this comment

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

This reads from stdin with read_to_string without adding context, and JSON output uses .expect("failed to serialize to JSON"), which can panic. Prefer .context("failed to read zone file from stdin")? and serde_json::to_string_pretty(&result).context("failed to serialize to JSON")? to keep errors user-friendly and non-panicking.

Copilot uses AI. Check for mistakes.
Comment thread src/commands/shield.rs
Comment on lines +943 to +946
if let OutputFormat::Json = format {
let json =
serde_json::to_string_pretty(&metrics).expect("failed to serialize to JSON");
println!("{json}");

Copilot AI Mar 20, 2026

Copy link

Choose a reason for hiding this comment

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

serde_json::to_string_pretty(&metrics).expect(...) will panic on serialization errors and bypass normal CLI error handling. Prefer propagating the error with .context("failed to serialize to JSON")? (consistent with other handlers like commands/auth.rs).

Copilot uses AI. Check for mistakes.
Comment thread tests/cli_stream.rs Outdated
Comment on lines +400 to +426
// Write a temporary SRT file to the system temp directory
let srt_path = std::env::temp_dir().join("hoppy_test_captions.srt");
std::fs::write(&srt_path, "1\n00:00:00,000 --> 00:00:05,000\nHello\n").unwrap();

let output = support::hoppy_mock_cmd_full(
"test-api-key",
&server.uri(),
None,
Some(&server.uri()),
None,
)
.args([
"--format",
"json",
"stream",
"video",
"caption",
"add",
"--library-id",
"10001",
"--video-id",
"vid-guid-0001",
"--srclang",
"en",
"--file",
srt_path.to_str().unwrap(),
])

Copilot AI Mar 20, 2026

Copy link

Choose a reason for hiding this comment

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

This test writes a fixed filename into the global temp directory ("hoppy_test_captions.srt"), which can cause cross-test interference when tests run in parallel and can leave stale files behind. Prefer tempfile::NamedTempFile (or a per-test unique path) and keep the file handle alive for the duration of the command.

Copilot uses AI. Check for mistakes.
Comment thread tests/cli_pull_zone.rs
Comment on lines +268 to +276
#[tokio::test]
async fn purge_url_sends_correct_request() {
let server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/purge"))
.and(query_param("url", "https://cdn.example.com/index.html"))
.respond_with(ResponseTemplate::new(204))
.mount(&server)
.await;

Copilot AI Mar 20, 2026

Copy link

Choose a reason for hiding this comment

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

This E2E test doesn’t assert the AccessKey header (and also doesn’t set .expect(1)), so it can pass even if authentication is accidentally dropped or the request is sent multiple times. Add a header("AccessKey", ...) matcher and .expect(1) to ensure the CLI sends the correct authenticated request exactly once.

Copilot generated this review using guidance from repository custom instructions.
Comment thread CLAUDE.md Outdated
You may organize content in suitable subfolders.
Keep all the documentation in the folder ./hoppy-knowledgebase as *.md markdown files with frontmatter in yaml format with properties of type text, numbers, checkboxes, dates, dates and times, or lists.

Use is as your second brain and document there:

Copilot AI Mar 20, 2026

Copy link

Choose a reason for hiding this comment

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

Grammar typo: “Use is as your second brain” should be “Use it as your second brain”.

Suggested change
Use is as your second brain and document there:
Use it as your second brain and document there:

Copilot uses AI. Check for mistakes.
Comment on lines +393 to +406
pub async fn import_dns_zone(&self, id: i64, zone_file: &str) -> Result<DnsImportResult> {
use reqwest::header::CONTENT_TYPE;
let url = format!("{}/dnszone/{id}/import", self.base_url);
// Build a minimal multipart body manually: bunny.net expects a field named "file".
let boundary = "HoppyBoundary";
let body = format!(
"--{boundary}\r\nContent-Disposition: form-data; name=\"file\"; filename=\"zone.txt\"\r\nContent-Type: text/plain\r\n\r\n{zone_file}\r\n--{boundary}--\r\n"
);
let content_type = format!("multipart/form-data; boundary={boundary}");
let rb = self
.auth(self.http.post(&url))
.header(CONTENT_TYPE, content_type)
.body(body);
let response = self.send(rb).await?;

Copilot AI Mar 20, 2026

Copy link

Choose a reason for hiding this comment

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

import_dns_zone builds multipart/form-data manually with a fixed boundary ("HoppyBoundary") and interpolates the zone file content directly. Multipart boundaries should be unique/unpredictable; with a fixed boundary, valid zone content containing that boundary sequence can corrupt the request. Prefer enabling reqwest’s multipart feature and using reqwest::multipart::Form (or at least generate a per-request boundary and build a bytes body safely).

Copilot uses AI. Check for mistakes.
Comment thread src/commands/stream.rs
Comment on lines +520 to +523
if let OutputFormat::Json = format {
let json =
serde_json::to_string_pretty(&result).expect("failed to serialize to JSON");
println!("{json}");

Copilot AI Mar 20, 2026

Copy link

Choose a reason for hiding this comment

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

This uses serde_json::to_string_pretty(...).expect(...) in non-test code, which will panic on serialization errors and bypass the CLI’s normal error reporting. Use anyhow::Context (or ?) to propagate a structured error instead of panicking.

Copilot uses AI. Check for mistakes.
ractive and others added 2 commits March 20, 2026 09:37
- Generate unique multipart boundary per request in DNS import
- Add error context for stdin reads in DNS import
- Use tempfile::NamedTempFile in caption test for isolation
- Add AccessKey header matcher and expect(1) to purge URL test
- Fix CLAUDE.md typo ("Use is" → "Use it")
- Fix iteration-12 checklist: mark unimplemented metrics as deferred

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Addresses remaining Copilot review comments: use anyhow::Context
instead of .expect() for serde_json serialization in the new
dns import, shield metrics, and stream caption handlers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@ractive
ractive merged commit f4f758f into main Mar 20, 2026
5 of 7 checks passed
@ractive
ractive deleted the iter-12/api-coverage-gaps branch March 20, 2026 08:46
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