tandem-server 0.4.25

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

#[tokio::test]
async fn presets_index_route_returns_layered_index_shape() {
    let state = test_state().await;
    let app = app_router(state);
    let req = Request::builder()
        .method("GET")
        .uri("/presets/index")
        .body(Body::empty())
        .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 index = payload.get("index").cloned().unwrap_or(Value::Null);
    assert!(index
        .get("skill_modules")
        .and_then(|v| v.as_array())
        .is_some());
    assert!(index
        .get("agent_presets")
        .and_then(|v| v.as_array())
        .is_some());
    assert!(index
        .get("automation_presets")
        .and_then(|v| v.as_array())
        .is_some());
    assert!(index
        .get("pack_presets")
        .and_then(|v| v.as_array())
        .is_some());
    assert!(index
        .get("generated_at_ms")
        .and_then(|v| v.as_u64())
        .is_some());
}

#[tokio::test]
async fn presets_compose_preview_is_deterministic() {
    let state = test_state().await;
    let app = app_router(state);
    let req = Request::builder()
        .method("POST")
        .uri("/presets/compose/preview")
        .header("content-type", "application/json")
        .body(Body::from(
            json!({
                "base_prompt": "Base",
                "fragments": [
                    {"id":"zeta","phase":"style","content":"Style Z"},
                    {"id":"alpha","phase":"core","content":"Core A"},
                    {"id":"safe","phase":"safety","content":"Do no harm"}
                ]
            })
            .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 ordered = payload
        .get("composition")
        .and_then(|v| v.get("ordered_fragment_ids"))
        .and_then(|v| v.as_array())
        .cloned()
        .unwrap_or_default();
    assert_eq!(ordered.len(), 3);
    assert_eq!(ordered[0].as_str(), Some("alpha"));
    assert_eq!(ordered[1].as_str(), Some("zeta"));
    assert_eq!(ordered[2].as_str(), Some("safe"));
    assert!(payload
        .get("composition")
        .and_then(|v| v.get("composition_hash"))
        .and_then(|v| v.as_str())
        .map(|s| !s.is_empty())
        .unwrap_or(false));
}

#[tokio::test]
async fn presets_override_put_and_delete_roundtrip() {
    let state = test_state().await;
    let app = app_router(state);
    let put_req = Request::builder()
        .method("PUT")
        .uri("/presets/overrides/agent_preset/dev_agent")
        .header("content-type", "application/json")
        .body(Body::from(
            json!({
                "content": "id: dev_agent\nversion: 1.0.0\n"
            })
            .to_string(),
        ))
        .expect("request");
    let put_resp = app.clone().oneshot(put_req).await.expect("response");
    assert_eq!(put_resp.status(), StatusCode::OK);
    let put_body = to_bytes(put_resp.into_body(), usize::MAX)
        .await
        .expect("body");
    let put_payload: Value = serde_json::from_slice(&put_body).expect("json");
    assert_eq!(
        put_payload.get("saved").and_then(|v| v.as_bool()),
        Some(true)
    );

    let del_req = Request::builder()
        .method("DELETE")
        .uri("/presets/overrides/agent_preset/dev_agent")
        .body(Body::empty())
        .expect("request");
    let del_resp = app.clone().oneshot(del_req).await.expect("response");
    assert_eq!(del_resp.status(), StatusCode::OK);
    let del_body = to_bytes(del_resp.into_body(), usize::MAX)
        .await
        .expect("body");
    let del_payload: Value = serde_json::from_slice(&del_body).expect("json");
    assert_eq!(
        del_payload.get("removed").and_then(|v| v.as_bool()),
        Some(true)
    );
}

#[tokio::test]
async fn presets_fork_copies_source_into_overrides() {
    let state = test_state().await;
    let app = app_router(state);
    let seed_req = Request::builder()
        .method("PUT")
        .uri("/presets/overrides/skill_module/source_seed")
        .header("content-type", "application/json")
        .body(Body::from(
            json!({
                "content": "id: source_seed\nversion: 1.0.0\n"
            })
            .to_string(),
        ))
        .expect("request");
    let seed_resp = app.clone().oneshot(seed_req).await.expect("response");
    assert_eq!(seed_resp.status(), StatusCode::OK);
    let seed_body = to_bytes(seed_resp.into_body(), usize::MAX)
        .await
        .expect("body");
    let seed_payload: Value = serde_json::from_slice(&seed_body).expect("json");
    let source_path = seed_payload
        .get("path")
        .and_then(|v| v.as_str())
        .unwrap_or("")
        .to_string();
    assert!(!source_path.is_empty());

    let fork_req = Request::builder()
        .method("POST")
        .uri("/presets/fork")
        .header("content-type", "application/json")
        .body(Body::from(
            json!({
                "kind": "skill_module",
                "source_path": source_path,
                "target_id": "forked_skill"
            })
            .to_string(),
        ))
        .expect("request");
    let fork_resp = app.clone().oneshot(fork_req).await.expect("response");
    assert_eq!(fork_resp.status(), StatusCode::OK);
    let fork_body = to_bytes(fork_resp.into_body(), usize::MAX)
        .await
        .expect("body");
    let fork_payload: Value = serde_json::from_slice(&fork_body).expect("json");
    assert_eq!(
        fork_payload.get("forked").and_then(|v| v.as_bool()),
        Some(true)
    );
    let forked_path = fork_payload
        .get("path")
        .and_then(|v| v.as_str())
        .unwrap_or("");
    assert!(forked_path.ends_with("forked_skill.yaml"));
}

#[tokio::test]
async fn presets_capability_summary_merges_agent_and_task_caps() {
    let state = test_state().await;
    let app = app_router(state);
    let req = Request::builder()
        .method("POST")
        .uri("/presets/capability_summary")
        .header("content-type", "application/json")
        .body(Body::from(
            json!({
                "agent": {
                    "required": ["github.create_pull_request"],
                    "optional": ["slack.post_message"]
                },
                "tasks": [
                    {"required": ["slack.post_message"], "optional": []},
                    {"required": [], "optional": ["jira.create_issue"]}
                ]
            })
            .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 required = payload
        .get("summary")
        .and_then(|v| v.get("automation"))
        .and_then(|v| v.get("required"))
        .and_then(|v| v.as_array())
        .cloned()
        .unwrap_or_default();
    let optional = payload
        .get("summary")
        .and_then(|v| v.get("automation"))
        .and_then(|v| v.get("optional"))
        .and_then(|v| v.as_array())
        .cloned()
        .unwrap_or_default();
    assert_eq!(required.len(), 2);
    assert_eq!(optional.len(), 1);
    assert_eq!(
        payload
            .get("summary")
            .and_then(|v| v.get("totals"))
            .and_then(|v| v.get("task_count"))
            .and_then(|v| v.as_u64()),
        Some(2)
    );
}

#[tokio::test]
async fn presets_export_overrides_returns_zip_payload() {
    let state = test_state().await;
    let app = app_router(state);
    let seed_req = Request::builder()
        .method("PUT")
        .uri("/presets/overrides/skill_module/export_seed")
        .header("content-type", "application/json")
        .body(Body::from(
            json!({
                "content": "id: export_seed\nversion: 1.0.0\n"
            })
            .to_string(),
        ))
        .expect("request");
    let seed_resp = app.clone().oneshot(seed_req).await.expect("response");
    assert_eq!(seed_resp.status(), StatusCode::OK);

    let export_req = Request::builder()
        .method("POST")
        .uri("/presets/export_overrides")
        .header("content-type", "application/json")
        .body(Body::from(
            json!({
                "name": "exported-overrides",
                "version": "1.0.0"
            })
            .to_string(),
        ))
        .expect("request");
    let export_resp = app.clone().oneshot(export_req).await.expect("response");
    assert_eq!(export_resp.status(), StatusCode::OK);
    let export_body = to_bytes(export_resp.into_body(), usize::MAX)
        .await
        .expect("body");
    let export_payload: Value = serde_json::from_slice(&export_body).expect("json");
    assert!(export_payload
        .get("exported")
        .and_then(|v| v.get("path"))
        .and_then(|v| v.as_str())
        .map(|s| s.ends_with(".zip"))
        .unwrap_or(false));
    assert!(
        export_payload
            .get("exported")
            .and_then(|v| v.get("bytes"))
            .and_then(|v| v.as_u64())
            .unwrap_or(0)
            > 0
    );
}