use axum::body::Body;
use axum::http::{Request, StatusCode, header};
use http_body_util::BodyExt;
use tower::ServiceExt;
fn app() -> axum::Router {
let p = ocserve_http::Payloads {
config: serde_json::json!({
"$schema": "https://opencode.ai/config.json",
"model": "opencode/big-pickle",
"agent": {"build": {"model": "x"}},
"command": {"init": {"template": "y"}},
"mode": "build",
"username": "u",
"provider": {"openai": {"models": {"gpt": {"name": "gpt"}}}},
"mcp": {},
"permission": {"*": "ask"},
}),
..Default::default()
};
ocserve_http::router(ocserve_http::AppState::with_payloads(None, p))
}
async fn resp(app: &axum::Router, uri: &str, headers: &[(&str, &str)]) -> axum::response::Response {
let mut b = Request::builder().method("GET").uri(uri);
for (k, v) in headers {
b = b.header(*k, *v);
}
app.clone()
.oneshot(b.body(Body::empty()).unwrap())
.await
.unwrap()
}
async fn body_bytes(r: axum::response::Response) -> bytes::Bytes {
r.into_body().collect().await.unwrap().to_bytes()
}
#[tokio::test]
async fn wire_route_identity_unchanged_and_negotiates() {
let app = app();
let r = resp(&app, "/config", &[]).await;
assert_eq!(r.status(), StatusCode::OK);
assert!(r.headers().get(header::CONTENT_ENCODING).is_none());
assert_eq!(r.headers().get(header::VARY).unwrap(), "Accept-Encoding");
let identity = body_bytes(r).await;
assert!(serde_json::from_slice::<serde_json::Value>(&identity).is_ok());
let r = resp(&app, "/config", &[("accept-encoding", "gzip")]).await;
assert_eq!(r.headers().get(header::CONTENT_ENCODING).unwrap(), "gzip");
let gz = body_bytes(r).await;
assert!(gz.len() < identity.len());
let mut d = flate2::read::GzDecoder::new(&gz[..]);
let mut dec = Vec::new();
use std::io::Read as _;
d.read_to_end(&mut dec).unwrap();
assert_eq!(dec, identity);
let r = resp(&app, "/config", &[("accept-encoding", "br")]).await;
assert_eq!(r.headers().get(header::CONTENT_ENCODING).unwrap(), "br");
let _ = body_bytes(r).await;
}
#[tokio::test]
async fn wire_route_304_on_matching_etag() {
let app = app();
let r = resp(&app, "/config", &[]).await;
let etag = r
.headers()
.get(header::ETAG)
.unwrap()
.to_str()
.unwrap()
.to_string();
let r = resp(&app, "/config", &[("if-none-match", &etag)]).await;
assert_eq!(r.status(), StatusCode::NOT_MODIFIED);
assert_eq!(body_bytes(r).await.len(), 0);
let r = resp(&app, "/config", &[("if-none-match", "\"nope\"")]).await;
assert_eq!(r.status(), StatusCode::OK);
}
#[tokio::test]
async fn session_list_limit_and_search() {
let app = app();
for t in ["alpha", "beta", "gamma"] {
let body = Body::from(serde_json::to_vec(&serde_json::json!({"title": t})).unwrap());
let r = Request::builder()
.method("POST")
.uri("/session")
.header("content-type", "application/json")
.body(body)
.unwrap();
app.clone().oneshot(r).await.unwrap();
}
let all = body_bytes(resp(&app, "/session", &[]).await).await;
let all: serde_json::Value = serde_json::from_slice(&all).unwrap();
assert_eq!(all.as_array().unwrap().len(), 3);
let lim = body_bytes(resp(&app, "/session?limit=2", &[]).await).await;
let lim: serde_json::Value = serde_json::from_slice(&lim).unwrap();
assert_eq!(lim.as_array().unwrap().len(), 2);
let s = body_bytes(resp(&app, "/session?search=beta", &[]).await).await;
let s: serde_json::Value = serde_json::from_slice(&s).unwrap();
assert_eq!(s.as_array().unwrap().len(), 1);
assert_eq!(s[0]["title"], "beta");
}
#[tokio::test]
async fn route_closure_shapes() {
let app = app();
let r = resp(&app, "/global/config", &[]).await;
assert_eq!(r.status(), StatusCode::OK);
assert_eq!(
r.headers().get(header::CONTENT_TYPE).unwrap(),
"application/json"
);
let v: serde_json::Value = serde_json::from_slice(&body_bytes(r).await).unwrap();
assert!(v.is_object());
for k in ["agent", "command", "mode", "username"] {
assert!(v.get(k).is_none(), "/global/config must omit {k}: {v}");
}
for (uri, expect_empty) in [("/file/status", true), ("/find/symbol", true)] {
let v: serde_json::Value =
serde_json::from_slice(&body_bytes(resp(&app, uri, &[]).await).await).unwrap();
assert_eq!(
v.as_array().map(|a| a.is_empty()),
Some(expect_empty),
"{uri}"
);
}
let v: serde_json::Value =
serde_json::from_slice(&body_bytes(resp(&app, "/vcs", &[]).await).await).unwrap();
assert!(v.get("branch").is_some() && v.get("default_branch").is_some());
}
#[tokio::test]
async fn vcs_diff_bad_mode_is_query_envelope() {
let app = app();
let r = resp(&app, "/vcs/diff?mode=bogus", &[]).await;
assert_eq!(r.status(), StatusCode::BAD_REQUEST);
let b = body_bytes(r).await;
let v: serde_json::Value = serde_json::from_slice(&b).unwrap();
assert_eq!(v["name"], "BadRequest");
assert_eq!(v["data"]["kind"], "Query");
}
#[tokio::test]
async fn sse_is_never_compressed() {
let app = app();
let r = resp(&app, "/global/event", &[("accept-encoding", "br,gzip")]).await;
assert_eq!(r.status(), StatusCode::OK);
assert!(r.headers().get(header::CONTENT_ENCODING).is_none());
}