use iicp_client::{IicpError, IicpNode, NodeConfig};
use serde_json::{json, Value};
use std::time::Duration;
const CHAT: &str = "urn:iicp:intent:llm:chat:v1";
async fn spawn_node(port: u16, ok: bool) {
let cfg = NodeConfig::new(
"node-inference-test",
format!("http://127.0.0.1:{port}"),
CHAT,
);
let node = IicpNode::new(cfg);
let addr = format!("127.0.0.1:{port}");
tokio::spawn(async move {
let _ = node
.serve(
move |_req| async move {
if ok {
Ok(json!({
"choices": [{
"message": {"role": "assistant", "content": "4"},
"finish_reason": "stop"
}],
"model": "mock-backend",
"usage": {"prompt_tokens": 5, "completion_tokens": 1}
}))
} else {
Err(IicpError::Node("mock backend unavailable".into()))
}
},
&addr,
None,
)
.await;
});
}
async fn wait_ready(port: u16) {
let client = reqwest::Client::new();
for _ in 0..100 {
if let Ok(r) = client
.get(format!("http://127.0.0.1:{port}/iicp/health"))
.send()
.await
{
if r.status().is_success() {
return;
}
}
tokio::time::sleep(Duration::from_millis(50)).await;
}
panic!("node never became ready on :{port}");
}
#[tokio::test]
async fn task_returns_real_completion_with_success_status() {
let port = 19584;
spawn_node(port, true).await;
wait_ready(port).await;
let resp = reqwest::Client::new()
.post(format!("http://127.0.0.1:{port}/v1/task"))
.json(&json!({
"task_id": "infer-1",
"intent": CHAT,
"payload": {"messages": [{"role": "user", "content": "What is 2+2? Reply with only the number."}]},
"constraints": {"timeout_ms": 5000}
}))
.send()
.await
.expect("POST /v1/task");
assert_eq!(
resp.status().as_u16(),
200,
"task must return 200, not 502/skip"
);
let body: Value = resp.json().await.expect("json body");
assert_eq!(
body["status"], "success",
"spec iicp-dir.md task status MUST be 'success' (parity with the Python adapter): {body}"
);
assert_eq!(body["task_id"], "infer-1");
assert_eq!(
body["result"]["choices"][0]["message"]["content"], "4",
"the node must return the backend completion in result: {body}"
);
}
#[tokio::test]
async fn task_backend_failure_returns_error_status() {
let port = 19585;
spawn_node(port, false).await;
wait_ready(port).await;
let resp = reqwest::Client::new()
.post(format!("http://127.0.0.1:{port}/v1/task"))
.json(&json!({
"task_id": "infer-fail-1",
"intent": CHAT,
"payload": {"messages": [{"role": "user", "content": "hi"}]},
"constraints": {"timeout_ms": 5000}
}))
.send()
.await
.expect("POST /v1/task");
assert_eq!(
resp.status().as_u16(),
500,
"backend failure must be a 500, not a silent pass"
);
let body: Value = resp.json().await.expect("json body");
assert_eq!(
body["status"], "error",
"failure status must be 'error': {body}"
);
}