use reqwest::Method;
use serde_json::json;
use wiremock::matchers::{body_json, header, method, path, query_param};
use wiremock::{Mock, MockServer, ResponseTemplate};
use crate::transport::{encode_segment, Transport};
use crate::Error;
fn transport(server: &MockServer) -> Transport {
Transport::new(server.uri(), "secret-key".into())
}
#[tokio::test]
async fn get_sends_api_key_user_agent_and_query() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/thing"))
.and(header("X-API-KEY", "secret-key"))
.and(header("Accept", "application/json"))
.and(query_param("a", "1"))
.and(query_param("resources", "R1"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!([1, 2])))
.expect(1)
.mount(&server)
.await;
let out: Vec<i32> = transport(&server)
.get_json(
"/thing",
&[
("a", "1".into()),
("resources", "R1".into()),
("resources", "R2".into()),
],
)
.await
.unwrap();
assert_eq!(out, vec![1, 2]);
let reqs = server.received_requests().await.unwrap();
let ua = reqs[0]
.headers
.get("user-agent")
.unwrap()
.to_str()
.unwrap()
.to_string();
assert!(ua.starts_with("dimescheduler-rust/"), "{ua}");
assert!(reqs[0].url.query().unwrap().contains("resources=R2"));
}
#[tokio::test]
async fn send_json_posts_body_and_decodes() {
let server = MockServer::start().await;
Mock::given(method("PUT"))
.and(path("/thing"))
.and(body_json(json!({ "k": "v" })))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({ "ok": true })))
.expect(1)
.mount(&server)
.await;
let out: serde_json::Value = transport(&server)
.send_json(Method::PUT, "/thing", &json!({ "k": "v" }))
.await
.unwrap();
assert_eq!(out["ok"], true);
}
#[tokio::test]
async fn non_success_maps_to_api_error_with_extracted_message() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/thing"))
.respond_with(
ResponseTemplate::new(400).set_body_json(
json!({ "Error": "bad", "Description": "jobNo missing", "Code": "E1" }),
),
)
.mount(&server)
.await;
let err = transport(&server)
.get_json::<serde_json::Value>("/thing", &[])
.await
.unwrap_err();
match err {
Error::Api { status, body } => {
assert_eq!(status, 400);
assert_eq!(body, "jobNo missing (code: E1)");
}
other => panic!("expected Api error, got {other:?}"),
}
}
#[tokio::test]
async fn empty_body_ok_for_unit() {
let server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/thing"))
.respond_with(ResponseTemplate::new(200))
.mount(&server)
.await;
transport(&server)
.send_empty(Method::POST, "/thing", &json!({}))
.await
.unwrap();
}
#[tokio::test]
async fn empty_body_on_typed_response_is_http_error() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/thing"))
.respond_with(ResponseTemplate::new(200))
.mount(&server)
.await;
let err = transport(&server)
.get_json::<Vec<i32>>("/thing", &[])
.await
.unwrap_err();
assert!(matches!(err, Error::Http(_)), "{err:?}");
}
#[test]
fn encode_segment_matches_encode_uri_component() {
assert_eq!(encode_segment("APT 1/2"), "APT%201%2F2");
assert_eq!(encode_segment("JOB-001_a.b~c"), "JOB-001_a.b~c");
assert_eq!(encode_segment("a?b#c"), "a%3Fb%23c");
}