context69-sdk 0.5.0

Async Rust SDK for the Context69 HTTP API.
Documentation
use axum::http::{Method, StatusCode, header};
use context69_contracts::{LibraryTextContentFormat, MoveFolderRequest, UpsertLibraryTextRequest};
use reqwest::multipart::Part;
use serde_json::json;
use uuid::Uuid;

use super::support::{client, spawn_empty, spawn_json};

fn tree_json() -> serde_json::Value {
    json!({
        "root": {
            "group_key": "", "group_path": "", "visibility": "private",
            "folder_id": null, "parent_folder_id": null, "name": "Library",
            "path": "/", "processing_count": 0, "children": [], "files": []
        }
    })
}

fn file_json(file_id: Uuid) -> serde_json::Value {
    json!({
        "file_id": file_id, "group_key": "ops", "group_path": "ops/platform",
        "visibility": "private", "folder_id": null, "folder_path": "/",
        "filename": "runbook.md", "media_type": "text/markdown", "size_bytes": 8,
        "sha256": "abc", "ingest_status": "succeeded", "error_message": null,
        "created_at": "2026-07-10T00:00:00Z", "updated_at": "2026-07-10T00:00:00Z",
        "ingested_at": "2026-07-10T00:00:00Z", "sections": [], "jobs": []
    })
}

#[tokio::test]
async fn global_library_tree_uses_global_scope() {
    let (base_url, captured) = spawn_json(StatusCode::OK, &tree_json()).await;
    let tree = client(&base_url).library().tree().await.expect("tree");
    let request = captured.await.expect("captured request");
    assert_eq!(tree.root.path, "/");
    assert_eq!(request.uri.path(), "/v1/library/tree");
}

#[tokio::test]
async fn group_library_text_upsert_uses_group_scope() {
    let response = json!({"files": [], "jobs": []});
    let (base_url, captured) = spawn_json(StatusCode::OK, &response).await;
    let input = UpsertLibraryTextRequest {
        external_id: "incident-42".to_string(),
        folder_id: None,
        title: "Incident".to_string(),
        content: "body".to_string(),
        content_format: LibraryTextContentFormat::Markdown,
        source_uri: None,
        summary: None,
        published_at: None,
        metadata_json: json!({}),
    };
    client(&base_url)
        .group("ops/platform")
        .library()
        .texts()
        .upsert(&input)
        .await
        .expect("upsert text");
    let request = captured.await.expect("captured request");
    assert_eq!(request.method, Method::PUT);
    assert_eq!(
        request.uri.path(),
        "/v1/groups/by-path/ops%2Fplatform/library/texts"
    );
}

#[tokio::test]
async fn temporary_group_library_file_chain_gets_file() {
    let file_id = Uuid::new_v4();
    let (base_url, captured) = spawn_json(StatusCode::OK, &file_json(file_id)).await;
    let file = client(&base_url)
        .group("ops/platform")
        .library()
        .file(file_id)
        .get()
        .await
        .expect("get file");
    let request = captured.await.expect("captured request");
    assert_eq!(file.file_id, file_id);
    assert_eq!(
        request.uri.path(),
        format!("/v1/groups/by-path/ops%2Fplatform/library/files/{file_id}")
    );
}

#[tokio::test]
async fn library_folder_resource_moves() {
    let folder_id = Uuid::new_v4();
    let response = json!({
        "folder_id": folder_id, "group_key": "", "group_path": "",
        "visibility": "private", "parent_folder_id": null, "name": "Docs", "path": "/Docs",
        "created_at": "2026-07-10T00:00:00Z", "updated_at": "2026-07-10T00:00:00Z"
    });
    let (base_url, captured) = spawn_json(StatusCode::OK, &response).await;
    client(&base_url)
        .library()
        .folder(folder_id)
        .move_to(&MoveFolderRequest {
            target_folder_id: None,
        })
        .await
        .expect("move folder");
    let request = captured.await.expect("captured request");
    assert_eq!(request.method, Method::POST);
    assert_eq!(
        request.uri.path(),
        format!("/v1/library/folders/{folder_id}/move")
    );
}

#[tokio::test]
async fn library_files_upload_builds_multipart_form() {
    let folder_id = Uuid::new_v4();
    let response = json!({"files": [], "jobs": []});
    let (base_url, captured) = spawn_json(StatusCode::OK, &response).await;
    client(&base_url)
        .library()
        .files()
        .upload(
            Some(folder_id),
            vec![Part::text("hello").file_name("note.md")],
        )
        .await
        .expect("upload files");
    let request = captured.await.expect("captured request");
    assert_eq!(request.uri.path(), "/v1/library/files/upload");
    assert!(
        request.headers[header::CONTENT_TYPE]
            .to_str()
            .unwrap()
            .starts_with("multipart/form-data; boundary=")
    );
    let body = String::from_utf8_lossy(&request.body);
    assert!(body.contains(&folder_id.to_string()));
    assert!(body.contains("filename=\"note.md\""));
}

#[tokio::test]
async fn library_file_delete_uses_item_endpoint() {
    let file_id = Uuid::new_v4();
    let (base_url, captured) = spawn_empty(StatusCode::NO_CONTENT).await;
    client(&base_url)
        .library()
        .file(file_id)
        .delete()
        .await
        .expect("delete file");
    assert_eq!(captured.await.unwrap().method, Method::DELETE);
}