akribes-sdk 0.22.6

Rust client SDK for the Akribes workflow server
Documentation
//! Integration tests for the `ConvertClient` sub-client (multipart upload to
//! the server's `/convert` and `/projects/{id}/convert` routes).
//!
//! The server handler (`crates/akribes-server/src/handlers/convert.rs`) reads
//! a single multipart field named `file` and echoes back a JSON
//! `{markdown, document_id?, filename?}`. These tests assert the SDK targets
//! the right route, sends multipart with the right content-type, and decodes
//! the response.

use akribes_sdk::AkribesClient;
use mockito::{Matcher, Server};

fn make_client(server: &Server) -> AkribesClient {
    AkribesClient::builder(server.url())
        .project_id(7)
        .name("convert-test")
        .id("convert-id")
        .build()
}

#[tokio::test]
async fn convert_file_posts_multipart_to_unscoped_route() {
    let mut server = Server::new_async().await;
    // Unscoped /convert. Body must be multipart/form-data and carry the
    // filename + bytes inside a `file` part.
    let _m = server
        .mock("POST", "/convert")
        .match_header("content-type", Matcher::Regex("multipart/form-data".into()))
        .match_body(Matcher::AllOf(vec![
            Matcher::Regex(r#"name="file""#.into()),
            Matcher::Regex(r#"filename="report\.pdf""#.into()),
            Matcher::Regex("%PDF-stub".into()),
        ]))
        .with_status(200)
        .with_header("content-type", "application/json")
        .with_body(r##"{"markdown":"# Heading","document_id":"doc_1","filename":"report.pdf"}"##)
        .create_async()
        .await;

    let result = make_client(&server)
        .convert()
        .convert_file("report.pdf", b"%PDF-stub".to_vec())
        .await
        .unwrap();
    assert_eq!(result.markdown, "# Heading");
    assert_eq!(result.document_id.as_deref(), Some("doc_1"));
    assert_eq!(result.filename.as_deref(), Some("report.pdf"));
}

#[tokio::test]
async fn convert_file_for_project_targets_project_route() {
    let mut server = Server::new_async().await;
    let _m = server
        .mock("POST", "/projects/7/convert")
        .match_header("content-type", Matcher::Regex("multipart/form-data".into()))
        .with_status(200)
        .with_header("content-type", "application/json")
        .with_body(r#"{"markdown":"converted"}"#)
        .create_async()
        .await;

    let result = make_client(&server)
        .convert()
        .convert_file_for_project(7, "x.docx", b"PK-stub".to_vec())
        .await
        .unwrap();
    assert_eq!(result.markdown, "converted");
    // document_id/filename absent in the response → None.
    assert!(result.document_id.is_none());
}

#[tokio::test]
async fn project_scope_convert_helper_routes_to_project_convert() {
    let mut server = Server::new_async().await;
    // ProjectScope::convert_file is the ergonomic wrapper that must route to
    // the project-scoped endpoint using the scope's own project_id.
    let _m = server
        .mock("POST", "/projects/7/convert")
        .with_status(200)
        .with_header("content-type", "application/json")
        .with_body(r#"{"markdown":"ok"}"#)
        .create_async()
        .await;

    let result = make_client(&server)
        .project(7)
        .convert_file("a.html", b"<html/>".to_vec())
        .await
        .unwrap();
    assert_eq!(result.markdown, "ok");
}

#[tokio::test]
async fn convert_file_surfaces_403_forbidden_as_fatal() {
    let mut server = Server::new_async().await;
    // The real server rejects the unscoped /convert for non-service tokens
    // with 403. The SDK classifies 403 as a Fatal error.
    let _m = server
        .mock("POST", "/convert")
        .with_status(403)
        .with_body("Unscoped /convert requires a service token")
        .create_async()
        .await;

    let err = make_client(&server)
        .convert()
        .convert_file("x.pdf", b"data".to_vec())
        .await
        .unwrap_err();
    match err {
        akribes_sdk::AkribesError::Fatal { message, .. } => {
            assert!(message.contains("service token"), "got: {message}");
        }
        other => panic!("expected Fatal, got {other:?}"),
    }
}