cellos-server 0.5.3

HTTP control plane for CellOS — admission, projection over JetStream, WebSocket fan-out of CloudEvents. Pure event-sourced architecture.
Documentation
//! E2E report SRV-001: `GET /v1/version` is the day-1 reachability probe.
//!
//! These tests exercise the route end-to-end through the axum router so
//! we catch wiring regressions (missing route, wrong method, auth
//! middleware drift) in addition to handler logic.

use axum::body::Body;
use axum::http::{header, Request, StatusCode};
use cellos_server::{router, AppState};
use http_body_util::BodyExt;
use tower::ServiceExt;

const TOKEN: &str = "srv001-test-token";

fn state() -> AppState {
    AppState::new(None, TOKEN)
}

fn version_request(auth: Option<&str>) -> Request<Body> {
    let mut b = Request::builder().method("GET").uri("/v1/version");
    if let Some(t) = auth {
        b = b.header(header::AUTHORIZATION, format!("Bearer {t}"));
    }
    b.body(Body::empty()).expect("build request")
}

/// Happy path: with a valid Bearer token the route returns 200 and a
/// JSON body matching the documented SRV-001 shape.
#[tokio::test]
async fn version_returns_documented_shape_with_auth() {
    let app = router(state());
    let resp = app
        .oneshot(version_request(Some(TOKEN)))
        .await
        .expect("router response");
    assert_eq!(resp.status(), StatusCode::OK);

    let ct = resp
        .headers()
        .get(header::CONTENT_TYPE)
        .and_then(|v| v.to_str().ok())
        .unwrap_or_default()
        .to_owned();
    assert!(
        ct.starts_with("application/json"),
        "version response must be JSON, got {ct:?}",
    );

    let bytes = resp.into_body().collect().await.unwrap().to_bytes();
    let body: serde_json::Value = serde_json::from_slice(&bytes).expect("body parses as JSON");

    // Required nested objects.
    let server = body
        .get("server")
        .expect("response must contain a `server` object");
    let api = body
        .get("api")
        .expect("response must contain an `api` object");

    // `server.version` must equal the crate version — pin via env! so a
    // workspace-wide version bump updates this test in lock-step.
    assert_eq!(
        server.get("version").and_then(|v| v.as_str()),
        Some(env!("CARGO_PKG_VERSION")),
        "server.version must match the cellos-server crate version",
    );

    // `server.buildProfile` is exactly one of "release" / "debug",
    // matching the compile-time `cfg!(debug_assertions)` branch.
    let profile = server
        .get("buildProfile")
        .and_then(|v| v.as_str())
        .expect("server.buildProfile must be a string");
    assert!(
        profile == "release" || profile == "debug",
        "buildProfile must be release or debug; got {profile:?}",
    );

    // `api.version` is the stable surface marker.
    assert_eq!(api.get("version").and_then(|v| v.as_str()), Some("v1"));

    // `gitSha` is best-effort: it MUST either be absent (option_env!
    // returned None) or be a non-empty string. We do not pin a specific
    // value so the test stays stable across git states. The
    // serde-side `skip_serializing_if` guarantees we never see
    // `"gitSha": null`.
    match server.get("gitSha") {
        None => {}
        Some(serde_json::Value::String(s)) => {
            assert!(!s.is_empty(), "gitSha if present must be non-empty");
        }
        other => panic!("gitSha must be a string or absent; got {other:?}"),
    }
}

/// Without an `Authorization` header the route must return 401, like
/// every other route. This is the structural assertion that the
/// version endpoint does NOT accidentally become a public information
/// disclosure surface.
#[tokio::test]
async fn version_returns_unauthorized_without_bearer() {
    let app = router(state());
    let resp = app
        .oneshot(version_request(None))
        .await
        .expect("router response");
    assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
}

/// A malformed bearer token must also produce 401, not 200. This pins
/// that the route is wired through `require_bearer` and not (for
/// example) accidentally short-circuited.
#[tokio::test]
async fn version_returns_unauthorized_with_wrong_token() {
    let app = router(state());
    let resp = app
        .oneshot(version_request(Some("not-the-real-token")))
        .await
        .expect("router response");
    assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
}