From 173fd3d105ee579eafbf46a611d7d62d2f63d88f Mon Sep 17 00:00:00 2001 From: Timo Pollmeier Date: Wed, 24 Jun 2026 16:30:22 +0200 Subject: [PATCH 1/5] Add plan for refactoring ZAP HTTP mocks Co-authored-by: AI (copilot/full) --- doc/specs/http-mock-refactor.plan.md | 162 +++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 doc/specs/http-mock-refactor.plan.md diff --git a/doc/specs/http-mock-refactor.plan.md b/doc/specs/http-mock-refactor.plan.md new file mode 100644 index 0000000..5f64e9e --- /dev/null +++ b/doc/specs/http-mock-refactor.plan.md @@ -0,0 +1,162 @@ +# HTTP Mock Refactor Plan + +Status: Proposed (2026-06-24) + +## Goal + +Improve readability and maintainability of tests that use HTTP mocks by extracting repeated Wiremock setup into focused helper functions while preserving test intent and behavior. + +## Decisions + +- Keep this plan implementation-agnostic. +- Prioritize readability over reducing line count. +- Keep abstraction depth low. +- If introducing an additional abstraction layer, pause and confirm before proceeding. +- Keep helpers local by default, but allow shared helpers when duplication is obvious. +- Use `.expect()` helper defaults only for common cases. +- Use a hybrid `.expect()` approach: keep common-case defaults in helpers and keep uncommon overrides inline. +- Run full test suite validation. +- For `src/scan/worker_tests.rs`, use endpoint helpers only in the first pass. +- Re-evaluate scenario struct introduction only after Phase 1 if readability is still insufficient. + +## In-Scope Test Files + +- src/scan/worker_tests.rs +- src/zapclient/alert_tests.rs +- src/zapclient/ajaxspider_tests.rs +- src/zapclient/ascan_tests.rs +- src/zapclient/context_tests.rs + +Estimated in-scope tests: + +- src/scan/worker_tests.rs: 13 +- src/zapclient/alert_tests.rs: 5 +- src/zapclient/ajaxspider_tests.rs: 13 +- src/zapclient/ascan_tests.rs: 9 +- src/zapclient/context_tests.rs: 16 + +Total: 56 + +## Non-Goals + +- No production-code changes. +- No behavior changes in test assertions. +- No framework migration away from wiremock. + +## Refactor Strategy + +### 1. Endpoint-level helpers + +Extract small helpers that mount a single endpoint and response behavior. + +Examples: + +- mount_context_new_ok(server) +- mount_context_include_ok(server) +- mount_context_remove(server, status_code) +- mount_ajax_spider_set_max_duration(server, integer, expected_calls) +- mount_ajax_spider_scan_ok(server) +- mount_ajax_spider_status(server, status) +- mount_ajax_spider_stop(server, status_code, expected_calls) +- mount_ascan_scan(server, body, expected_calls) +- mount_ascan_status(server, status_code, body, expected_calls) +- mount_ascan_stop(server, status_code, expected_calls) +- mount_alerts_page(server, start, body) + +### 2. Scenario bundles + +Compose the endpoint-level helpers into scenario helpers where repeated sequences exist. + +Examples: + +- mount_context_flow_ok(server) +- mount_context_cleanup(server, status_code) +- mount_safe_mode_without_active_scan(server) +- mount_running_active_stage_stoppable(server) + +### 3. Keep test intent explicit + +- Keep special-case body matchers and expect counts close to test scenarios. +- Avoid a single giant builder that hides behavior. +- Keep abstraction depth low and add layers only when clearly justified. +- Keep uncommon `.expect()` overrides inline so exceptional behavior remains visible. + +## Phased Rollout + +### Phase 1: src/scan/worker_tests.rs + +- Extract repeated context lifecycle mounts. +- Expand existing helper style already used in this file. +- Replace repetitive blocks in mock_zap_server_* functions with helper calls. +- Do not introduce a scenario struct in this phase. + +Acceptance: + +- Same endpoint paths, bodies, and expect(n) semantics. +- Existing test names/assertions unchanged. +- Improved readability with shorter scenario setup. + +### Phase 2: src/zapclient/context_tests.rs + +- Extract helpers for context action success/failure patterns. +- Keep edge-case response payloads visible per test. + +Acceptance: + +- Repeated mount boilerplate reduced. +- Error handling test clarity preserved. + +### Phase 3: src/zapclient/ajaxspider_tests.rs and src/zapclient/ascan_tests.rs + +- Introduce endpoint-focused helpers for scan/status/stop actions. +- Keep status parsing and unexpected content tests explicit. + +Acceptance: + +- Less repeated setup. +- Parsing and validation behavior remains obvious in tests. + +### Phase 4: src/zapclient/alert_tests.rs + +- Add helpers for paginated alerts and malformed payload responses. +- Preserve visibility of paging parameters and response body structure. + +Acceptance: + +- Reduced duplication with clear pagination intent. + +## Helper Placement + +Default: + +- Keep helpers local to each test file first. +- Allow promotion to shared test support module when duplication is obvious. + +## Validation + +After each phase, run targeted tests and then the full test suite. + +- cargo test scan::worker_tests -- --nocapture +- cargo test zapclient::context_tests -- --nocapture +- cargo test zapclient::ajaxspider_tests -- --nocapture +- cargo test zapclient::ascan_tests -- --nocapture +- cargo test zapclient::alert_tests -- --nocapture +- cargo test -- --nocapture + +## Risks and Mitigations + +- Risk: over-abstraction hides endpoint behavior. + - Mitigation: keep helpers narrow and endpoint-specific. + +- Risk: altered expect(n) counts change semantics. + - Mitigation: preserve and review each expect(n) during refactor. + +- Risk: debugging gets harder with indirection. + - Mitigation: start with local helpers and clear names. + +## Done Definition + +- All in-scope test files use helper-based HTTP mock setup where repetition exists. +- No behavior changes in tests. +- Readability improves without losing scenario-specific clarity. +- All affected tests pass. From 9ccd8c40124812ee9ef87d12cf75dc4276de6ef7 Mon Sep 17 00:00:00 2001 From: Timo Pollmeier Date: Wed, 24 Jun 2026 16:45:34 +0200 Subject: [PATCH 2/5] Change: Use helpers for mock HTTP server endpoints in worker tests Co-authored-by: AI (copilot/full) --- doc/specs/http-mock-refactor.plan.md | 10 +- src/scan/worker_tests.rs | 1181 ++++++-------------------- 2 files changed, 285 insertions(+), 906 deletions(-) diff --git a/doc/specs/http-mock-refactor.plan.md b/doc/specs/http-mock-refactor.plan.md index 5f64e9e..4bc557b 100644 --- a/doc/specs/http-mock-refactor.plan.md +++ b/doc/specs/http-mock-refactor.plan.md @@ -1,6 +1,6 @@ # HTTP Mock Refactor Plan -Status: Proposed (2026-06-24) +Status: In Progress (2026-06-24) ## Goal @@ -85,6 +85,8 @@ Examples: ### Phase 1: src/scan/worker_tests.rs +Status: Done (2026-06-24) + - Extract repeated context lifecycle mounts. - Expand existing helper style already used in this file. - Replace repetitive blocks in mock_zap_server_* functions with helper calls. @@ -96,6 +98,12 @@ Acceptance: - Existing test names/assertions unchanged. - Improved readability with shorter scenario setup. +Validation recorded: + +- `src/scan/worker_tests.rs` compiles without errors after refactor. +- Worker runtime tests executed and passed. +- Full test suite executed and passed. + ### Phase 2: src/zapclient/context_tests.rs - Extract helpers for context action success/failure patterns. diff --git a/src/scan/worker_tests.rs b/src/scan/worker_tests.rs index d6167e7..d29ad07 100644 --- a/src/scan/worker_tests.rs +++ b/src/scan/worker_tests.rs @@ -74,261 +74,263 @@ fn make_safe_mode_request_with_ajax_timeout(host: &str, timeout_seconds: u64) -> } } -async fn mount_ajax_spider_set_option_max_duration_ok(server: &MockServer) { - Mock::given(method("POST")) - .and(path("/JSON/ajaxSpider/action/setOptionMaxDuration")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), - ) - .mount(server) - .await; -} - -async fn mock_zap_server() -> MockServer { - let server = MockServer::start().await; - +async fn mount_context_new_ok(server: &MockServer) { Mock::given(method("POST")) .and(path("/JSON/context/action/newContext")) .respond_with( ResponseTemplate::new(200).set_body_raw(r#"{"contextId":"ctx-1"}"#, "application/json"), ) - .mount(&server) + .mount(server) .await; +} +async fn mount_context_include_ok(server: &MockServer) { Mock::given(method("POST")) .and(path("/JSON/context/action/includeInContext")) .respond_with( ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), ) - .mount(&server) - .await; - - mount_ajax_spider_set_option_max_duration_ok(&server).await; - - Mock::given(method("POST")) - .and(path("/JSON/ajaxSpider/action/scan")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), - ) - .mount(&server) + .mount(server) .await; +} +async fn mount_context_remove(server: &MockServer, status_code: u16, body: &'static str) { Mock::given(method("POST")) - .and(path("/JSON/ajaxSpider/view/status")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"status":"stopped"}"#, "application/json"), - ) - .mount(&server) + .and(path("/JSON/context/action/removeContext")) + .respond_with(ResponseTemplate::new(status_code).set_body_raw(body, "application/json")) + .mount(server) .await; +} +async fn mount_ajax_spider_set_option_max_duration_ok(server: &MockServer) { Mock::given(method("POST")) - .and(path("/JSON/ascan/action/scan")) + .and(path("/JSON/ajaxSpider/action/setOptionMaxDuration")) .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"scan":"active-1"}"#, "application/json"), + ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), ) - .mount(&server) + .mount(server) .await; +} +async fn mount_ajax_spider_set_option_max_duration( + server: &MockServer, + timeout_seconds: u64, + expected_calls: u64, +) { Mock::given(method("POST")) - .and(path("/JSON/ascan/view/status")) + .and(path("/JSON/ajaxSpider/action/setOptionMaxDuration")) + .and(body_string_contains(format!("Integer={timeout_seconds}"))) .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"status":"100"}"#, "application/json"), + ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), ) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/alert/view/alerts")) - .and(body_string_contains("start=0")) - .respond_with(ResponseTemplate::new(200).set_body_raw( - r#"{"alerts":[{"alertRef":"10001","name":"Finding","risk":"Low","description":"detail","url":"https://example.test/app"}]}"#, - "application/json", - )) - .mount(&server) + .expect(expected_calls) + .mount(server) .await; +} +async fn mount_ajax_spider_scan_ok(server: &MockServer) { Mock::given(method("POST")) - .and(path("/JSON/alert/view/alerts")) - .and(body_string_contains("start=1")) + .and(path("/JSON/ajaxSpider/action/scan")) .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"alerts":[]}"#, "application/json"), + ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), ) - .mount(&server) + .mount(server) .await; +} +async fn mount_ajax_spider_scan_ok_with_expect(server: &MockServer, expected_calls: u64) { Mock::given(method("POST")) - .and(path("/JSON/context/action/removeContext")) + .and(path("/JSON/ajaxSpider/action/scan")) .respond_with( ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), ) - .mount(&server) + .expect(expected_calls) + .mount(server) .await; - - server } -async fn mock_zap_server_safe_mode_without_active_scan_requests() -> MockServer { - let server = MockServer::start().await; - +async fn mount_ajax_spider_status(server: &MockServer, status: &'static str) { Mock::given(method("POST")) - .and(path("/JSON/context/action/newContext")) + .and(path("/JSON/ajaxSpider/view/status")) .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"contextId":"ctx-1"}"#, "application/json"), + ResponseTemplate::new(200) + .set_body_raw(format!(r#"{{"status":"{status}"}}"#), "application/json"), ) - .mount(&server) + .mount(server) .await; +} +async fn mount_ajax_spider_stop_ok(server: &MockServer) { Mock::given(method("POST")) - .and(path("/JSON/context/action/includeInContext")) + .and(path("/JSON/ajaxSpider/action/stop")) .respond_with( ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), ) - .mount(&server) + .mount(server) .await; +} - mount_ajax_spider_set_option_max_duration_ok(&server).await; - +async fn mount_ajax_spider_stop_ok_with_expect(server: &MockServer, expected_calls: u64) { Mock::given(method("POST")) - .and(path("/JSON/ajaxSpider/action/scan")) + .and(path("/JSON/ajaxSpider/action/stop")) .respond_with( ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), ) - .mount(&server) + .expect(expected_calls) + .mount(server) .await; +} +async fn mount_ascan_scan_ok(server: &MockServer) { Mock::given(method("POST")) - .and(path("/JSON/ajaxSpider/view/status")) + .and(path("/JSON/ascan/action/scan")) .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"status":"stopped"}"#, "application/json"), + ResponseTemplate::new(200).set_body_raw(r#"{"scan":"active-1"}"#, "application/json"), ) - .mount(&server) + .mount(server) .await; +} +async fn mount_ascan_scan_ok_with_expect(server: &MockServer, expected_calls: u64) { Mock::given(method("POST")) .and(path("/JSON/ascan/action/scan")) .respond_with( ResponseTemplate::new(200).set_body_raw(r#"{"scan":"active-1"}"#, "application/json"), ) - .expect(0) - .mount(&server) + .expect(expected_calls) + .mount(server) .await; +} +async fn mount_ascan_status(server: &MockServer, status_code: u16, body: &'static str) { Mock::given(method("POST")) .and(path("/JSON/ascan/view/status")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"status":"100"}"#, "application/json"), - ) - .expect(0) - .mount(&server) + .respond_with(ResponseTemplate::new(status_code).set_body_raw(body, "application/json")) + .mount(server) .await; +} +async fn mount_ascan_status_with_expect( + server: &MockServer, + status_code: u16, + body: &'static str, + expected_calls: u64, +) { Mock::given(method("POST")) - .and(path("/JSON/alert/view/alerts")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"alerts":[]}"#, "application/json"), - ) - .mount(&server) + .and(path("/JSON/ascan/view/status")) + .respond_with(ResponseTemplate::new(status_code).set_body_raw(body, "application/json")) + .expect(expected_calls) + .mount(server) .await; +} +async fn mount_ascan_status_with_delay( + server: &MockServer, + delay: Duration, + status_code: u16, + body: &'static str, +) { Mock::given(method("POST")) - .and(path("/JSON/context/action/removeContext")) + .and(path("/JSON/ascan/view/status")) .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), + ResponseTemplate::new(status_code) + .set_delay(delay) + .set_body_raw(body, "application/json"), ) - .mount(&server) + .mount(server) .await; - - server } -async fn mock_zap_server_for_ajax_spider_timeout_enforcement() -> MockServer { - let server = MockServer::start().await; - +async fn mount_ascan_stop(server: &MockServer, status_code: u16, expected_calls: u64) { Mock::given(method("POST")) - .and(path("/JSON/context/action/newContext")) + .and(path("/JSON/ascan/action/stop")) .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"contextId":"ctx-1"}"#, "application/json"), - ) - .mount(&server) + ResponseTemplate::new(status_code) + .set_body_raw( + if status_code == 200 { + r#"{"Result":"OK"}"# + } else { + r#"{"code":"internal"}"# + }, + "application/json", + ), + ) + .expect(expected_calls) + .mount(server) .await; +} +async fn mount_alerts_empty(server: &MockServer) { Mock::given(method("POST")) - .and(path("/JSON/context/action/includeInContext")) + .and(path("/JSON/alert/view/alerts")) .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), + ResponseTemplate::new(200).set_body_raw(r#"{"alerts":[]}"#, "application/json"), ) - .mount(&server) + .mount(server) .await; +} +async fn mount_alerts_page(server: &MockServer, start: u64, body: &'static str) { Mock::given(method("POST")) - .and(path("/JSON/ajaxSpider/action/setOptionMaxDuration")) - .and(body_string_contains("Integer=1")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), - ) - .expect(1) - .mount(&server) + .and(path("/JSON/alert/view/alerts")) + .and(body_string_contains(format!("start={start}"))) + .respond_with(ResponseTemplate::new(200).set_body_raw(body, "application/json")) + .mount(server) .await; +} - Mock::given(method("POST")) - .and(path("/JSON/ajaxSpider/action/scan")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), - ) - .expect(1) - .mount(&server) - .await; +async fn mock_zap_server() -> MockServer { + let server = MockServer::start().await; - Mock::given(method("POST")) - .and(path("/JSON/ajaxSpider/view/status")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"status":"stopped"}"#, "application/json"), - ) - .mount(&server) - .await; + mount_context_new_ok(&server).await; + mount_context_include_ok(&server).await; + mount_ajax_spider_set_option_max_duration_ok(&server).await; - Mock::given(method("POST")) - .and(path("/JSON/ajaxSpider/action/stop")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), - ) - .expect(0) - .mount(&server) - .await; + mount_ajax_spider_scan_ok(&server).await; + mount_ajax_spider_status(&server, "stopped").await; + mount_ascan_scan_ok(&server).await; + mount_ascan_status(&server, 200, r#"{"status":"100"}"#).await; + mount_alerts_page( + &server, + 0, + r#"{"alerts":[{"alertRef":"10001","name":"Finding","risk":"Low","description":"detail","url":"https://example.test/app"}]}"#, + ) + .await; + mount_alerts_page(&server, 1, r#"{"alerts":[]}"#).await; + mount_context_remove(&server, 200, r#"{"Result":"OK"}"#).await; - Mock::given(method("POST")) - .and(path("/JSON/ascan/action/scan")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"scan":"active-1"}"#, "application/json"), - ) - .expect(0) - .mount(&server) - .await; + server +} - Mock::given(method("POST")) - .and(path("/JSON/ascan/view/status")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"status":"100"}"#, "application/json"), - ) - .expect(0) - .mount(&server) - .await; +async fn mock_zap_server_safe_mode_without_active_scan_requests() -> MockServer { + let server = MockServer::start().await; - Mock::given(method("POST")) - .and(path("/JSON/alert/view/alerts")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"alerts":[]}"#, "application/json"), - ) - .mount(&server) - .await; + mount_context_new_ok(&server).await; + mount_context_include_ok(&server).await; + mount_ajax_spider_set_option_max_duration_ok(&server).await; + mount_ajax_spider_scan_ok(&server).await; + mount_ajax_spider_status(&server, "stopped").await; + mount_ascan_scan_ok_with_expect(&server, 0).await; + mount_ascan_status_with_expect(&server, 200, r#"{"status":"100"}"#, 0).await; + mount_alerts_empty(&server).await; + mount_context_remove(&server, 200, r#"{"Result":"OK"}"#).await; - Mock::given(method("POST")) - .and(path("/JSON/context/action/removeContext")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), - ) - .mount(&server) - .await; + server +} + +async fn mock_zap_server_for_ajax_spider_timeout_enforcement() -> MockServer { + let server = MockServer::start().await; + + mount_context_new_ok(&server).await; + mount_context_include_ok(&server).await; + mount_ajax_spider_set_option_max_duration(&server, 1, 1).await; + mount_ajax_spider_scan_ok_with_expect(&server, 1).await; + mount_ajax_spider_status(&server, "stopped").await; + mount_ajax_spider_stop_ok_with_expect(&server, 0).await; + mount_ascan_scan_ok_with_expect(&server, 0).await; + mount_ascan_status_with_expect(&server, 200, r#"{"status":"100"}"#, 0).await; + mount_alerts_empty(&server).await; + mount_context_remove(&server, 200, r#"{"Result":"OK"}"#).await; server } @@ -336,504 +338,103 @@ async fn mock_zap_server_for_ajax_spider_timeout_enforcement() -> MockServer { async fn mock_zap_server_for_unlimited_ajax_spider_timeout() -> MockServer { let server = MockServer::start().await; - Mock::given(method("POST")) - .and(path("/JSON/context/action/newContext")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"contextId":"ctx-1"}"#, "application/json"), - ) - .mount(&server) - .await; + mount_context_new_ok(&server).await; + mount_context_include_ok(&server).await; + mount_ajax_spider_set_option_max_duration(&server, 0, 1).await; + mount_ajax_spider_scan_ok_with_expect(&server, 1).await; + mount_ajax_spider_status(&server, "stopped").await; + mount_ajax_spider_stop_ok_with_expect(&server, 0).await; + mount_ascan_scan_ok_with_expect(&server, 0).await; + mount_ascan_status_with_expect(&server, 200, r#"{"status":"100"}"#, 0).await; + mount_alerts_empty(&server).await; + mount_context_remove(&server, 200, r#"{"Result":"OK"}"#).await; - Mock::given(method("POST")) - .and(path("/JSON/context/action/includeInContext")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), - ) - .mount(&server) - .await; + server +} - Mock::given(method("POST")) - .and(path("/JSON/ajaxSpider/action/setOptionMaxDuration")) - .and(body_string_contains("Integer=0")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), - ) - .expect(1) - .mount(&server) - .await; +async fn mock_zap_server_for_default_ajax_spider_timeout() -> MockServer { + let server = MockServer::start().await; - Mock::given(method("POST")) - .and(path("/JSON/ajaxSpider/action/scan")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), - ) - .expect(1) - .mount(&server) - .await; + mount_context_new_ok(&server).await; + mount_context_include_ok(&server).await; + mount_ajax_spider_set_option_max_duration(&server, 3600, 1).await; + mount_ajax_spider_scan_ok_with_expect(&server, 1).await; + mount_ajax_spider_status(&server, "stopped").await; + mount_ascan_scan_ok_with_expect(&server, 0).await; + mount_ascan_status_with_expect(&server, 200, r#"{"status":"100"}"#, 0).await; + mount_alerts_empty(&server).await; + mount_context_remove(&server, 200, r#"{"Result":"OK"}"#).await; - Mock::given(method("POST")) - .and(path("/JSON/ajaxSpider/view/status")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"status":"stopped"}"#, "application/json"), - ) - .mount(&server) - .await; + server +} - Mock::given(method("POST")) - .and(path("/JSON/ajaxSpider/action/stop")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), - ) - .expect(0) - .mount(&server) - .await; +async fn mock_zap_server_for_spider_timeout_grace_stop_request() -> MockServer { + let server = MockServer::start().await; - Mock::given(method("POST")) - .and(path("/JSON/ascan/action/scan")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"scan":"active-1"}"#, "application/json"), - ) - .expect(0) - .mount(&server) - .await; + mount_context_new_ok(&server).await; + mount_context_include_ok(&server).await; + mount_ajax_spider_set_option_max_duration(&server, 1, 1).await; + mount_ajax_spider_scan_ok_with_expect(&server, 1).await; + mount_ajax_spider_status(&server, "running").await; + mount_ajax_spider_stop_ok(&server).await; + mount_ascan_scan_ok_with_expect(&server, 0).await; + mount_ascan_status_with_expect(&server, 200, r#"{"status":"100"}"#, 0).await; + mount_alerts_empty(&server).await; + mount_context_remove(&server, 200, r#"{"Result":"OK"}"#).await; - Mock::given(method("POST")) - .and(path("/JSON/ascan/view/status")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"status":"100"}"#, "application/json"), - ) - .expect(0) - .mount(&server) - .await; + server +} - Mock::given(method("POST")) - .and(path("/JSON/alert/view/alerts")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"alerts":[]}"#, "application/json"), - ) - .mount(&server) - .await; +async fn mock_zap_server_for_spider_stop_status_change_timeout() -> MockServer { + let server = MockServer::start().await; - Mock::given(method("POST")) - .and(path("/JSON/context/action/removeContext")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), - ) - .mount(&server) - .await; + mount_context_new_ok(&server).await; + mount_context_include_ok(&server).await; + mount_ajax_spider_set_option_max_duration(&server, 1, 1).await; + mount_ajax_spider_scan_ok_with_expect(&server, 1).await; + mount_ajax_spider_status(&server, "running").await; + mount_ajax_spider_stop_ok_with_expect(&server, 1).await; + mount_ascan_scan_ok_with_expect(&server, 0).await; + mount_ascan_status_with_expect(&server, 200, r#"{"status":"100"}"#, 0).await; + mount_alerts_empty(&server).await; + mount_context_remove(&server, 200, r#"{"Result":"OK"}"#).await; server } -async fn mock_zap_server_for_default_ajax_spider_timeout() -> MockServer { +async fn mock_zap_server_with_active_status_error() -> MockServer { let server = MockServer::start().await; - Mock::given(method("POST")) - .and(path("/JSON/context/action/newContext")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"contextId":"ctx-1"}"#, "application/json"), - ) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/context/action/includeInContext")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), - ) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/ajaxSpider/action/setOptionMaxDuration")) - .and(body_string_contains("Integer=3600")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), - ) - .expect(1) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/ajaxSpider/action/scan")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), - ) - .expect(1) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/ajaxSpider/view/status")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"status":"stopped"}"#, "application/json"), - ) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/ascan/action/scan")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"scan":"active-1"}"#, "application/json"), - ) - .expect(0) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/ascan/view/status")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"status":"100"}"#, "application/json"), - ) - .expect(0) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/alert/view/alerts")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"alerts":[]}"#, "application/json"), - ) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/context/action/removeContext")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), - ) - .mount(&server) - .await; - - server -} - -async fn mock_zap_server_for_spider_timeout_grace_stop_request() -> MockServer { - let server = MockServer::start().await; - - Mock::given(method("POST")) - .and(path("/JSON/context/action/newContext")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"contextId":"ctx-1"}"#, "application/json"), - ) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/context/action/includeInContext")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), - ) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/ajaxSpider/action/setOptionMaxDuration")) - .and(body_string_contains("Integer=1")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), - ) - .expect(1) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/ajaxSpider/action/scan")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), - ) - .expect(1) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/ajaxSpider/view/status")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"status":"running"}"#, "application/json"), - ) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/ajaxSpider/action/stop")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), - ) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/ascan/action/scan")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"scan":"active-1"}"#, "application/json"), - ) - .expect(0) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/ascan/view/status")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"status":"100"}"#, "application/json"), - ) - .expect(0) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/alert/view/alerts")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"alerts":[]}"#, "application/json"), - ) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/context/action/removeContext")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), - ) - .mount(&server) - .await; - - server -} - -async fn mock_zap_server_for_spider_stop_status_change_timeout() -> MockServer { - let server = MockServer::start().await; - - Mock::given(method("POST")) - .and(path("/JSON/context/action/newContext")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"contextId":"ctx-1"}"#, "application/json"), - ) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/context/action/includeInContext")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), - ) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/ajaxSpider/action/setOptionMaxDuration")) - .and(body_string_contains("Integer=1")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), - ) - .expect(1) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/ajaxSpider/action/scan")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), - ) - .expect(1) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/ajaxSpider/view/status")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"status":"running"}"#, "application/json"), - ) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/ajaxSpider/action/stop")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), - ) - .expect(1) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/ascan/action/scan")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"scan":"active-1"}"#, "application/json"), - ) - .expect(0) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/ascan/view/status")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"status":"100"}"#, "application/json"), - ) - .expect(0) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/alert/view/alerts")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"alerts":[]}"#, "application/json"), - ) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/context/action/removeContext")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), - ) - .mount(&server) - .await; - - server -} - -async fn mock_zap_server_with_active_status_error() -> MockServer { - let server = MockServer::start().await; - - Mock::given(method("POST")) - .and(path("/JSON/context/action/newContext")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"contextId":"ctx-1"}"#, "application/json"), - ) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/context/action/includeInContext")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), - ) - .mount(&server) - .await; - - mount_ajax_spider_set_option_max_duration_ok(&server).await; - - Mock::given(method("POST")) - .and(path("/JSON/ajaxSpider/action/scan")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), - ) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/ajaxSpider/view/status")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"status":"stopped"}"#, "application/json"), - ) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/ascan/action/scan")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"scan":"active-1"}"#, "application/json"), - ) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/ascan/view/status")) - .respond_with( - ResponseTemplate::new(500).set_body_raw(r#"{"code":"internal"}"#, "application/json"), - ) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/context/action/removeContext")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), - ) - .mount(&server) - .await; - - server -} - -async fn mock_zap_server_with_remove_context_error() -> MockServer { - let server = MockServer::start().await; - - Mock::given(method("POST")) - .and(path("/JSON/context/action/newContext")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"contextId":"ctx-1"}"#, "application/json"), - ) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/context/action/includeInContext")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), - ) - .mount(&server) - .await; - - mount_ajax_spider_set_option_max_duration_ok(&server).await; - - Mock::given(method("POST")) - .and(path("/JSON/ajaxSpider/action/scan")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), - ) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/ajaxSpider/view/status")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"status":"stopped"}"#, "application/json"), - ) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/ascan/action/scan")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"scan":"active-1"}"#, "application/json"), - ) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/ascan/view/status")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"status":"100"}"#, "application/json"), - ) - .mount(&server) - .await; + mount_context_new_ok(&server).await; + mount_context_include_ok(&server).await; + mount_ajax_spider_set_option_max_duration_ok(&server).await; + mount_ajax_spider_scan_ok(&server).await; + mount_ajax_spider_status(&server, "stopped").await; + mount_ascan_scan_ok(&server).await; + mount_ascan_status(&server, 500, r#"{"code":"internal"}"#).await; + mount_context_remove(&server, 200, r#"{"Result":"OK"}"#).await; - Mock::given(method("POST")) - .and(path("/JSON/alert/view/alerts")) - .and(body_string_contains("start=0")) - .respond_with(ResponseTemplate::new(200).set_body_raw( - r#"{"alerts":[{"alertRef":"10001","name":"Finding","risk":"Low","description":"detail","url":"https://example.test/app"}]}"#, - "application/json", - )) - .mount(&server) - .await; + server +} - Mock::given(method("POST")) - .and(path("/JSON/alert/view/alerts")) - .and(body_string_contains("start=1")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"alerts":[]}"#, "application/json"), - ) - .mount(&server) - .await; +async fn mock_zap_server_with_remove_context_error() -> MockServer { + let server = MockServer::start().await; - Mock::given(method("POST")) - .and(path("/JSON/context/action/removeContext")) - .respond_with( - ResponseTemplate::new(500).set_body_raw(r#"{"code":"internal"}"#, "application/json"), - ) - .mount(&server) - .await; + mount_context_new_ok(&server).await; + mount_context_include_ok(&server).await; + mount_ajax_spider_set_option_max_duration_ok(&server).await; + mount_ajax_spider_scan_ok(&server).await; + mount_ajax_spider_status(&server, "stopped").await; + mount_ascan_scan_ok(&server).await; + mount_ascan_status(&server, 200, r#"{"status":"100"}"#).await; + mount_alerts_page( + &server, + 0, + r#"{"alerts":[{"alertRef":"10001","name":"Finding","risk":"Low","description":"detail","url":"https://example.test/app"}]}"#, + ) + .await; + mount_alerts_page(&server, 1, r#"{"alerts":[]}"#).await; + mount_context_remove(&server, 500, r#"{"code":"internal"}"#).await; server } @@ -841,80 +442,16 @@ async fn mock_zap_server_with_remove_context_error() -> MockServer { async fn mock_zap_server_for_running_stop_in_active_scan() -> MockServer { let server = MockServer::start().await; - Mock::given(method("POST")) - .and(path("/JSON/context/action/newContext")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"contextId":"ctx-1"}"#, "application/json"), - ) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/context/action/includeInContext")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), - ) - .mount(&server) - .await; - + mount_context_new_ok(&server).await; + mount_context_include_ok(&server).await; mount_ajax_spider_set_option_max_duration_ok(&server).await; - - Mock::given(method("POST")) - .and(path("/JSON/ajaxSpider/action/scan")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), - ) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/ajaxSpider/view/status")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"status":"stopped"}"#, "application/json"), - ) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/ascan/action/scan")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"scan":"active-1"}"#, "application/json"), - ) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/ascan/view/status")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"status":"10"}"#, "application/json"), - ) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/ascan/action/stop")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), - ) - .expect(1) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/alert/view/alerts")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"alerts":[]}"#, "application/json"), - ) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/context/action/removeContext")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), - ) - .mount(&server) - .await; + mount_ajax_spider_scan_ok(&server).await; + mount_ajax_spider_status(&server, "stopped").await; + mount_ascan_scan_ok(&server).await; + mount_ascan_status(&server, 200, r#"{"status":"10"}"#).await; + mount_ascan_stop(&server, 200, 1).await; + mount_alerts_empty(&server).await; + mount_context_remove(&server, 200, r#"{"Result":"OK"}"#).await; server } @@ -922,64 +459,14 @@ async fn mock_zap_server_for_running_stop_in_active_scan() -> MockServer { async fn mock_zap_server_for_running_stop_in_spider() -> MockServer { let server = MockServer::start().await; - Mock::given(method("POST")) - .and(path("/JSON/context/action/newContext")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"contextId":"ctx-1"}"#, "application/json"), - ) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/context/action/includeInContext")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), - ) - .mount(&server) - .await; - + mount_context_new_ok(&server).await; + mount_context_include_ok(&server).await; mount_ajax_spider_set_option_max_duration_ok(&server).await; - - Mock::given(method("POST")) - .and(path("/JSON/ajaxSpider/action/scan")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), - ) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/ajaxSpider/view/status")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"status":"running"}"#, "application/json"), - ) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/ajaxSpider/action/stop")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), - ) - .expect(1) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/alert/view/alerts")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"alerts":[]}"#, "application/json"), - ) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/context/action/removeContext")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), - ) - .mount(&server) - .await; + mount_ajax_spider_scan_ok(&server).await; + mount_ajax_spider_status(&server, "running").await; + mount_ajax_spider_stop_ok_with_expect(&server, 1).await; + mount_alerts_empty(&server).await; + mount_context_remove(&server, 200, r#"{"Result":"OK"}"#).await; server } @@ -987,80 +474,16 @@ async fn mock_zap_server_for_running_stop_in_spider() -> MockServer { async fn mock_zap_server_for_running_stop_in_active_stage_with_stop_failure() -> MockServer { let server = MockServer::start().await; - Mock::given(method("POST")) - .and(path("/JSON/context/action/newContext")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"contextId":"ctx-1"}"#, "application/json"), - ) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/context/action/includeInContext")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), - ) - .mount(&server) - .await; - + mount_context_new_ok(&server).await; + mount_context_include_ok(&server).await; mount_ajax_spider_set_option_max_duration_ok(&server).await; - - Mock::given(method("POST")) - .and(path("/JSON/ajaxSpider/action/scan")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), - ) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/ajaxSpider/view/status")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"status":"stopped"}"#, "application/json"), - ) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/ascan/action/scan")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"scan":"active-1"}"#, "application/json"), - ) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/ascan/view/status")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"status":"10"}"#, "application/json"), - ) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/ascan/action/stop")) - .respond_with( - ResponseTemplate::new(500).set_body_raw(r#"{"code":"internal"}"#, "application/json"), - ) - .expect(1) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/alert/view/alerts")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"alerts":[]}"#, "application/json"), - ) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/context/action/removeContext")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), - ) - .mount(&server) - .await; + mount_ajax_spider_scan_ok(&server).await; + mount_ajax_spider_status(&server, "stopped").await; + mount_ascan_scan_ok(&server).await; + mount_ascan_status(&server, 200, r#"{"status":"10"}"#).await; + mount_ascan_stop(&server, 500, 1).await; + mount_alerts_empty(&server).await; + mount_context_remove(&server, 200, r#"{"Result":"OK"}"#).await; server } @@ -1068,73 +491,21 @@ async fn mock_zap_server_for_running_stop_in_active_stage_with_stop_failure() -> async fn mock_zap_server_for_forced_stop_timeout() -> MockServer { let server = MockServer::start().await; - Mock::given(method("POST")) - .and(path("/JSON/context/action/newContext")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"contextId":"ctx-1"}"#, "application/json"), - ) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/context/action/includeInContext")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), - ) - .mount(&server) - .await; - + mount_context_new_ok(&server).await; + mount_context_include_ok(&server).await; mount_ajax_spider_set_option_max_duration_ok(&server).await; - - Mock::given(method("POST")) - .and(path("/JSON/ajaxSpider/action/scan")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), - ) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/ajaxSpider/view/status")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"status":"stopped"}"#, "application/json"), - ) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/ascan/action/scan")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"scan":"active-1"}"#, "application/json"), - ) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/ascan/view/status")) - .respond_with( - ResponseTemplate::new(200) - .set_delay(Duration::from_millis(200)) - .set_body_raw(r#"{"status":"10"}"#, "application/json"), - ) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/alert/view/alerts")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"alerts":[]}"#, "application/json"), - ) - .mount(&server) - .await; - - Mock::given(method("POST")) - .and(path("/JSON/context/action/removeContext")) - .respond_with( - ResponseTemplate::new(200).set_body_raw(r#"{"Result":"OK"}"#, "application/json"), - ) - .mount(&server) - .await; + mount_ajax_spider_scan_ok(&server).await; + mount_ajax_spider_status(&server, "stopped").await; + mount_ascan_scan_ok(&server).await; + mount_ascan_status_with_delay( + &server, + Duration::from_millis(200), + 200, + r#"{"status":"10"}"#, + ) + .await; + mount_alerts_empty(&server).await; + mount_context_remove(&server, 200, r#"{"Result":"OK"}"#).await; server } From 462436ad29dbc0cf9fae8dbb39fa92840ef28fcd Mon Sep 17 00:00:00 2001 From: Timo Pollmeier Date: Thu, 25 Jun 2026 08:46:05 +0200 Subject: [PATCH 3/5] Refactor ZAP client tests to use endpoint mount helper functions Co-authored-by: AI (copilot/full) --- doc/specs/http-mock-refactor.plan.md | 24 +++- src/zapclient/ajaxspider_tests.rs | 115 ++++++++++-------- src/zapclient/alert_tests.rs | 48 ++++---- src/zapclient/ascan_tests.rs | 93 +++++++++------ src/zapclient/context_tests.rs | 168 +++++++++++++++------------ 5 files changed, 269 insertions(+), 179 deletions(-) diff --git a/doc/specs/http-mock-refactor.plan.md b/doc/specs/http-mock-refactor.plan.md index 4bc557b..06cf2af 100644 --- a/doc/specs/http-mock-refactor.plan.md +++ b/doc/specs/http-mock-refactor.plan.md @@ -1,6 +1,6 @@ # HTTP Mock Refactor Plan -Status: In Progress (2026-06-24) +Status: Done (2026-06-25) ## Goal @@ -106,6 +106,8 @@ Validation recorded: ### Phase 2: src/zapclient/context_tests.rs +Status: Done (2026-06-25) + - Extract helpers for context action success/failure patterns. - Keep edge-case response payloads visible per test. @@ -114,8 +116,15 @@ Acceptance: - Repeated mount boilerplate reduced. - Error handling test clarity preserved. +Validation recorded: + +- `context_tests` targeted run executed and passed (`16 passed`). +- Full test suite executed and passed. + ### Phase 3: src/zapclient/ajaxspider_tests.rs and src/zapclient/ascan_tests.rs +Status: Done (2026-06-25) + - Introduce endpoint-focused helpers for scan/status/stop actions. - Keep status parsing and unexpected content tests explicit. @@ -124,8 +133,16 @@ Acceptance: - Less repeated setup. - Parsing and validation behavior remains obvious in tests. +Validation recorded: + +- `ajaxspider_tests` targeted run executed and passed (`13 passed`). +- `ascan_tests` targeted run executed and passed (`9 passed`). +- Full test suite executed and passed. + ### Phase 4: src/zapclient/alert_tests.rs +Status: Done (2026-06-25) + - Add helpers for paginated alerts and malformed payload responses. - Preserve visibility of paging parameters and response body structure. @@ -133,6 +150,11 @@ Acceptance: - Reduced duplication with clear pagination intent. +Validation recorded: + +- `alert_tests` targeted run executed and passed (`5 passed`). +- Full test suite executed and passed. + ## Helper Placement Default: diff --git a/src/zapclient/ajaxspider_tests.rs b/src/zapclient/ajaxspider_tests.rs index d22df3b..dccd75c 100644 --- a/src/zapclient/ajaxspider_tests.rs +++ b/src/zapclient/ajaxspider_tests.rs @@ -12,6 +12,33 @@ use super::{AjaxSpiderStatus, ZapClient, ZapClientError}; const API_KEY: &str = "test-api-key"; +async fn mount_ajax_spider_scan(server: &MockServer, response: ResponseTemplate) { + Mock::given(method("POST")) + .and(path("/JSON/ajaxSpider/action/scan")) + .respond_with(response) + .expect(1) + .mount(server) + .await; +} + +async fn mount_ajax_spider_status(server: &MockServer, response: ResponseTemplate) { + Mock::given(method("POST")) + .and(path("/JSON/ajaxSpider/view/status")) + .respond_with(response) + .expect(1) + .mount(server) + .await; +} + +async fn mount_ajax_spider_stop(server: &MockServer, response: ResponseTemplate) { + Mock::given(method("POST")) + .and(path("/JSON/ajaxSpider/action/stop")) + .respond_with(response) + .expect(1) + .mount(server) + .await; +} + #[tokio::test] async fn start_ajax_spider_scan_posts_to_zap_ajax_scan_endpoint() { let server = MockServer::start().await; @@ -63,12 +90,11 @@ async fn set_ajax_spider_max_duration_posts_to_zap_option_endpoint() { async fn start_ajax_spider_scan_returns_unexpected_status_on_http_error() { let server = MockServer::start().await; - Mock::given(method("POST")) - .and(path("/JSON/ajaxSpider/action/scan")) - .respond_with(ResponseTemplate::new(500).set_body_string("zap unavailable")) - .expect(1) - .mount(&server) - .await; + mount_ajax_spider_scan( + &server, + ResponseTemplate::new(500).set_body_string("zap unavailable"), + ) + .await; let client = ZapClient::new(server.uri(), API_KEY.to_string()).expect("client should be constructed"); @@ -91,12 +117,11 @@ async fn start_ajax_spider_scan_returns_unexpected_status_on_http_error() { async fn start_ajax_spider_scan_returns_parse_error_for_invalid_schema() { let server = MockServer::start().await; - Mock::given(method("POST")) - .and(path("/JSON/ajaxSpider/action/scan")) - .respond_with(ResponseTemplate::new(200).set_body_string("{\"status\":\"OK\"}")) - .expect(1) - .mount(&server) - .await; + mount_ajax_spider_scan( + &server, + ResponseTemplate::new(200).set_body_string("{\"status\":\"OK\"}"), + ) + .await; let client = ZapClient::new(server.uri(), API_KEY.to_string()).expect("client should be constructed"); @@ -116,12 +141,11 @@ async fn start_ajax_spider_scan_returns_parse_error_for_invalid_schema() { async fn start_ajax_spider_scan_returns_unexpected_content_when_result_is_not_ok() { let server = MockServer::start().await; - Mock::given(method("POST")) - .and(path("/JSON/ajaxSpider/action/scan")) - .respond_with(ResponseTemplate::new(200).set_body_string("{\"Result\":\"FAIL\"}")) - .expect(1) - .mount(&server) - .await; + mount_ajax_spider_scan( + &server, + ResponseTemplate::new(200).set_body_string("{\"Result\":\"FAIL\"}"), + ) + .await; let client = ZapClient::new(server.uri(), API_KEY.to_string()).expect("client should be constructed"); @@ -190,12 +214,11 @@ async fn get_ajax_spider_status_accepts_lowercase_stopped() { async fn get_ajax_spider_status_rejects_uppercase_status() { let server = MockServer::start().await; - Mock::given(method("POST")) - .and(path("/JSON/ajaxSpider/view/status")) - .respond_with(ResponseTemplate::new(200).set_body_string("{\"status\":\"Stopped\"}")) - .expect(1) - .mount(&server) - .await; + mount_ajax_spider_status( + &server, + ResponseTemplate::new(200).set_body_string("{\"status\":\"Stopped\"}"), + ) + .await; let client = ZapClient::new(server.uri(), API_KEY.to_string()).expect("client should be constructed"); @@ -218,12 +241,11 @@ async fn get_ajax_spider_status_rejects_uppercase_status() { async fn get_ajax_spider_status_returns_unexpected_status_on_http_error() { let server = MockServer::start().await; - Mock::given(method("POST")) - .and(path("/JSON/ajaxSpider/view/status")) - .respond_with(ResponseTemplate::new(500).set_body_string("zap unavailable")) - .expect(1) - .mount(&server) - .await; + mount_ajax_spider_status( + &server, + ResponseTemplate::new(500).set_body_string("zap unavailable"), + ) + .await; let client = ZapClient::new(server.uri(), API_KEY.to_string()).expect("client should be constructed"); @@ -246,12 +268,11 @@ async fn get_ajax_spider_status_returns_unexpected_status_on_http_error() { async fn get_ajax_spider_status_returns_parse_error_for_invalid_schema() { let server = MockServer::start().await; - Mock::given(method("POST")) - .and(path("/JSON/ajaxSpider/view/status")) - .respond_with(ResponseTemplate::new(200).set_body_string("{\"progress\":\"100\"}")) - .expect(1) - .mount(&server) - .await; + mount_ajax_spider_status( + &server, + ResponseTemplate::new(200).set_body_string("{\"progress\":\"100\"}"), + ) + .await; let client = ZapClient::new(server.uri(), API_KEY.to_string()).expect("client should be constructed"); @@ -271,12 +292,11 @@ async fn get_ajax_spider_status_returns_parse_error_for_invalid_schema() { async fn get_ajax_spider_status_returns_unexpected_content_for_unknown_status() { let server = MockServer::start().await; - Mock::given(method("POST")) - .and(path("/JSON/ajaxSpider/view/status")) - .respond_with(ResponseTemplate::new(200).set_body_string("{\"status\":\"100\"}")) - .expect(1) - .mount(&server) - .await; + mount_ajax_spider_status( + &server, + ResponseTemplate::new(200).set_body_string("{\"status\":\"100\"}"), + ) + .await; let client = ZapClient::new(server.uri(), API_KEY.to_string()).expect("client should be constructed"); @@ -320,12 +340,11 @@ async fn stop_ajax_spider_scan_posts_to_zap_ajax_stop_endpoint() { async fn stop_ajax_spider_scan_returns_unexpected_status_on_http_error() { let server = MockServer::start().await; - Mock::given(method("POST")) - .and(path("/JSON/ajaxSpider/action/stop")) - .respond_with(ResponseTemplate::new(500).set_body_string("zap unavailable")) - .expect(1) - .mount(&server) - .await; + mount_ajax_spider_stop( + &server, + ResponseTemplate::new(500).set_body_string("zap unavailable"), + ) + .await; let client = ZapClient::new(server.uri(), API_KEY.to_string()).expect("client should be constructed"); diff --git a/src/zapclient/alert_tests.rs b/src/zapclient/alert_tests.rs index b2a4a60..253de51 100644 --- a/src/zapclient/alert_tests.rs +++ b/src/zapclient/alert_tests.rs @@ -12,6 +12,15 @@ use super::{AlertRiskLevel, ZapClient, ZapClientError}; const API_KEY: &str = "test-api-key"; +async fn mount_alerts(server: &MockServer, response: ResponseTemplate) { + Mock::given(method("POST")) + .and(path("/JSON/alert/view/alerts")) + .respond_with(response) + .expect(1) + .mount(server) + .await; +} + #[tokio::test] async fn get_alerts_gets_zap_alert_view_alerts_endpoint() { let server = MockServer::start().await; @@ -59,16 +68,13 @@ async fn get_alerts_gets_zap_alert_view_alerts_endpoint() { async fn get_alerts_returns_unknown_risk_level_for_unrecognized_value() { let server = MockServer::start().await; - Mock::given(method("POST")) - .and(path("/JSON/alert/view/alerts")) - .respond_with( - ResponseTemplate::new(200).set_body_string( - "{\"alerts\":[{\"alertRef\":\"50001\",\"name\":\"Custom Risk\",\"risk\":\"Critical\",\"description\":\"Unknown risk value from API\",\"url\":\"https://example.com/custom\"}]}", - ), - ) - .expect(1) - .mount(&server) - .await; + mount_alerts( + &server, + ResponseTemplate::new(200).set_body_string( + "{\"alerts\":[{\"alertRef\":\"50001\",\"name\":\"Custom Risk\",\"risk\":\"Critical\",\"description\":\"Unknown risk value from API\",\"url\":\"https://example.com/custom\"}]}", + ), + ) + .await; let client = ZapClient::new(server.uri(), API_KEY.to_string()).expect("client should be constructed"); @@ -86,12 +92,11 @@ async fn get_alerts_returns_unknown_risk_level_for_unrecognized_value() { async fn get_alerts_returns_unexpected_status_on_http_error() { let server = MockServer::start().await; - Mock::given(method("POST")) - .and(path("/JSON/alert/view/alerts")) - .respond_with(ResponseTemplate::new(500).set_body_string("zap unavailable")) - .expect(1) - .mount(&server) - .await; + mount_alerts( + &server, + ResponseTemplate::new(500).set_body_string("zap unavailable"), + ) + .await; let client = ZapClient::new(server.uri(), API_KEY.to_string()).expect("client should be constructed"); @@ -114,12 +119,11 @@ async fn get_alerts_returns_unexpected_status_on_http_error() { async fn get_alerts_returns_parse_error_for_invalid_schema() { let server = MockServer::start().await; - Mock::given(method("POST")) - .and(path("/JSON/alert/view/alerts")) - .respond_with(ResponseTemplate::new(200).set_body_string("{\"alertItems\":[]}")) - .expect(1) - .mount(&server) - .await; + mount_alerts( + &server, + ResponseTemplate::new(200).set_body_string("{\"alertItems\":[]}"), + ) + .await; let client = ZapClient::new(server.uri(), API_KEY.to_string()).expect("client should be constructed"); diff --git a/src/zapclient/ascan_tests.rs b/src/zapclient/ascan_tests.rs index e9d92c7..10cf6f2 100644 --- a/src/zapclient/ascan_tests.rs +++ b/src/zapclient/ascan_tests.rs @@ -12,6 +12,33 @@ use super::{ZapClient, ZapClientError}; const API_KEY: &str = "test-api-key"; +async fn mount_ascan_scan(server: &MockServer, response: ResponseTemplate) { + Mock::given(method("POST")) + .and(path("/JSON/ascan/action/scan")) + .respond_with(response) + .expect(1) + .mount(server) + .await; +} + +async fn mount_ascan_status(server: &MockServer, response: ResponseTemplate) { + Mock::given(method("POST")) + .and(path("/JSON/ascan/view/status")) + .respond_with(response) + .expect(1) + .mount(server) + .await; +} + +async fn mount_ascan_stop(server: &MockServer, response: ResponseTemplate) { + Mock::given(method("POST")) + .and(path("/JSON/ascan/action/stop")) + .respond_with(response) + .expect(1) + .mount(server) + .await; +} + #[tokio::test] async fn start_active_scan_posts_to_zap_ascan_scan_endpoint() { let server = MockServer::start().await; @@ -43,12 +70,11 @@ async fn start_active_scan_posts_to_zap_ascan_scan_endpoint() { async fn start_active_scan_returns_unexpected_status_on_http_error() { let server = MockServer::start().await; - Mock::given(method("POST")) - .and(path("/JSON/ascan/action/scan")) - .respond_with(ResponseTemplate::new(500).set_body_string("zap unavailable")) - .expect(1) - .mount(&server) - .await; + mount_ascan_scan( + &server, + ResponseTemplate::new(500).set_body_string("zap unavailable"), + ) + .await; let client = ZapClient::new(server.uri(), API_KEY.to_string()).expect("client should be constructed"); @@ -71,12 +97,11 @@ async fn start_active_scan_returns_unexpected_status_on_http_error() { async fn start_active_scan_returns_parse_error_for_invalid_schema() { let server = MockServer::start().await; - Mock::given(method("POST")) - .and(path("/JSON/ascan/action/scan")) - .respond_with(ResponseTemplate::new(200).set_body_string("{\"scanId\":\"7\"}")) - .expect(1) - .mount(&server) - .await; + mount_ascan_scan( + &server, + ResponseTemplate::new(200).set_body_string("{\"scanId\":\"7\"}"), + ) + .await; let client = ZapClient::new(server.uri(), API_KEY.to_string()).expect("client should be constructed"); @@ -120,12 +145,11 @@ async fn get_active_scan_status_gets_zap_ascan_status_endpoint() { async fn get_active_scan_status_returns_unexpected_status_on_http_error() { let server = MockServer::start().await; - Mock::given(method("POST")) - .and(path("/JSON/ascan/view/status")) - .respond_with(ResponseTemplate::new(500).set_body_string("zap unavailable")) - .expect(1) - .mount(&server) - .await; + mount_ascan_status( + &server, + ResponseTemplate::new(500).set_body_string("zap unavailable"), + ) + .await; let client = ZapClient::new(server.uri(), API_KEY.to_string()).expect("client should be constructed"); @@ -148,12 +172,11 @@ async fn get_active_scan_status_returns_unexpected_status_on_http_error() { async fn get_active_scan_status_returns_parse_error_for_invalid_schema() { let server = MockServer::start().await; - Mock::given(method("POST")) - .and(path("/JSON/ascan/view/status")) - .respond_with(ResponseTemplate::new(200).set_body_string("{\"progress\":42}")) - .expect(1) - .mount(&server) - .await; + mount_ascan_status( + &server, + ResponseTemplate::new(200).set_body_string("{\"progress\":42}"), + ) + .await; let client = ZapClient::new(server.uri(), API_KEY.to_string()).expect("client should be constructed"); @@ -173,12 +196,11 @@ async fn get_active_scan_status_returns_parse_error_for_invalid_schema() { async fn get_active_scan_status_returns_unexpected_content_for_out_of_range_status() { let server = MockServer::start().await; - Mock::given(method("POST")) - .and(path("/JSON/ascan/view/status")) - .respond_with(ResponseTemplate::new(200).set_body_string("{\"status\":\"101\"}")) - .expect(1) - .mount(&server) - .await; + mount_ascan_status( + &server, + ResponseTemplate::new(200).set_body_string("{\"status\":\"101\"}"), + ) + .await; let client = ZapClient::new(server.uri(), API_KEY.to_string()).expect("client should be constructed"); @@ -223,12 +245,11 @@ async fn stop_active_scan_posts_to_zap_ascan_stop_endpoint() { async fn stop_active_scan_returns_unexpected_status_on_http_error() { let server = MockServer::start().await; - Mock::given(method("POST")) - .and(path("/JSON/ascan/action/stop")) - .respond_with(ResponseTemplate::new(500).set_body_string("zap unavailable")) - .expect(1) - .mount(&server) - .await; + mount_ascan_stop( + &server, + ResponseTemplate::new(500).set_body_string("zap unavailable"), + ) + .await; let client = ZapClient::new(server.uri(), API_KEY.to_string()).expect("client should be constructed"); diff --git a/src/zapclient/context_tests.rs b/src/zapclient/context_tests.rs index f54ba73..84a86de 100644 --- a/src/zapclient/context_tests.rs +++ b/src/zapclient/context_tests.rs @@ -12,6 +12,42 @@ use super::{ZapClient, ZapClientError}; const API_KEY: &str = "test-api-key"; +async fn mount_context_list(server: &MockServer, response: ResponseTemplate) { + Mock::given(method("POST")) + .and(path("/JSON/context/view/contextList")) + .respond_with(response) + .expect(1) + .mount(server) + .await; +} + +async fn mount_new_context(server: &MockServer, response: ResponseTemplate) { + Mock::given(method("POST")) + .and(path("/JSON/context/action/newContext")) + .respond_with(response) + .expect(1) + .mount(server) + .await; +} + +async fn mount_remove_context(server: &MockServer, response: ResponseTemplate) { + Mock::given(method("POST")) + .and(path("/JSON/context/action/removeContext")) + .respond_with(response) + .expect(1) + .mount(server) + .await; +} + +async fn mount_include_in_context(server: &MockServer, response: ResponseTemplate) { + Mock::given(method("POST")) + .and(path("/JSON/context/action/includeInContext")) + .respond_with(response) + .expect(1) + .mount(server) + .await; +} + #[tokio::test] async fn context_list_posts_to_zap_context_list_endpoint() { let server = MockServer::start().await; @@ -43,12 +79,11 @@ async fn context_list_posts_to_zap_context_list_endpoint() { #[tokio::test] async fn context_list_returns_unexpected_status_on_http_error() { let server = MockServer::start().await; - Mock::given(method("POST")) - .and(path("/JSON/context/view/contextList")) - .respond_with(ResponseTemplate::new(500).set_body_string("zap unavailable")) - .expect(1) - .mount(&server) - .await; + mount_context_list( + &server, + ResponseTemplate::new(500).set_body_string("zap unavailable"), + ) + .await; let client = ZapClient::new(server.uri(), API_KEY.to_string()).expect("client should be constructed"); @@ -70,12 +105,11 @@ async fn context_list_returns_unexpected_status_on_http_error() { #[tokio::test] async fn context_list_returns_parse_error_for_invalid_schema() { let server = MockServer::start().await; - Mock::given(method("POST")) - .and(path("/JSON/context/view/contextList")) - .respond_with(ResponseTemplate::new(200).set_body_string("{\"contexts\":[]}")) - .expect(1) - .mount(&server) - .await; + mount_context_list( + &server, + ResponseTemplate::new(200).set_body_string("{\"contexts\":[]}"), + ) + .await; let client = ZapClient::new(server.uri(), API_KEY.to_string()).expect("client should be constructed"); @@ -119,12 +153,11 @@ async fn new_context_posts_to_zap_new_context_endpoint() { #[tokio::test] async fn new_context_returns_unexpected_status_on_http_error() { let server = MockServer::start().await; - Mock::given(method("POST")) - .and(path("/JSON/context/action/newContext")) - .respond_with(ResponseTemplate::new(500).set_body_string("zap unavailable")) - .expect(1) - .mount(&server) - .await; + mount_new_context( + &server, + ResponseTemplate::new(500).set_body_string("zap unavailable"), + ) + .await; let client = ZapClient::new(server.uri(), API_KEY.to_string()).expect("client should be constructed"); @@ -146,12 +179,11 @@ async fn new_context_returns_unexpected_status_on_http_error() { #[tokio::test] async fn new_context_returns_parse_error_for_invalid_schema() { let server = MockServer::start().await; - Mock::given(method("POST")) - .and(path("/JSON/context/action/newContext")) - .respond_with(ResponseTemplate::new(200).set_body_string("{\"id\":\"7\"}")) - .expect(1) - .mount(&server) - .await; + mount_new_context( + &server, + ResponseTemplate::new(200).set_body_string("{\"id\":\"7\"}"), + ) + .await; let client = ZapClient::new(server.uri(), API_KEY.to_string()).expect("client should be constructed"); @@ -193,12 +225,11 @@ async fn remove_context_posts_to_zap_remove_context_endpoint() { #[tokio::test] async fn remove_context_returns_unexpected_status_on_http_error() { let server = MockServer::start().await; - Mock::given(method("POST")) - .and(path("/JSON/context/action/removeContext")) - .respond_with(ResponseTemplate::new(500).set_body_string("zap unavailable")) - .expect(1) - .mount(&server) - .await; + mount_remove_context( + &server, + ResponseTemplate::new(500).set_body_string("zap unavailable"), + ) + .await; let client = ZapClient::new(server.uri(), API_KEY.to_string()).expect("client should be constructed"); @@ -220,12 +251,11 @@ async fn remove_context_returns_unexpected_status_on_http_error() { #[tokio::test] async fn remove_context_returns_parse_error_for_invalid_schema() { let server = MockServer::start().await; - Mock::given(method("POST")) - .and(path("/JSON/context/action/removeContext")) - .respond_with(ResponseTemplate::new(200).set_body_string("{\"status\":\"OK\"}")) - .expect(1) - .mount(&server) - .await; + mount_remove_context( + &server, + ResponseTemplate::new(200).set_body_string("{\"status\":\"OK\"}"), + ) + .await; let client = ZapClient::new(server.uri(), API_KEY.to_string()).expect("client should be constructed"); @@ -244,12 +274,11 @@ async fn remove_context_returns_parse_error_for_invalid_schema() { #[tokio::test] async fn remove_context_returns_unexpected_content_when_result_is_not_ok() { let server = MockServer::start().await; - Mock::given(method("POST")) - .and(path("/JSON/context/action/removeContext")) - .respond_with(ResponseTemplate::new(200).set_body_string("{\"Result\":\"FAIL\"}")) - .expect(1) - .mount(&server) - .await; + mount_remove_context( + &server, + ResponseTemplate::new(200).set_body_string("{\"Result\":\"FAIL\"}"), + ) + .await; let client = ZapClient::new(server.uri(), API_KEY.to_string()).expect("client should be constructed"); @@ -271,12 +300,11 @@ async fn remove_context_returns_unexpected_content_when_result_is_not_ok() { #[tokio::test] async fn remove_context_rejects_lowercase_ok_result() { let server = MockServer::start().await; - Mock::given(method("POST")) - .and(path("/JSON/context/action/removeContext")) - .respond_with(ResponseTemplate::new(200).set_body_string("{\"Result\":\"ok\"}")) - .expect(1) - .mount(&server) - .await; + mount_remove_context( + &server, + ResponseTemplate::new(200).set_body_string("{\"Result\":\"ok\"}"), + ) + .await; let client = ZapClient::new(server.uri(), API_KEY.to_string()).expect("client should be constructed"); @@ -324,12 +352,11 @@ async fn include_in_context_posts_to_zap_include_in_context_endpoint() { #[tokio::test] async fn include_in_context_returns_unexpected_status_on_http_error() { let server = MockServer::start().await; - Mock::given(method("POST")) - .and(path("/JSON/context/action/includeInContext")) - .respond_with(ResponseTemplate::new(500).set_body_string("zap unavailable")) - .expect(1) - .mount(&server) - .await; + mount_include_in_context( + &server, + ResponseTemplate::new(500).set_body_string("zap unavailable"), + ) + .await; let client = ZapClient::new(server.uri(), API_KEY.to_string()).expect("client should be constructed"); @@ -351,12 +378,11 @@ async fn include_in_context_returns_unexpected_status_on_http_error() { #[tokio::test] async fn include_in_context_returns_parse_error_for_invalid_schema() { let server = MockServer::start().await; - Mock::given(method("POST")) - .and(path("/JSON/context/action/includeInContext")) - .respond_with(ResponseTemplate::new(200).set_body_string("{\"status\":\"OK\"}")) - .expect(1) - .mount(&server) - .await; + mount_include_in_context( + &server, + ResponseTemplate::new(200).set_body_string("{\"status\":\"OK\"}"), + ) + .await; let client = ZapClient::new(server.uri(), API_KEY.to_string()).expect("client should be constructed"); @@ -375,12 +401,11 @@ async fn include_in_context_returns_parse_error_for_invalid_schema() { #[tokio::test] async fn include_in_context_returns_unexpected_content_when_result_is_not_ok() { let server = MockServer::start().await; - Mock::given(method("POST")) - .and(path("/JSON/context/action/includeInContext")) - .respond_with(ResponseTemplate::new(200).set_body_string("{\"Result\":\"FAIL\"}")) - .expect(1) - .mount(&server) - .await; + mount_include_in_context( + &server, + ResponseTemplate::new(200).set_body_string("{\"Result\":\"FAIL\"}"), + ) + .await; let client = ZapClient::new(server.uri(), API_KEY.to_string()).expect("client should be constructed"); @@ -402,12 +427,11 @@ async fn include_in_context_returns_unexpected_content_when_result_is_not_ok() { #[tokio::test] async fn include_in_context_rejects_lowercase_ok_result() { let server = MockServer::start().await; - Mock::given(method("POST")) - .and(path("/JSON/context/action/includeInContext")) - .respond_with(ResponseTemplate::new(200).set_body_string("{\"Result\":\"ok\"}")) - .expect(1) - .mount(&server) - .await; + mount_include_in_context( + &server, + ResponseTemplate::new(200).set_body_string("{\"Result\":\"ok\"}"), + ) + .await; let client = ZapClient::new(server.uri(), API_KEY.to_string()).expect("client should be constructed"); From 288cf88a49312bcbf96d33fa5e0e76b35ac7b54b Mon Sep 17 00:00:00 2001 From: Timo Pollmeier Date: Thu, 25 Jun 2026 09:00:27 +0200 Subject: [PATCH 4/5] Reformat with cargo fmt --- src/scan/worker_tests.rs | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/scan/worker_tests.rs b/src/scan/worker_tests.rs index d29ad07..b8b9533 100644 --- a/src/scan/worker_tests.rs +++ b/src/scan/worker_tests.rs @@ -244,18 +244,15 @@ async fn mount_ascan_status_with_delay( async fn mount_ascan_stop(server: &MockServer, status_code: u16, expected_calls: u64) { Mock::given(method("POST")) .and(path("/JSON/ascan/action/stop")) - .respond_with( - ResponseTemplate::new(status_code) - .set_body_raw( - if status_code == 200 { - r#"{"Result":"OK"}"# - } else { - r#"{"code":"internal"}"# - }, - "application/json", - ), - ) - .expect(expected_calls) + .respond_with(ResponseTemplate::new(status_code).set_body_raw( + if status_code == 200 { + r#"{"Result":"OK"}"# + } else { + r#"{"code":"internal"}"# + }, + "application/json", + )) + .expect(expected_calls) .mount(server) .await; } From bc8c18c294a6c54b3bfaf2fe65adaed2afda3a88 Mon Sep 17 00:00:00 2001 From: Timo Pollmeier Date: Thu, 25 Jun 2026 14:07:08 +0200 Subject: [PATCH 5/5] Add rules for HTTP mocks to AGENTS.md Co-authored-by: AI (copilot/full) --- AGENTS.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index b167402..6d86b4a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -22,3 +22,21 @@ Testing guidelines for specific modules or features may be found in the `doc/spe - Always document the intention of tests. Test names, fixture names, or short comments should make clear which behavior, regression, or contract the test protects. + +### HTTP mock conventions + +When using Wiremock (or equivalent HTTP mocking), follow these conventions: + +- Prefer endpoint-level helper functions that mount one endpoint behavior at a + time (for example, one helper per ZAP endpoint path). +- Keep helper abstractions shallow. Avoid deep/builder-style layers that hide + scenario intent; test setup should remain readable without tracing multiple + indirections. +- Mount endpoints in the same order they are expected to be requested in the + scenario flow (for example, context setup -> spider -> active scan -> alerts + -> passive scan -> cleanup). +- Keep uncommon expectations (special bodies, unusual `expect(n)` counts, + explicit negative cases) close to the specific test scenario rather than + hiding them behind generic helpers. +- When introducing shared mock helpers, use them only for obvious repetition; + prefer local test-file helpers first.