silicon-commit-client 0.4.1

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":{"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(())
}