link-assistant-router 1.4.3

Link.Assistant.Router — Claude MAX OAuth proxy and token gateway for Anthropic APIs
Documentation
use super::*;

#[test]
fn managed_client_bindings_are_signed_stored_and_validated() {
    let manager = TokenManager::new("binding-secret");
    let (token, id) = manager
        .issue_with_id(&IssueRequest {
            ttl_hours: 1,
            label: "managed-codex",
            account: Some("primary"),
            client_kind: Some("codex"),
            principal_id: Some("primary"),
            ..IssueRequest::default()
        })
        .expect("issue bound token");

    let claims = manager
        .validate_token(&token)
        .expect("validate bound token");
    assert_eq!(claims.client_kind.as_deref(), Some("codex"));
    assert_eq!(claims.principal_id.as_deref(), Some("primary"));
    let record = manager
        .store()
        .get(&id)
        .expect("read store")
        .expect("record");
    assert_eq!(record.client_kind.as_deref(), Some("codex"));
    assert_eq!(record.principal_id.as_deref(), Some("primary"));
}

#[test]
fn generic_and_admin_tokens_have_no_implicit_client_binding() {
    let manager = TokenManager::new("unbound-secret");
    for token in [
        manager.issue_token(1, "generic").expect("generic token"),
        manager
            .issue_admin_token(1, "administrator")
            .expect("admin token"),
    ] {
        let claims = manager.validate_token(&token).expect("valid token");
        assert_eq!(claims.client_kind, None);
        assert_eq!(claims.principal_id, None);
    }
}

#[test]
fn rotation_preserves_client_and_principal_without_a_widening_override() {
    let manager = TokenManager::new("rotation-binding-secret");
    let (_, id) = manager
        .issue_with_id(&IssueRequest {
            ttl_hours: 1,
            label: "managed-claude",
            account: Some("primary"),
            client_kind: Some("claude"),
            principal_id: Some("primary"),
            ..IssueRequest::default()
        })
        .expect("issue bound token");

    let rotated = manager
        .rotate_token_with(
            &id,
            &RotateOverrides {
                ttl_hours: Some(2),
                ..RotateOverrides::default()
            },
        )
        .expect("rotate token");
    let claims = manager
        .validate_token(&rotated)
        .expect("validate replacement");
    assert_eq!(claims.client_kind.as_deref(), Some("claude"));
    assert_eq!(claims.principal_id.as_deref(), Some("primary"));
}

#[test]
fn a_store_binding_that_disagrees_with_the_signed_claim_fails_closed() {
    let manager = TokenManager::new("binding-mismatch-secret");
    let (token, id) = manager
        .issue_with_id(&IssueRequest {
            ttl_hours: 1,
            label: "managed-codex",
            account: Some("primary"),
            client_kind: Some("codex"),
            principal_id: Some("primary"),
            ..IssueRequest::default()
        })
        .expect("issue bound token");
    let store = manager.store();
    let mut record = store.get(&id).unwrap().unwrap();
    record.client_kind = Some("claude".to_string());
    store.put(record).expect("replace record");

    assert!(matches!(
        manager.validate_token(&token),
        Err(TokenError::Invalid(message)) if message.contains("binding")
    ));
}

#[test]
fn incomplete_or_unknown_client_bindings_are_rejected_at_issue_time() {
    let manager = TokenManager::new("invalid-binding-secret");
    for request in [
        IssueRequest {
            ttl_hours: 1,
            client_kind: Some("codex"),
            ..IssueRequest::default()
        },
        IssueRequest {
            ttl_hours: 1,
            principal_id: Some("primary"),
            ..IssueRequest::default()
        },
        IssueRequest {
            ttl_hours: 1,
            client_kind: Some("invented-client"),
            principal_id: Some("primary"),
            ..IssueRequest::default()
        },
    ] {
        assert!(request.validate().is_err());
    }
    assert_eq!(manager.list_tokens().expect("store remains empty").len(), 0);
}