#![cfg(feature = "server")]
use axon::axon_server::{build_router, validate_server_default_backend, ServerConfig};
use axon::parser::AXONENDPOINT_BACKEND_VALUES;
use axum::body::Body;
use axum::http::{Request, StatusCode};
use http_body_util::BodyExt;
use tower::ServiceExt;
#[test]
fn s1_none_is_always_valid() {
assert!(
validate_server_default_backend(&None).is_ok(),
"36.g D7: `None` ≡ no server default — always valid"
);
}
#[test]
fn s2_every_catalog_entry_is_valid() {
for &b in AXONENDPOINT_BACKEND_VALUES {
assert!(
validate_server_default_backend(&Some(b.to_string())).is_ok(),
"36.g D7: catalog entry `{b}` must be a valid server default"
);
}
}
#[test]
fn s3_unknown_backend_is_rejected_with_a_listing() {
let err = validate_server_default_backend(&Some("gpt-9-ultra".into()))
.expect_err("36.g D7: an unknown server default must be rejected");
assert!(
err.contains("gpt-9-ultra"),
"36.g: the diagnostic must name the offending value. Got: {err}"
);
assert!(
err.contains("anthropic") && err.contains("stub"),
"36.g: the diagnostic must list the valid catalog. Got: {err}"
);
assert!(
err.contains("--backend") || err.contains("AXON_DEFAULT_BACKEND"),
"36.g: the diagnostic should name the surfaces. Got: {err}"
);
}
fn server_cfg(default_backend: Option<&str>) -> ServerConfig {
ServerConfig {
host: "127.0.0.1".into(),
port: 0,
channel: "memory".into(),
auth_token: String::new(),
log_level: "INFO".into(),
log_format: "json".into(),
log_file: None,
database_url: None,
config_path: None,
strict_type_driven_transport: false,
default_backend: default_backend.map(|s| s.to_string()),
schemas_dir: None,
}
}
async fn register_backend(app: &axum::Router, name: &str) {
let req = Request::builder()
.method("PUT")
.uri(format!("/v1/backends/{name}"))
.header("content-type", "application/json")
.body(Body::from(r#"{"enabled":true}"#))
.unwrap();
let resp = app.clone().oneshot(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK, "register {name} failed");
}
async fn deploy(app: &axum::Router, src: &str) {
let body = serde_json::json!({ "source": src, "source_file": "test.axon" });
let req = Request::builder()
.method("POST")
.uri("/v1/deploy")
.header("content-type", "application/json")
.body(Body::from(body.to_string()))
.unwrap();
let resp = app.clone().oneshot(req).await.unwrap();
let status = resp.status();
let bytes = resp.into_body().collect().await.unwrap().to_bytes();
let json: serde_json::Value = serde_json::from_slice(&bytes).unwrap_or_default();
assert_eq!(status, StatusCode::OK, "deploy failed: {json}");
assert_eq!(json.get("success").and_then(|v| v.as_bool()), Some(true), "{json}");
}
async fn hit_json(app: &axum::Router, path: &str) -> serde_json::Value {
let req = Request::builder()
.method("POST")
.uri(path)
.header("content-type", "application/json")
.body(Body::from("{}"))
.unwrap();
let resp = app.clone().oneshot(req).await.unwrap();
assert_eq!(resp.status(), StatusCode::OK, "{path} must dispatch");
let bytes = resp.into_body().collect().await.unwrap().to_bytes();
serde_json::from_slice(&bytes).unwrap()
}
#[tokio::test]
async fn s4_server_default_feeds_rung_3_over_the_registry() {
let app = build_router(server_cfg(Some("stub")));
register_backend(&app, "anthropic").await;
deploy(
&app,
"flow Chat() -> Unit { step S { ask: \"hi\" } }\n\
axonendpoint E { public: true method: POST path: \"/chat\" execute: Chat }",
)
.await;
let json = hit_json(&app, "/chat").await;
assert_eq!(
json["execution_metrics"]["backend"], "stub",
"36.g D7: an undeclared route must resolve to the server \
default `stub` (rung 3) — outranking the `anthropic` registry \
entry (rung 4a). Body: {json}"
);
assert!(json["step_audit"]["steps_executed"].as_u64().unwrap_or(0) >= 1);
}
#[tokio::test]
async fn s5_route_declaration_outranks_server_default() {
let app = build_router(server_cfg(Some("openai")));
deploy(
&app,
"flow Chat() -> Unit { step S { ask: \"hi\" } }\n\
axonendpoint E { public: true method: POST path: \"/chat\" execute: Chat backend: stub }",
)
.await;
let json = hit_json(&app, "/chat").await;
assert_eq!(
json["execution_metrics"]["backend"], "stub",
"36.g D7: the route's declared `backend: stub` (rung 2) must \
outrank the server default `openai` (rung 3). Body: {json}"
);
assert!(json["step_audit"]["steps_executed"].as_u64().unwrap_or(0) >= 1);
}
#[tokio::test]
async fn s6_no_server_default_undeclared_route_still_dispatches() {
let app = build_router(server_cfg(None));
deploy(
&app,
"flow Chat() -> Unit { step S { ask: \"hi\" } }\n\
axonendpoint E { public: true method: POST path: \"/plain\" execute: Chat backend: stub }",
)
.await;
let json = hit_json(&app, "/plain").await;
assert_eq!(json["execution_metrics"]["backend"], "stub");
assert!(json["step_audit"]["steps_executed"].as_u64().unwrap_or(0) >= 1);
}