← snapshot
19171 bytes
//! Integration tests driving the router directly, no socket.
#![expect(
clippy::unwrap_used,
reason = "test assertions read better with unwrap"
)]
use std::sync::Arc;
use axum::Router;
use axum::body::Body;
use axum::http::{Request, StatusCode, header};
use http_body_util::BodyExt as _;
use jac_serve::state::AppState;
use serde_json::{Value, json};
use tower::ServiceExt as _;
fn app() -> Router {
jac_serve::app(Arc::new(AppState::new()))
}
async fn call(app: &Router, method: &str, uri: &str, body: Option<Value>) -> (StatusCode, Value) {
let request = match body {
Some(v) => Request::builder()
.method(method)
.uri(uri)
.header(header::CONTENT_TYPE, "application/json")
.body(Body::from(v.to_string()))
.unwrap(),
None => Request::builder()
.method(method)
.uri(uri)
.body(Body::empty())
.unwrap(),
};
let response = app.clone().oneshot(request).await.unwrap();
let status = response.status();
let bytes = response.into_body().collect().await.unwrap().to_bytes();
let value = if bytes.is_empty() {
Value::Null
} else {
serde_json::from_slice(&bytes).unwrap()
};
(status, value)
}
fn init_body() -> Value {
json!({
"name": "Meridian Systems",
"founder": { "display_name": "Ada" },
"agents": [ { "model": "loom-bot" } ],
"initial_commit": {
"message": "auth: initial login flow",
"files": [ { "path": "src/auth/login.rs", "content": "fn login() {}" } ]
},
"founding_decision": {
"title": "auth retry fails closed",
"rationale": "401/403 are terminal",
"families": ["constraints"],
"scope": ["src/auth"]
}
})
}
#[tokio::test]
async fn init_roundtrip() {
let app = app();
let (status, body) = call(&app, "POST", "/api/repos", Some(init_body())).await;
assert_eq!(status, StatusCode::CREATED, "{body}");
assert_eq!(body["slug"], "meridian-systems");
assert_eq!(body["founder"]["kind"], "human");
assert_eq!(body["founder"]["display_name"], "Ada");
assert_eq!(body["default_ref"], "main");
assert_eq!(body["head"]["provenance"], "human");
assert_eq!(body["founding_decision"]["state"], "unsettled");
assert!(
body["honesty"]
.as_str()
.unwrap()
.contains("not authenticated")
);
// The repo shows up in the listing with its unsettled count.
let (status, body) = call(&app, "GET", "/api/repos", None).await;
assert_eq!(status, StatusCode::OK);
assert_eq!(body["repos"][0]["decisions"]["unsettled"], 1);
// A duplicate name collides on the slug.
let (status, _) = call(&app, "POST", "/api/repos", Some(init_body())).await;
assert_eq!(status, StatusCode::CONFLICT);
}
#[tokio::test]
async fn commit_log_detail_blob() {
let app = app();
let (_, init) = call(&app, "POST", "/api/repos", Some(init_body())).await;
let agent = init["agents"][0]["id"].as_str().unwrap().to_owned();
let (status, commit) = call(
&app,
"POST",
"/api/repos/meridian-systems/commits",
Some(json!({
"ref": "main",
"actor": { "kind": "agent", "id": agent },
"provenance": "agent",
"message": "auth: retry",
"files": [
{ "path": "src/auth/login.rs", "content": "fn login() {}" },
{ "path": "src/auth/backoff.rs", "content": "fn retry() {}" }
]
})),
)
.await;
assert_eq!(status, StatusCode::CREATED, "{commit}");
assert_eq!(commit["snapshot"]["provenance"], "agent");
assert_eq!(commit["snapshot"]["author"]["display_name"], "loom-bot");
let snap_id = commit["snapshot"]["id"].as_str().unwrap().to_owned();
let (status, log) = call(
&app,
"GET",
"/api/repos/meridian-systems/log?ref=main",
None,
)
.await;
assert_eq!(status, StatusCode::OK);
let entries = log["entries"].as_array().unwrap();
assert_eq!(entries.len(), 2, "initial + retry commit");
assert_eq!(entries[0]["id"], snap_id.as_str());
let (status, detail) = call(
&app,
"GET",
&format!("/api/repos/meridian-systems/snapshots/{snap_id}"),
None,
)
.await;
assert_eq!(status, StatusCode::OK);
let changed: Vec<&str> = detail["changed_from_parent"]
.as_array()
.unwrap()
.iter()
.map(|v| v.as_str().unwrap())
.collect();
assert_eq!(changed, ["src/auth/backoff.rs"]);
let blob = detail["files"]
.as_array()
.unwrap()
.iter()
.find(|f| f["path"] == "src/auth/backoff.rs")
.unwrap()["blob"]
.as_str()
.unwrap()
.to_owned();
let (status, blob_body) = call(
&app,
"GET",
&format!("/api/repos/meridian-systems/blobs/{blob}"),
None,
)
.await;
assert_eq!(status, StatusCode::OK);
assert_eq!(blob_body["content"], "fn retry() {}");
assert_eq!(blob_body["binary"], false);
}
#[tokio::test]
async fn the_gate_story_over_http() {
let app = app();
let (_, init) = call(&app, "POST", "/api/repos", Some(init_body())).await;
let agent = init["agents"][0]["id"].as_str().unwrap().to_owned();
let founder = init["founder"]["id"].as_str().unwrap().to_owned();
let decision = init["founding_decision"]["id"].as_str().unwrap().to_owned();
// Branch and commit into the governed scope.
let (status, _) = call(
&app,
"POST",
"/api/repos/meridian-systems/refs",
Some(json!({ "name": "feature/backoff", "from": { "ref": "main" } })),
)
.await;
assert_eq!(status, StatusCode::CREATED);
call(
&app,
"POST",
"/api/repos/meridian-systems/commits",
Some(json!({
"ref": "feature/backoff",
"actor": { "kind": "agent", "id": agent },
"provenance": "agent",
"message": "auth: retry",
"files": [
{ "path": "src/auth/login.rs", "content": "fn login() {}" },
{ "path": "src/auth/backoff.rs", "content": "fn retry() {}" }
]
})),
)
.await;
// Preview: blocked, and nothing moved.
let (status, preview) = call(
&app,
"GET",
"/api/repos/meridian-systems/promote/preview?from=feature/backoff&into=main",
None,
)
.await;
assert_eq!(status, StatusCode::OK);
assert_eq!(preview["verdict"]["verdict"], "blocked");
assert_eq!(preview["fast_forward"], true);
// The real promotion: 200 with a blocked verdict — a result, not an error.
let (status, verdict) = call(
&app,
"POST",
"/api/repos/meridian-systems/promote",
Some(json!({ "from": "feature/backoff", "into": "main" })),
)
.await;
assert_eq!(status, StatusCode::OK);
assert_eq!(verdict["verdict"], "blocked");
assert_eq!(verdict["reason"], "unsettled-decisions");
assert_eq!(verdict["ref_moved"], false);
assert_eq!(verdict["unsettled"][0], decision.as_str());
// A whitespace statement is refused with the engine's exact message.
let (status, err) = call(
&app,
"POST",
&format!("/api/repos/meridian-systems/decisions/{decision}/attest"),
Some(json!({ "by": founder, "statement": " " })),
)
.await;
assert_eq!(status, StatusCode::UNPROCESSABLE_ENTITY);
assert!(
err["error"]["message"]
.as_str()
.unwrap()
.contains("1..=2000")
);
// Ada attests; the decision settles with her verbatim words.
let (status, settled) = call(
&app,
"POST",
&format!("/api/repos/meridian-systems/decisions/{decision}/attest"),
Some(json!({ "by": founder, "statement": "I walked the 401 path; it fails closed." })),
)
.await;
assert_eq!(status, StatusCode::OK, "{settled}");
assert_eq!(settled["state"], "settled");
assert_eq!(settled["attestation"]["attestor"]["display_name"], "Ada");
// The same promotion, admitted, one decision checked, ref moved.
let (status, verdict) = call(
&app,
"POST",
"/api/repos/meridian-systems/promote",
Some(json!({ "from": "feature/backoff", "into": "main" })),
)
.await;
assert_eq!(status, StatusCode::OK);
assert_eq!(verdict["verdict"], "admitted");
assert_eq!(verdict["decisions_checked"], 1);
assert_eq!(verdict["ungoverned"], false);
assert_eq!(verdict["ref_moved"], true);
}
#[tokio::test]
async fn ungoverned_admission_says_so() {
let app = app();
let (_, init) = call(&app, "POST", "/api/repos", Some(init_body())).await;
let founder = init["founder"]["id"].as_str().unwrap().to_owned();
call(
&app,
"POST",
"/api/repos/meridian-systems/refs",
Some(json!({ "name": "docs/pass", "from": { "ref": "main" } })),
)
.await;
call(
&app,
"POST",
"/api/repos/meridian-systems/commits",
Some(json!({
"ref": "docs/pass",
"actor": { "kind": "human", "id": founder },
"provenance": "human",
"message": "docs only",
"files": [
{ "path": "src/auth/login.rs", "content": "fn login() {}" },
{ "path": "docs/readme.md", "content": "# readme" }
]
})),
)
.await;
let (status, verdict) = call(
&app,
"POST",
"/api/repos/meridian-systems/promote",
Some(json!({ "from": "docs/pass", "into": "main" })),
)
.await;
assert_eq!(status, StatusCode::OK);
assert_eq!(verdict["verdict"], "admitted");
assert_eq!(verdict["decisions_checked"], 0);
assert_eq!(
verdict["ungoverned"], true,
"zero checked must be reported as ungoverned, not as verification"
);
}
#[tokio::test]
async fn non_fast_forward_is_refused() {
let app = app();
let (_, init) = call(&app, "POST", "/api/repos", Some(init_body())).await;
let founder = init["founder"]["id"].as_str().unwrap().to_owned();
// A stray ref with no shared history.
call(
&app,
"POST",
"/api/repos/meridian-systems/commits",
Some(json!({
"ref": "stray",
"actor": { "kind": "human", "id": founder },
"provenance": "human",
"message": "unrelated",
"files": [ { "path": "b.rs", "content": "2" } ]
})),
)
.await;
let (status, err) = call(
&app,
"POST",
"/api/repos/meridian-systems/promote",
Some(json!({ "from": "stray", "into": "main" })),
)
.await;
assert_eq!(status, StatusCode::CONFLICT);
assert_eq!(err["error"]["code"], "non-fast-forward");
}
#[tokio::test]
async fn rendezvous_across_repos() {
let app = app();
let (_, seed) = call(&app, "POST", "/api/demo", None).await;
assert_eq!(seed["repos"][0], "meridian-systems");
// The board holds three publications, none carrying content.
let (status, board) = call(&app, "GET", "/api/rendezvous/publications", None).await;
assert_eq!(status, StatusCode::OK);
assert_eq!(board["publications"].as_array().unwrap().len(), 3);
// Halcyon's probe matched Meridian's decision sketch during the seed.
assert!(
!seed["rendezvous"]["matches"].as_array().unwrap().is_empty(),
"the twin decisions should rendezvous"
);
let (_, intros) = call(&app, "GET", "/api/rendezvous/introductions", None).await;
assert_eq!(intros["introductions"].as_array().unwrap().len(), 1);
// The live gate: the parked branch previews blocked.
let (status, preview) = call(
&app,
"GET",
"/api/repos/meridian-systems/promote/preview?from=feature/session-cache&into=main",
None,
)
.await;
assert_eq!(status, StatusCode::OK);
assert_eq!(preview["verdict"]["verdict"], "blocked");
// Seeding twice collides, honestly.
let (status, _) = call(&app, "POST", "/api/demo", None).await;
assert_eq!(status, StatusCode::CONFLICT);
}
/// A remark is raised against a real claim, and settles exactly once.
#[tokio::test]
async fn remarks_anchor_and_settle() {
let app = app();
let (_, seeded) = call(&app, "POST", "/api/demo", None).await;
let slug = seeded["repos"][0].as_str().unwrap().to_owned();
let (_, detail) = call(&app, "GET", &format!("/api/repos/{slug}"), None).await;
let founder = detail["humans"][0]["id"].as_str().unwrap().to_owned();
// Nothing to act on yet.
let (status, empty) = call(&app, "GET", &format!("/api/repos/{slug}/remarks"), None).await;
assert_eq!(status, StatusCode::OK);
assert_eq!(empty["open"], 0);
let raise = json!({
"anchor": {
"kind": "decision",
"id": "whatever",
"quote": "Exponential backoff is for transient network errors only."
},
"body": "Use randomness to avoid a thundering herd problem.",
"kind": "suggestion",
"by": founder,
});
let (status, remark) = call(
&app,
"POST",
&format!("/api/repos/{slug}/remarks"),
Some(raise),
)
.await;
assert_eq!(status, StatusCode::CREATED);
assert_eq!(remark["state"], "open");
assert_eq!(remark["kind"], "suggestion");
// The quote travels with it: the suggestion is unreadable without the
// sentence that prompted it.
assert!(
remark["anchor"]["quote"]
.as_str()
.unwrap()
.contains("transient network errors")
);
let id = remark["id"].as_str().unwrap().to_owned();
let (_, open) = call(
&app,
"GET",
&format!("/api/repos/{slug}/remarks?state=open"),
None,
)
.await;
assert_eq!(open["open"], 1);
// Drafting has to name what it became.
let (status, _) = call(
&app,
"POST",
&format!("/api/repos/{slug}/remarks/{id}/settle"),
Some(json!({ "outcome": "drafted" })),
)
.await;
assert_eq!(status, StatusCode::UNPROCESSABLE_ENTITY);
let (status, settled) = call(
&app,
"POST",
&format!("/api/repos/{slug}/remarks/{id}/settle"),
Some(json!({ "outcome": "drafted", "decision": "abc123" })),
)
.await;
assert_eq!(status, StatusCode::OK);
assert_eq!(settled["state"], "drafted");
assert_eq!(settled["outcome"]["decision"], "abc123");
// Settling twice is a conflict, not a silent overwrite.
let (status, _) = call(
&app,
"POST",
&format!("/api/repos/{slug}/remarks/{id}/settle"),
Some(json!({ "outcome": "declined" })),
)
.await;
assert_eq!(status, StatusCode::CONFLICT);
let (_, after) = call(
&app,
"GET",
&format!("/api/repos/{slug}/remarks?state=open"),
None,
)
.await;
assert_eq!(after["open"], 0);
}
/// An empty remark, an unknown kind, and a stranger are all refused.
#[tokio::test]
async fn remarks_refuse_nonsense() {
let app = app();
let (_, seeded) = call(&app, "POST", "/api/demo", None).await;
let slug = seeded["repos"][0].as_str().unwrap().to_owned();
let (_, detail) = call(&app, "GET", &format!("/api/repos/{slug}"), None).await;
let founder = detail["humans"][0]["id"].as_str().unwrap().to_owned();
let anchor = json!({ "kind": "file", "id": "src/net/retry.rs" });
for body in [
json!({ "anchor": anchor, "body": " ", "kind": "suggestion", "by": founder }),
json!({ "anchor": anchor, "body": "ok", "kind": "shout", "by": founder }),
json!({ "anchor": anchor, "body": "ok", "kind": "note", "by": "hum-0000000000009999" }),
json!({
"anchor": json!({ "kind": "elsewhere", "id": "x" }),
"body": "ok", "kind": "note", "by": founder
}),
] {
let (status, _) = call(
&app,
"POST",
&format!("/api/repos/{slug}/remarks"),
Some(body),
)
.await;
assert_eq!(status, StatusCode::UNPROCESSABLE_ENTITY);
}
}
/// Transcription answers honestly when no model is configured.
#[tokio::test]
async fn transcribe_says_when_unavailable() {
// The test process has no WHISPER_MODEL, which is the unconfigured case.
let app = app();
let (status, body) = call(&app, "GET", "/api/transcribe", None).await;
assert_eq!(status, StatusCode::OK);
assert_eq!(body["configured"], false);
// Availability is a 200 saying "no", never an error — the caller is
// expected to fall back, not to treat this as a failure.
assert_eq!(body["engine"], "whisper.cpp");
}
/// A repository written to disk and read back reproduces the same addresses.
///
/// This is the only real test that an export means anything: a snapshot's id
/// has its timestamp, author, provenance, and tree hashed into it, so if any
/// of those is re-derived rather than replayed the ids drift and what is on
/// disk describes a history the engine can no longer produce.
#[tokio::test]
async fn a_repo_round_trips_through_disk() {
use jac_serve::gitimport::Bounds;
// Import this very repository — it is the git working copy the tests run
// inside, so no fixture has to be fabricated.
let Some(source) = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.and_then(std::path::Path::parent)
.map(std::path::Path::to_path_buf)
else {
return;
};
if !source.join(".git").exists() {
// Vendored checkouts have no .git; nothing to assert against.
return;
}
let out = std::env::temp_dir().join(format!("jac-roundtrip-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&out);
let bounds = Bounds {
commits: 3,
paths: 12,
};
let Ok(manifest) = jac_serve::batch::one(&source, &out, &bounds) else {
// A shallow clone or an empty window is not a failure of this claim.
return;
};
let slug = manifest["slug"].as_str().unwrap_or_default().to_owned();
assert!(!slug.is_empty(), "the import wrote no slug");
let dir = out.join(&slug);
let raw = std::fs::read(dir.join("snapshots.json")).unwrap_or_default();
let written: Vec<Value> = serde_json::from_slice(&raw).unwrap_or_default();
assert!(!written.is_empty(), "the import wrote nothing");
let read_back = jac_serve::store::load(&dir);
assert!(
read_back.is_ok(),
"reading the repo back failed: {:?}",
read_back.as_ref().err().map(ToString::to_string),
);
let Ok(loaded) = read_back else { return };
assert!(
loaded.mismatched.is_empty(),
"reading a repo back changed {} of {} addresses: {:?}",
loaded.mismatched.len(),
written.len(),
loaded.mismatched,
);
let _ = std::fs::remove_dir_all(&out);
}