#![allow(clippy::unwrap_used)]
use std::sync::Arc;
use axum::{Router, body::Body};
use fraiseql_core::{cache::CachedDatabaseAdapter, schema::CompiledSchema};
use fraiseql_test_utils::failing_adapter::FailingAdapter;
use http::{Request, StatusCode};
use tower::ServiceExt;
use crate::{server::Server, server_config::ServerConfig};
async fn prod_router(config: ServerConfig) -> Router {
let server: Server<CachedDatabaseAdapter<FailingAdapter>> =
Server::new(config, CompiledSchema::new(), Arc::new(FailingAdapter::new()), None)
.await
.expect("Server::new should succeed for an empty schema + default config");
let state = server.build_app_state();
let app = server.mount_base_and_admin_routes(Router::new(), &state);
server.mount_extensions(app, &state)
}
#[tokio::test]
async fn default_prod_assembly_does_not_mount_realtime_v1() {
let app = prod_router(ServerConfig::default()).await;
let response = app
.oneshot(
Request::builder()
.method("GET")
.uri("/realtime/v1")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(
response.status(),
StatusCode::NOT_FOUND,
"the dormant /realtime/v1 entity-stream endpoint must not be mounted in a default \
production assembly (a mounted WS route without an upgrade header answers 400/426, \
not 404) — this invariant must hold before, during, and after the #605 removal",
);
}
#[tokio::test]
async fn prod_assembly_mounts_live_ws_subscription_path() {
let config = ServerConfig {
subscription_require_auth: Some(false),
..ServerConfig::default()
};
let app = prod_router(config).await;
let response = app
.oneshot(Request::builder().method("GET").uri("/ws").body(Body::empty()).unwrap())
.await
.unwrap();
assert_ne!(
response.status(),
StatusCode::NOT_FOUND,
"the live /ws GraphQL-subscription path must stay mounted in a default assembly \
(subscriptions_enabled defaults true) throughout the #605 realtime removal",
);
}
#[tokio::test]
async fn admin_assembly_does_not_expose_realtime_monitor_routes() {
let config = ServerConfig {
admin_api_enabled: true,
admin_token: Some("realtime-removal-test-admin-token-0123456789".to_string()),
..ServerConfig::default()
};
let app = prod_router(config).await;
let control = app
.clone()
.oneshot(
Request::builder()
.method("GET")
.uri("/admin/v1/schema")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(
control.status(),
StatusCode::UNAUTHORIZED,
"the studio admin router must be mounted (a surviving admin route answers 401 \
unauthenticated) — otherwise the realtime-route 404 checks below are vacuous",
);
for path in [
"/admin/v1/realtime/stats",
"/admin/v1/realtime/broadcast",
"/admin/v1/realtime/presence",
"/admin/v1/realtime/cdc",
] {
let response = app
.clone()
.oneshot(Request::builder().method("GET").uri(path).body(Body::empty()).unwrap())
.await
.unwrap();
assert_eq!(
response.status(),
StatusCode::NOT_FOUND,
"removed studio monitor route {path} must be unmounted (404), not merely \
auth-gated (401) — a mounted admin route would answer 401 here",
);
}
}
#[tokio::test]
async fn assembly_does_not_expose_realtime_broadcast_endpoint() {
let app = prod_router(ServerConfig::default()).await;
let control = app
.clone()
.oneshot(Request::builder().method("GET").uri("/health").body(Body::empty()).unwrap())
.await
.unwrap();
assert_ne!(
control.status(),
StatusCode::NOT_FOUND,
"sanity: a default assembly must mount /health, proving the router is non-empty",
);
for method in ["POST", "GET"] {
let response = app
.clone()
.oneshot(
Request::builder()
.method(method)
.uri("/realtime/v1/broadcast")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(
response.status(),
StatusCode::NOT_FOUND,
"POST /realtime/v1/broadcast was removed (Cluster C); no config can mount it \
(probed with {method}) — 404, never a mounted 401/405",
);
}
}