use serde_json::json;
use silicon_commit_client::{Client, Error, Mutation};
use wiremock::{
Mock, MockServer, ResponseTemplate,
matchers::{body_json, header, method, path},
};
#[tokio::test]
async fn login_uses_versioned_route_keeps_test_context_and_stable_retry_key()
-> Result<(), Box<dyn std::error::Error>> {
let server = MockServer::start().await;
Mock::given(method("POST")).and(path("/api/v1/auth/login"))
.and(header("x-testing-environment-key","abcdefghijklmnopqrstuvwxyz123456"))
.and(header("idempotency-key","reusable-login-key-001"))
.and(body_json(json!({"slt":"slt_one_time"})))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({"access_token":"oat_secret","refresh_token":"ort_secret","token_type":"Bearer","expires_in":3600,"scope":"profile.read","actor":{"type":"carbon","public_id":"c:person"},"org_id":"tos"}))).expect(2).mount(&server).await;
let client = Client::new(&server.uri())?
.with_test_key("abcdefghijklmnopqrstuvwxyz123456")?
.with_mutation(Mutation::with_key("reusable-login-key-001")?);
for _ in 0..2 {
let session = client.login_with_slt("slt_one_time").await?;
assert_eq!(session.access_token, "oat_secret");
assert!(!format!("{session:?}").contains("oat_secret"));
}
Ok(())
}
#[tokio::test]
async fn conditional_writes_and_empty_deletes_match_the_backend_contract()
-> Result<(), Box<dyn std::error::Error>> {
let server = MockServer::start().await;
Mock::given(method("PUT"))
.and(path("/api/v1/notification-settings"))
.and(header("if-match", "\"0\""))
.and(header("x-org-id", "tos"))
.and(header("authorization", "Bearer oat_private"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({"version":1})))
.expect(1)
.mount(&server)
.await;
Mock::given(method("DELETE"))
.and(path("/api/v1/todos/id"))
.respond_with(ResponseTemplate::new(204))
.expect(1)
.mount(&server)
.await;
let client = Client::new(&server.uri())?
.with_org_id("tos")
.with_bearer("oat_private")
.with_mutation(Mutation::new().if_match(0)?);
assert_eq!(
client
.update_notification_settings(
&json!({"webhook_url":null,"todo_list_subscription":null})
)
.await?["version"],
1
);
assert!(client.delete_todo("id").await?.is_null());
assert!(!format!("{client:?}").contains("oat_private"));
Ok(())
}
#[tokio::test]
async fn resource_ids_are_single_segments_and_redirects_are_not_followed()
-> Result<(), Box<dyn std::error::Error>> {
let server = MockServer::start().await;
let target = MockServer::start().await;
Mock::given(method("GET"))
.respond_with(ResponseTemplate::new(302).insert_header("location", target.uri()))
.expect(1)
.mount(&server)
.await;
let client = Client::new(&server.uri())?.with_bearer("private");
assert!(matches!(
client.get_project("name/diary?admin=true").await,
Err(Error::Api { .. })
));
let requests = server.received_requests().await.unwrap();
assert_eq!(
requests[0].url.path(),
"/api/v1/projects/name%2Fdiary%3Fadmin=true"
);
assert!(target.received_requests().await.unwrap().is_empty());
Ok(())
}
#[tokio::test]
async fn malformed_test_keys_and_base_urls_cannot_reach_a_production_route()
-> Result<(), Box<dyn std::error::Error>> {
let server = MockServer::start().await;
let client = Client::new(&server.uri())?.with_test_key("bad-key");
assert!(matches!(client, Err(Error::Invalid(_))));
assert!(server.received_requests().await.unwrap().is_empty());
for url in [
"file:///tmp/a",
"https://user:secret@example.com/",
"https://example.com/other",
"https://example.com/?token=x",
] {
assert!(Client::new(url).is_err());
}
for url in [
"https://example.com",
"https://example.com/api/v1",
"https://example.com/api/v1/",
] {
assert_eq!(
Client::new(url)?.base_url().as_str(),
"https://example.com/api/v1/"
);
}
Ok(())
}
#[test]
fn mutation_accepts_backend_minimum_idempotency_key_length() {
assert!(Mutation::with_key("12345678").is_ok());
assert!(Mutation::with_key("1234567").is_err());
}
#[tokio::test]
async fn login_status_distinguishes_rejected_credentials_from_service_failures()
-> Result<(), Box<dyn std::error::Error>> {
for status in [401, 403, 503] {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/api/v1/auth/status"))
.respond_with(
ResponseTemplate::new(status).set_body_json(json!({"error":{"code":"test_error"}})),
)
.expect(1)
.mount(&server)
.await;
let result = Client::new(&server.uri())?
.with_bearer("invalid")
.login_status()
.await;
if status == 401 {
assert_eq!(result?["authenticated"], false);
} else {
assert!(result.is_err());
}
}
Ok(())
}
#[tokio::test]
async fn collaborative_consumer_negotiates_and_preserves_secret_selection()
-> Result<(), Box<dyn std::error::Error>> {
let server = MockServer::start().await;
let secret = format!("ask_{}", "a".repeat(43));
Mock::given(method("POST"))
.and(path("/api/v1/projects/project/tasks/task/claim"))
.and(header("x-commit-supported-versions", "1"))
.and(header("x-testing-environment-key", secret.as_str()))
.and(header("x-commit-telemetry", "off"))
.respond_with(
ResponseTemplate::new(200)
.insert_header("x-commit-api-version", "1")
.set_body_json(
json!({"todo_id":"linked","assigned_to":{"type":"carbon","id":"alice"}}),
),
)
.expect(1)
.mount(&server)
.await;
let c = Client::new(&server.uri())?
.with_test_app_secret(secret)?
.with_telemetry(false);
assert_eq!(
c.claim_project_task("project", "task").await?["todo_id"],
"linked"
);
server.reset().await;
Mock::given(method("GET"))
.respond_with(
ResponseTemplate::new(200)
.insert_header("x-commit-api-version", "2")
.set_body_json(json!({})),
)
.expect(1)
.mount(&server)
.await;
assert!(matches!(
c.get_project("project").await,
Err(Error::Invalid(_))
));
Ok(())
}
#[tokio::test]
async fn testing_login_sends_selected_org_without_an_existing_bearer()
-> Result<(), Box<dyn std::error::Error>> {
let server = MockServer::start().await;
Mock::given(method("POST"))
.and(path("/api/v1/auth/login"))
.and(body_json(json!({"slt":"si:helper","org_id":"team-b"})))
.and(header("x-org-id", "team-b"))
.and(header(
"x-testing-environment-key",
"abcdefghijklmnopqrstuvwxyz123456",
))
.and(|r: &wiremock::Request| !r.headers.contains_key("authorization"))
.respond_with(ResponseTemplate::new(200).set_body_json(
json!({"access_token":"access", "refresh_token":"refresh",
"token_type":"Bearer","expires_in":3600,"scope":"self.identity.read",
"actor":{"type":"silicon","public_id":"si:helper"},"org_id":"team-b"}),
))
.expect(1)
.mount(&server)
.await;
let tokens = Client::new(&server.uri())?
.with_bearer("old-credentials")
.with_test_key("abcdefghijklmnopqrstuvwxyz123456")?
.with_org_id("team-b")
.login_with_slt("si:helper")
.await?;
assert_eq!(tokens.org_id.as_deref(), Some("team-b"));
Ok(())
}
#[tokio::test]
async fn invalid_session_responses_are_rejected_for_login_and_refresh()
-> Result<(), Box<dyn std::error::Error>> {
let baseline = json!({"access_token":"access", "refresh_token":"refresh", "token_type":"Bearer", "expires_in":3600,
"scope":"self.identity.read", "actor":{"type":"carbon","public_id":"c:person"}, "org_id":"tos"});
for (field, value) in [
("org_id", json!(null)),
("org_id", json!("ab")),
("org_id", json!("a".repeat(51))),
("org_id", json!("other")),
(
"actor",
json!({"type":"application","public_id":"c:person"}),
),
("actor", json!({"type":"carbon","id":"person"})),
("actor", json!({"type":"carbon","public_id":"si:person"})),
("scope", json!("self.identity.read obo:write")),
("access_token", json!("")),
("refresh_token", json!("")),
("expires_in", json!(0)),
("token_type", json!("Other")),
] {
let server = MockServer::start().await;
let mut invalid = baseline.clone();
invalid[field] = value;
Mock::given(method("POST"))
.respond_with(ResponseTemplate::new(200).set_body_json(invalid))
.expect(2)
.mount(&server)
.await;
let client = Client::new(&server.uri())?.with_org_id("tos");
assert!(
client.login_with_slt("code").await.is_err(),
"login accepted {field}"
);
assert!(
client.refresh_session("refresh").await.is_err(),
"refresh accepted {field}"
);
}
Ok(())
}