roboticus-api 0.11.3

HTTP routes, WebSocket, auth, rate limiting, and dashboard for the Roboticus agent runtime
Documentation
use super::*;

// ── GET /api/turns/{id}/context ───────────────────────────────

#[tokio::test]
async fn get_turn_context_returns_context_data() {
    let state = test_state();
    let sid = roboticus_db::sessions::create_new(&state.db, "agent-d", None).unwrap();
    roboticus_db::sessions::create_turn_with_id(
        &state.db,
        "turn-ctx-1",
        &sid,
        Some("gpt-4"),
        Some(300),
        Some(150),
        Some(0.03),
    )
    .unwrap();
    roboticus_db::sessions::upsert_context_snapshot(
        &state.db,
        roboticus_db::sessions::UpsertContextSnapshotInput {
            turn_id: "turn-ctx-1",
            complexity_level: "L2",
            token_budget: 16000,
            system_prompt_tokens: Some(1200),
            memory_tokens: Some(700),
            history_tokens: Some(900),
            history_depth: Some(6),
            memory_tiers_json: None,
            retrieved_memories_json: None,
            model: Some("gpt-4"),
        },
    )
    .unwrap();

    let app = build_router(state);
    let resp = app
        .oneshot(
            Request::builder()
                .uri("/api/turns/turn-ctx-1/context")
                .body(Body::empty())
                .unwrap(),
        )
        .await
        .unwrap();
    assert_eq!(resp.status(), StatusCode::OK);
    let body = json_body(resp).await;
    assert_eq!(body["turn_id"], "turn-ctx-1");
    assert_eq!(body["model"], "gpt-4");
    assert_eq!(body["tokens_in"], 300);
    assert_eq!(body["tokens_out"], 150);
    assert_eq!(body["token_budget"], 16000);
    assert_eq!(body["system_prompt_tokens"], 1200);
    assert_eq!(body["memory_tokens"], 700);
    assert_eq!(body["history_tokens"], 900);
    assert_eq!(body["history_depth"], 6);
    assert_eq!(body["complexity_level"], "L2");
    assert_eq!(body["snapshot"], true);
    assert_eq!(body["tool_call_count"], 0);
    assert_eq!(body["tool_failure_count"], 0);
}

#[tokio::test]
async fn get_turn_context_nonexistent_returns_404() {
    let app = build_router(test_state());
    let resp = app
        .oneshot(
            Request::builder()
                .uri("/api/turns/nonexistent/context")
                .body(Body::empty())
                .unwrap(),
        )
        .await
        .unwrap();
    assert_eq!(resp.status(), StatusCode::NOT_FOUND);
}

#[tokio::test]
async fn capabilities_resync_matches_tool_registry_names() {
    struct ParityTool;

    #[async_trait]
    impl roboticus_agent::tools::Tool for ParityTool {
        fn name(&self) -> &str {
            "parity_probe"
        }
        fn description(&self) -> &str {
            "test-only parity tool"
        }
        fn risk_level(&self) -> roboticus_core::RiskLevel {
            roboticus_core::RiskLevel::Safe
        }
        fn parameters_schema(&self) -> serde_json::Value {
            serde_json::json!({"type":"object"})
        }
        async fn execute(
            &self,
            _params: serde_json::Value,
            _ctx: &roboticus_agent::tools::ToolContext,
        ) -> std::result::Result<
            roboticus_agent::tools::ToolResult,
            roboticus_agent::tools::ToolError,
        > {
            Ok(roboticus_agent::tools::ToolResult {
                output: "ok".to_string(),
                metadata: None,
            })
        }
    }

    let mut state = test_state();
    let mut registry = ToolRegistry::new();
    registry.register(Box::new(ParityTool));
    state.tools = Arc::new(registry);
    state.resync_capabilities_from_tools().await;

    let mut tool_names: Vec<String> = state
        .tools
        .list()
        .iter()
        .map(|t| t.name().to_string())
        .collect();
    let mut capability_names = state.capabilities.list_names().await;
    tool_names.sort();
    capability_names.sort();
    assert_eq!(tool_names, capability_names);
}