use cleanlib_client::transport;
use wiremock::matchers::{method, path_regex};
use wiremock::{Mock, MockServer, ResponseTemplate};
fn audit_wire_fixture() -> serde_json::Value {
serde_json::json!({
"window": {
"since": "2026-05-22T00:00:00Z",
"until": "2026-05-23T00:00:00Z"
},
"records": [{
"request_id": "01936b8f-3c4a-7a12-9c00-000000000001",
"organization_id": "org-42",
"correlation_id": "corr-cleanlib-366",
"customer_ip_hashed": "sha256:aaaa",
"ecosystem": "npm",
"package_name": "lodash",
"package_version": "4.17.21",
"variant": "default",
"user_agent": "cleanlib-cli/0.1.4",
"policy_decision": "ALLOW",
"verdict_id": "01936b8f-3c4a-7a12-9c00-0000000000aa",
"verdict_source": "ALLOWED_NO_FINDINGS",
"policy_rule_id_matched": "rule-42",
"risk_acceptance_status": "NONE",
"reasoning": "no findings above threshold",
"gcs_hit": true,
"gcs_object_path": "gs://catalog/npm/lodash/4.17.21.json",
"bytes_served": 8192,
"request_at": "2026-05-22T10:00:00Z",
"ingest_at": null,
"gcs_at": null,
"verdict_at": "2026-05-22T10:00:01Z",
"policy_eval_at": "2026-05-22T10:00:02Z",
"response_at": "2026-05-22T10:00:03Z",
"app_version": "1.2.3"
}],
"record_count": 1,
"per_route": {"/v1/customer/verdicts/npm": 1},
"backend_status": "wired"
})
}
#[tokio::test]
async fn audit_response_deserializes_every_field_from_app_wire_shape() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path_regex(r"^/v1/audit"))
.respond_with(ResponseTemplate::new(200).set_body_json(audit_wire_fixture()))
.mount(&server)
.await;
let client = transport::Client::new(&server.uri(), Some("test-bearer".into()))
.expect("build test client");
let resp = client
.audit(Some("2026-05-22T00:00:00Z"), Some("ALLOW"), Some("npm"))
.await
.expect("audit call succeeds against wire-shape fixture");
assert_eq!(resp.record_count, 1, "record_count carrier lost");
assert_eq!(resp.backend_status, "wired", "backend_status carrier lost");
assert_eq!(
resp.window.since.as_deref(),
Some("2026-05-22T00:00:00Z"),
"window.since carrier lost"
);
assert_eq!(
resp.window.until.as_deref(),
Some("2026-05-23T00:00:00Z"),
"window.until carrier lost"
);
assert_eq!(
resp.per_route.get("/v1/customer/verdicts/npm"),
Some(&1),
"per_route breakdown lost"
);
assert_eq!(resp.records.len(), 1, "records array lost");
let e = &resp.records[0];
assert_eq!(
e.request_id, "01936b8f-3c4a-7a12-9c00-000000000001",
"request_id: silent-drop regression"
);
assert_eq!(
e.request_at, "2026-05-22T10:00:00Z",
"request_at: silent-drop regression (pre-fix name was `at`)"
);
assert_eq!(
e.ecosystem, "npm",
"ecosystem: silent-drop regression"
);
assert_eq!(
e.package_name, "lodash",
"package_name: silent-drop regression (pre-fix name was `package`)"
);
assert_eq!(
e.package_version, "4.17.21",
"package_version: silent-drop regression (pre-fix name was `version`)"
);
assert_eq!(
e.policy_decision, "ALLOW",
"policy_decision: silent-drop regression (pre-fix name was `decision`)"
);
assert_eq!(
e.reasoning, "no findings above threshold",
"reasoning: silent-drop regression (pre-fix name was `reason`)"
);
assert_eq!(e.correlation_id, "corr-cleanlib-366");
assert_eq!(e.variant, "default");
assert_eq!(e.verdict_id, "01936b8f-3c4a-7a12-9c00-0000000000aa");
assert_eq!(e.verdict_source, "ALLOWED_NO_FINDINGS");
assert_eq!(e.policy_rule_id_matched, "rule-42");
assert_eq!(e.risk_acceptance_status, "NONE");
assert!(e.gcs_hit, "gcs_hit: silent-drop regression");
assert_eq!(e.verdict_at, "2026-05-22T10:00:01Z");
assert_eq!(e.response_at, "2026-05-22T10:00:03Z");
assert_eq!(e.app_version, "1.2.3");
}
#[tokio::test]
async fn audit_response_carries_not_wired_backend_status() {
let server = MockServer::start().await;
let body = serde_json::json!({
"window": {"since": null, "until": null},
"records": [],
"record_count": 0,
"per_route": {},
"backend_status": "not_wired"
});
Mock::given(method("GET"))
.and(path_regex(r"^/v1/audit"))
.respond_with(ResponseTemplate::new(200).set_body_json(body))
.mount(&server)
.await;
let client = transport::Client::new(&server.uri(), Some("test-bearer".into()))
.expect("build test client");
let resp = client
.audit(None, None, None)
.await
.expect("audit call succeeds on not_wired path");
assert!(resp.records.is_empty());
assert_eq!(resp.record_count, 0);
assert_eq!(
resp.backend_status, "not_wired",
"backend_status honesty signal lost"
);
}