tandem-server 0.4.23

HTTP server for Tandem engine APIs
Documentation
use super::*;

#[tokio::test]
async fn capabilities_resolve_prefers_arcade_when_requested() {
    let state = test_state().await;
    let app = app_router(state);
    let req = Request::builder()
            .method("POST")
            .uri("/capabilities/resolve")
            .header("content-type", "application/json")
            .body(Body::from(
                json!({
                    "workflow_id": "wf-pr",
                    "required_capabilities": ["github.create_pull_request"],
                    "provider_preference": ["arcade", "composio"],
                    "available_tools": [
                        {"provider":"composio","tool_name":"mcp.composio.github_create_pull_request","schema":{}},
                        {"provider":"arcade","tool_name":"mcp.arcade.github_create_pull_request","schema":{}}
                    ]
                })
                .to_string(),
            ))
            .expect("request");
    let resp = app.oneshot(req).await.expect("response");
    assert_eq!(resp.status(), StatusCode::OK);
    let body = to_bytes(resp.into_body(), usize::MAX).await.expect("body");
    let payload: Value = serde_json::from_slice(&body).expect("json");
    let provider = payload
        .get("resolution")
        .and_then(|v| v.get("resolved"))
        .and_then(|v| v.as_array())
        .and_then(|rows| rows.first())
        .and_then(|row| row.get("provider"))
        .and_then(|v| v.as_str());
    assert_eq!(provider, Some("arcade"));
}

#[tokio::test]
async fn capabilities_resolve_returns_missing_capability_error() {
    let state = test_state().await;
    let app = app_router(state);
    let req = Request::builder()
        .method("POST")
        .uri("/capabilities/resolve")
        .header("content-type", "application/json")
        .body(Body::from(
            json!({
                "workflow_id": "wf-pr",
                "required_capabilities": ["github.create_pull_request"],
                "provider_preference": ["arcade", "composio"],
                "available_tools": []
            })
            .to_string(),
        ))
        .expect("request");
    let resp = app.oneshot(req).await.expect("response");
    assert_eq!(resp.status(), StatusCode::CONFLICT);
    let body = to_bytes(resp.into_body(), usize::MAX).await.expect("body");
    let payload: Value = serde_json::from_slice(&body).expect("json");
    assert_eq!(
        payload.get("code").and_then(|v| v.as_str()),
        Some("missing_capability")
    );
    assert_eq!(
        payload.get("workflow_id").and_then(|v| v.as_str()),
        Some("wf-pr")
    );
}

#[tokio::test]
async fn capabilities_readiness_returns_blocking_issues_when_unbound() {
    let state = test_state().await;
    let app = app_router(state);
    let req = Request::builder()
        .method("POST")
        .uri("/capabilities/readiness")
        .header("content-type", "application/json")
        .body(Body::from(
            json!({
                "workflow_id": "wf-readiness",
                "required_capabilities": ["github.create_pull_request"],
                "provider_preference": ["composio", "arcade", "mcp"],
                "available_tools": []
            })
            .to_string(),
        ))
        .expect("request");
    let resp = app.oneshot(req).await.expect("response");
    assert_eq!(resp.status(), StatusCode::CONFLICT);
    let body = to_bytes(resp.into_body(), usize::MAX).await.expect("body");
    let payload: Value = serde_json::from_slice(&body).expect("json");
    let readiness = payload.get("readiness").cloned().unwrap_or(Value::Null);
    assert_eq!(
        readiness.get("runnable").and_then(|v| v.as_bool()),
        Some(false)
    );
    assert!(readiness
        .get("unbound_capabilities")
        .and_then(|v| v.as_array())
        .is_some_and(|rows| !rows.is_empty()));
    assert!(readiness
        .get("blocking_issues")
        .and_then(|v| v.as_array())
        .is_some_and(|rows| !rows.is_empty()));
}