silicon-commit-client 0.2.2

Stateless Rust client for Silicon Commit
Documentation
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":{},"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(())
}