use lifeloop::{
AcceptablePlacement, CallbackRequest, CallbackResponse, FailureClass, FrameContext,
IntegrationMode, LifecycleEventKind, PayloadEnvelope, PayloadRef, PlacementClass,
ReceiptStatus, RequirementLevel, SCHEMA_VERSION,
};
use std::io::Write;
use std::process::{Command, Stdio};
fn frame_opening_request() -> CallbackRequest {
CallbackRequest {
schema_version: SCHEMA_VERSION.to_string(),
event: LifecycleEventKind::FrameOpening,
event_id: "evt-1".into(),
adapter_id: "codex".into(),
adapter_version: "0.1.0".into(),
integration_mode: IntegrationMode::NativeHook,
invocation_id: "inv-1".into(),
harness_session_id: Some("session-123".into()),
harness_run_id: None,
harness_task_id: None,
frame_context: Some(FrameContext::top_level("frm-1")),
capability_snapshot_ref: None,
payload_refs: vec![PayloadRef {
payload_id: "pay-staged-1".into(),
payload_kind: "instruction_frame".into(),
content_digest: None,
byte_size: Some(11),
}],
sequence: Some(1),
idempotency_key: Some("idem-1".into()),
metadata: serde_json::Map::new(),
}
}
fn fake_client(req: &CallbackRequest) -> CallbackResponse {
if req.validate().is_err() {
return CallbackResponse::failed(FailureClass::InvalidRequest);
}
match req.event {
LifecycleEventKind::FrameOpening => {
let payload = PayloadEnvelope {
schema_version: SCHEMA_VERSION.to_string(),
payload_id: "pay-fake-1".into(),
client_id: "fake-client".into(),
payload_kind: "instruction_frame".into(),
format: "client-defined".into(),
content_encoding: "utf8".into(),
body: Some("hello from the fake client".into()),
body_ref: None,
byte_size: 27,
content_digest: None,
acceptable_placements: vec![AcceptablePlacement {
placement: PlacementClass::PrePromptFrame,
requirement: RequirementLevel::Preferred,
}],
idempotency_key: req.idempotency_key.clone(),
expires_at_epoch_s: None,
redaction: None,
metadata: serde_json::Map::new(),
};
let mut resp = CallbackResponse::ok(ReceiptStatus::Delivered);
resp.client_payloads.push(payload);
resp
}
_ => CallbackResponse::ok(ReceiptStatus::Delivered),
}
}
#[test]
fn fake_client_produces_valid_response_for_frame_opening() {
let req = frame_opening_request();
let resp = fake_client(&req);
assert_eq!(resp.status, ReceiptStatus::Delivered);
assert_eq!(resp.client_payloads.len(), 1);
assert!(resp.validate().is_ok());
let payload = &resp.client_payloads[0];
assert_eq!(payload.client_id, "fake-client");
assert_eq!(payload.idempotency_key.as_deref(), Some("idem-1"));
}
#[test]
fn fake_client_fails_closed_on_invalid_request() {
let mut req = frame_opening_request();
req.frame_context = None;
let resp = fake_client(&req);
assert_eq!(resp.status, ReceiptStatus::Failed);
assert_eq!(resp.failure_class, Some(FailureClass::InvalidRequest));
assert!(resp.validate().is_ok());
}
#[test]
fn fake_client_handles_session_started_with_no_payload() {
let mut req = frame_opening_request();
req.event = LifecycleEventKind::SessionStarted;
req.frame_context = None;
let resp = fake_client(&req);
assert_eq!(resp.status, ReceiptStatus::Delivered);
assert!(resp.client_payloads.is_empty());
assert!(resp.validate().is_ok());
}
fn lifeloop_bin() -> std::path::PathBuf {
std::path::PathBuf::from(env!("CARGO_BIN_EXE_lifeloop"))
}
fn run_cli(args: &[&str], stdin_bytes: &[u8]) -> (i32, String, String) {
let mut child = Command::new(lifeloop_bin())
.args(args)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("spawn lifeloop binary");
child
.stdin
.as_mut()
.unwrap()
.write_all(stdin_bytes)
.expect("pipe stdin");
let output = child.wait_with_output().expect("wait for lifeloop");
(
output.status.code().unwrap_or(-1),
String::from_utf8_lossy(&output.stdout).into_owned(),
String::from_utf8_lossy(&output.stderr).into_owned(),
)
}
#[test]
fn cli_validates_a_well_formed_request() {
let req = frame_opening_request();
let bytes = serde_json::to_vec(&req).unwrap();
let (code, stdout, stderr) = run_cli(&["envelope", "validate", "request"], &bytes);
assert_eq!(code, 0, "stdout=`{stdout}` stderr=`{stderr}`");
assert_eq!(stdout.trim(), "ok");
assert!(stderr.is_empty());
}
#[test]
fn cli_rejects_a_request_with_missing_frame_context() {
let mut req = frame_opening_request();
req.frame_context = None;
let bytes = serde_json::to_vec(&req).unwrap();
let (code, _stdout, stderr) = run_cli(&["envelope", "validate", "request"], &bytes);
assert_eq!(code, 1, "expected validation exit (1), got {code}");
assert!(stderr.contains("frame.* events require frame_context"));
}
#[test]
fn cli_rejects_a_request_with_unknown_event_name() {
let mut value = serde_json::to_value(frame_opening_request()).unwrap();
value["event"] = serde_json::Value::String("session.unknown".into());
let bytes = serde_json::to_vec(&value).unwrap();
let (code, _stdout, stderr) = run_cli(&["envelope", "validate", "request"], &bytes);
assert_eq!(code, 3, "expected input exit (3), got {code}");
assert!(
stderr.contains("session.unknown") || stderr.contains("unknown variant"),
"stderr should name the bad variant: {stderr}"
);
}
#[test]
fn cli_validates_a_response() {
let resp = fake_client(&frame_opening_request());
let bytes = serde_json::to_vec(&resp).unwrap();
let (code, stdout, stderr) = run_cli(&["envelope", "validate", "response"], &bytes);
assert_eq!(code, 0, "stderr=`{stderr}`");
assert_eq!(stdout.trim(), "ok");
}
#[test]
fn cli_rejects_a_failed_response_missing_failure_class() {
let mut resp = CallbackResponse::failed(FailureClass::Timeout);
resp.failure_class = None;
let bytes = serde_json::to_vec(&resp).unwrap();
let (code, _stdout, stderr) = run_cli(&["envelope", "validate", "response"], &bytes);
assert_eq!(code, 1, "expected validation exit (1), got {code}");
assert!(stderr.contains("failure_class"));
}
#[test]
fn cli_echoes_a_request_through_serde_roundtrip() {
let req = frame_opening_request();
let bytes = serde_json::to_vec(&req).unwrap();
let (code, stdout, _stderr) = run_cli(&["envelope", "echo", "request"], &bytes);
assert_eq!(code, 0);
let parsed: CallbackRequest = serde_json::from_str(stdout.trim())
.expect("echo output should re-parse as a CallbackRequest");
assert_eq!(parsed, req);
}
#[test]
fn cli_rejects_empty_stdin() {
let (code, _stdout, stderr) = run_cli(&["envelope", "validate", "request"], b"");
assert_eq!(code, 3, "expected input exit (3), got {code}");
assert!(stderr.contains("stdin is empty"));
}
#[test]
fn cli_rejects_unknown_subcommand_with_usage_exit() {
let (code, _stdout, stderr) = run_cli(&["bogus"], b"");
assert_eq!(code, 2, "expected usage exit (2), got {code}");
assert!(stderr.contains("unknown command"));
}
#[test]
fn cli_rejects_envelope_kind_with_usage_exit() {
let (code, _stdout, stderr) = run_cli(&["envelope", "validate", "manifest"], b"");
assert_eq!(code, 2, "expected usage exit (2), got {code}");
assert!(stderr.contains("unknown kind"));
}