context69-sdk 0.5.0

Async Rust SDK for the Context69 HTTP API.
Documentation
use axum::http::StatusCode;
use context69_contracts::{ApiErrorResponse, SearchRequest};

use super::{
    super::{Context69Client, transport::parse_api_error_message},
    support::{client, spawn_json},
};
use crate::Error;

#[test]
fn builder_normalizes_base_url_with_trailing_slash() {
    let client = Context69Client::builder()
        .base_url("http://localhost:8096")
        .expect("base url")
        .build()
        .expect("client");
    assert_eq!(
        client.url("/healthz").expect("healthz url").as_str(),
        "http://localhost:8096/healthz"
    );
}

#[test]
fn builder_rejects_non_pat_token() {
    let error = Context69Client::builder()
        .base_url("http://localhost:8096")
        .expect("base url")
        .with_personal_access_token("plain-access-token")
        .expect_err("invalid token should fail");
    assert!(matches!(error, Error::InvalidPersonalAccessToken(_)));
}

#[test]
fn parses_api_error_body() {
    assert_eq!(
        parse_api_error_message(r#"{"error":"missing bearer token"}"#),
        Some("missing bearer token".to_string())
    );
}

#[tokio::test]
async fn protected_api_requires_pat() {
    let client = Context69Client::builder()
        .base_url("http://localhost:8096")
        .expect("base url")
        .build()
        .expect("client");
    let error = client.groups().list().await.expect_err("requires PAT");
    assert!(matches!(error, Error::AuthenticationRequired));
}

#[tokio::test]
async fn unauthorized_response_does_not_refresh() {
    let (base_url, _) = spawn_json(
        StatusCode::UNAUTHORIZED,
        &ApiErrorResponse {
            error: "expired".to_string(),
        },
    )
    .await;
    let error = client(&base_url)
        .search()
        .execute(&search_request("unauthorized"))
        .await
        .expect_err("should fail");
    match error {
        Error::HttpStatus {
            status, api_error, ..
        } => {
            assert_eq!(status, StatusCode::UNAUTHORIZED);
            assert_eq!(api_error.as_deref(), Some("expired"));
        }
        other => panic!("unexpected error: {other}"),
    }
}

fn search_request(query: &str) -> SearchRequest {
    SearchRequest {
        query: query.to_string(),
        limit: 8,
        source_key: None,
        group_path: None,
        published_after: None,
        published_before: None,
    }
}