use std::sync::Arc;
use aion::EngineBuilder;
use aion_integration_acp::catalogue;
use aion_store::{EventStore, InMemoryStore};
use axum::http::StatusCode;
use serde_json::Value;
use tower::ServiceExt;
use super::super::router::workflow_router;
use super::super::test_support::{get_request, read_json, runtime_config, server_state};
use crate::assistant::mcp::{ASSISTANT_MCP_PATH, SESSION_TOKEN_KIND, SESSION_TOOL_NAMES};
use crate::namespace::grants::GRANT_WORDS;
use crate::test_support::{EngineUnderTest, StateUnderTest};
use crate::{
NamespaceResolver, StaticScheduleNamespaces, StaticWorkflowNamespaces, config::NamespaceMode,
};
type TestResult = Result<(), Box<dyn std::error::Error>>;
async fn router_state() -> Result<StateUnderTest, Box<dyn std::error::Error>> {
let store: Arc<dyn EventStore> = Arc::new(InMemoryStore::default());
let engine = EngineUnderTest::new(Arc::new(
EngineBuilder::new()
.stop_drain_timeout(std::time::Duration::from_secs(5))
.store_arc(store)
.in_memory_visibility()
.scheduler_threads(1)
.build()
.await?,
));
let resolver = NamespaceResolver::from_parts(
NamespaceMode::SharedEngine,
Some(engine.handle()),
Arc::new(StaticWorkflowNamespaces::default()),
Arc::new(StaticScheduleNamespaces::default()),
);
server_state(engine, resolver, runtime_config()).await
}
async fn descriptor() -> Result<Value, Box<dyn std::error::Error>> {
let state = router_state().await?;
let response = workflow_router(state.clone())
.oneshot(get_request("/assistant")?)
.await?;
assert_eq!(response.status(), StatusCode::OK);
let content_type = response
.headers()
.get(axum::http::header::CONTENT_TYPE)
.and_then(|value| value.to_str().ok())
.unwrap_or_default()
.to_owned();
assert!(
content_type.starts_with("application/json"),
"an absent route answers text/html from the SPA catch-all; got `{content_type}`"
);
read_json(response).await
}
#[tokio::test]
async fn the_server_describes_every_harness_it_ships() -> TestResult {
assert!(
!catalogue::CATALOGUE.is_empty(),
"the catalogue is empty, so this cell measures nothing"
);
let body = descriptor().await?;
let harnesses = body["harnesses"]
.as_array()
.ok_or("the description must carry a harness list")?;
assert_eq!(harnesses.len(), catalogue::CATALOGUE.len());
for entry in catalogue::CATALOGUE {
let described = harnesses
.iter()
.find(|harness| harness["name"] == entry.id)
.ok_or_else(|| format!("the description must name `{}`: {body}", entry.id))?;
assert_eq!(described["kind"], "acp");
assert_eq!(described["launch"], entry.launch());
assert_eq!(described["available"], entry.available());
assert_eq!(
described["install_hint"].is_null(),
entry.available(),
"the hint is present exactly when the harness is unavailable: {described}"
);
}
Ok(())
}
#[tokio::test]
async fn the_description_carries_the_tool_wiring_a_session_is_handed() -> TestResult {
let body = descriptor().await?;
let own = &body["tools"]["assistant"];
assert_eq!(own["server"], "assistant");
assert_eq!(own["route"], ASSISTANT_MCP_PATH);
assert_eq!(own["token"]["kind"], SESSION_TOKEN_KIND);
assert_eq!(own["token"]["minted_by"], "server");
assert_eq!(own["token"]["scope"], "session");
let tools = own["tools"]
.as_array()
.ok_or("the assistant tool server must publish its catalogue")?;
assert_eq!(tools.len(), SESSION_TOOL_NAMES.len());
for name in SESSION_TOOL_NAMES {
assert!(
tools.iter().any(|tool| tool == name),
"the description must name the tool `{name}`: {body}"
);
}
assert!(
body["tools"]["aion"].is_boolean(),
"whether the general MCP endpoint is handed over is a stated fact: {body}"
);
Ok(())
}
#[tokio::test]
async fn the_description_carries_every_grant_word_for_this_caller() -> TestResult {
assert!(
!GRANT_WORDS.is_empty(),
"the grant vocabulary is empty, so this cell measures nothing"
);
let body = descriptor().await?;
let grants = body["grants"]
.as_array()
.ok_or("the description must carry the grant rows")?;
assert_eq!(grants.len(), GRANT_WORDS.len());
for grant in GRANT_WORDS {
let described = grants
.iter()
.find(|row| row["name"] == grant.word())
.ok_or_else(|| format!("the description must name `{}`: {body}", grant.word()))?;
assert_eq!(described["description"], grant.description());
assert!(
described["held"].is_boolean(),
"a grant row states whether this caller holds it: {described}"
);
}
Ok(())
}
#[tokio::test]
async fn the_description_states_availability_and_the_callers_last_pick() -> TestResult {
let body = descriptor().await?;
let enabled = body["sessions_enabled"]
.as_bool()
.ok_or("session availability must be stated")?;
assert_eq!(
body["sessions_disabled_reason"].is_null(),
enabled,
"a refusal must carry the reason and an availability must not: {body}"
);
assert!(
body["default_harness"].is_null(),
"a caller who has opened nothing has no last pick: {body}"
);
Ok(())
}
#[tokio::test]
async fn the_description_names_no_workflow_document() -> TestResult {
let body = descriptor().await?;
for retired in [
"workflow_type",
"task_queue",
"content_hash",
"document_filename",
"document_bytes",
"input_schema",
"signals",
"queries",
"session",
"residency",
] {
assert!(
body.get(retired).is_none(),
"the description must carry no `{retired}`: {body}"
);
}
Ok(())
}
#[tokio::test]
async fn the_document_route_is_not_served() -> TestResult {
let state = router_state().await?;
let response = workflow_router(state.clone())
.oneshot(get_request("/assistant/document")?)
.await?;
assert_eq!(response.status(), StatusCode::NOT_FOUND);
Ok(())
}
#[tokio::test]
async fn a_path_this_router_does_not_serve_is_a_404() -> TestResult {
let state = router_state().await?;
let response = workflow_router(state.clone())
.oneshot(get_request("/assistant/not-a-real-surface")?)
.await?;
assert_eq!(response.status(), StatusCode::NOT_FOUND);
Ok(())
}