use std::collections::BTreeMap;
use lifeloop::router::{
AdapterRegistry, AdapterResolution, CallbackInvoker, DefaultCallbackInvoker, FnClientCallback,
RoutingPlan, route,
};
use lifeloop::{
AcceptablePlacement, AdapterManifest, AdapterRole, CallbackRequest, CallbackResponse,
ConformanceLevel, FailureClass, FrameContext, IntegrationMode, LifecycleEventKind,
ManifestContextPressure, ManifestPlacementClass, ManifestPlacementSupport, ManifestReceipts,
PayloadEnvelope, PayloadRef, PlacementClass, ReceiptStatus, RegisteredAdapter,
RequirementLevel, SCHEMA_VERSION, SupportState,
};
const FAKE_ID: &str = "fake-adapter";
const FAKE_VERSION: &str = "0.0.1";
fn manifest() -> AdapterManifest {
let mut placement = BTreeMap::new();
placement.insert(
ManifestPlacementClass::PreFrameTrailing,
ManifestPlacementSupport {
support: SupportState::Native,
max_bytes: None,
},
);
AdapterManifest {
contract_version: SCHEMA_VERSION.to_string(),
adapter_id: FAKE_ID.into(),
adapter_version: FAKE_VERSION.into(),
display_name: "Fake".into(),
role: AdapterRole::PrimaryWorker,
integration_modes: vec![IntegrationMode::NativeHook],
lifecycle_events: BTreeMap::new(),
placement,
context_pressure: ManifestContextPressure {
support: SupportState::Native,
evidence: None,
},
receipts: ManifestReceipts {
native: false,
lifeloop_synthesized: true,
receipt_ledger: SupportState::Unavailable,
},
session_identity: None,
session_rename: None,
renewal: None,
approval_surface: None,
failure_modes: Vec::new(),
telemetry_sources: Vec::new(),
known_degradations: Vec::new(),
}
}
struct Fixture(AdapterManifest);
impl AdapterRegistry for Fixture {
fn resolve(&self, id: &str, version: &str) -> AdapterResolution {
if id != self.0.adapter_id {
return AdapterResolution::UnknownId;
}
if version != self.0.adapter_version {
return AdapterResolution::VersionMismatch {
registered_version: self.0.adapter_version.clone(),
};
}
AdapterResolution::Found(RegisteredAdapter {
manifest: self.0.clone(),
conformance: ConformanceLevel::PreConformance,
})
}
}
fn frame_request() -> CallbackRequest {
CallbackRequest {
schema_version: SCHEMA_VERSION.to_string(),
event: LifecycleEventKind::FrameOpening,
event_id: "evt-cb-1".into(),
adapter_id: FAKE_ID.into(),
adapter_version: FAKE_VERSION.into(),
integration_mode: IntegrationMode::NativeHook,
invocation_id: "inv-cb-1".into(),
harness_session_id: Some("sess-1".into()),
harness_run_id: Some("run-1".into()),
harness_task_id: None,
frame_context: Some(FrameContext::top_level("frm-1")),
capability_snapshot_ref: None,
payload_refs: vec![PayloadRef {
payload_id: "pay-1".into(),
payload_kind: "instruction_frame".into(),
content_digest: None,
byte_size: Some(11),
}],
sequence: None,
idempotency_key: Some("idem-cb-1".into()),
metadata: serde_json::Map::new(),
}
}
fn build_plan() -> RoutingPlan {
let fx = Fixture(manifest());
route(&frame_request(), &fx).expect("plan builds")
}
fn opaque_payload(body: &str) -> PayloadEnvelope {
PayloadEnvelope {
schema_version: SCHEMA_VERSION.to_string(),
payload_id: "pay-1".into(),
client_id: "fake-client".into(),
payload_kind: "instruction_frame".into(),
format: "client-defined".into(),
content_encoding: "utf8".into(),
body: Some(body.into()),
body_ref: None,
byte_size: body.len() as u64,
content_digest: None,
acceptable_placements: vec![AcceptablePlacement {
placement: PlacementClass::PrePromptFrame,
requirement: RequirementLevel::Preferred,
}],
idempotency_key: None,
expires_at_epoch_s: None,
redaction: None,
metadata: serde_json::Map::new(),
}
}
#[test]
fn invoker_synthesizes_request_from_plan() {
let plan = build_plan();
let req = lifeloop::router::synthesize_request(&plan);
assert_eq!(req.schema_version, SCHEMA_VERSION);
assert_eq!(req.event, LifecycleEventKind::FrameOpening);
assert_eq!(req.event_id, "evt-cb-1");
assert_eq!(req.adapter_id, FAKE_ID);
assert_eq!(req.adapter_version, FAKE_VERSION);
assert_eq!(req.integration_mode, IntegrationMode::NativeHook);
assert_eq!(req.invocation_id, "inv-cb-1");
assert_eq!(req.idempotency_key.as_deref(), Some("idem-cb-1"));
assert_eq!(req.payload_refs.len(), 1);
assert_eq!(req.payload_refs[0].payload_id, "pay-1");
}
#[test]
fn fake_client_roundtrip_returns_response_unmodified() {
let plan = build_plan();
let payload = opaque_payload("hello opaque");
let payload_clone = payload.clone();
let cb = FnClientCallback::<_, std::convert::Infallible>::new(
move |req: &CallbackRequest, payloads: &[PayloadEnvelope]| {
assert_eq!(req.event_id, "evt-cb-1");
assert_eq!(payloads.len(), 1);
assert_eq!(payloads[0].body.as_deref(), Some("hello opaque"));
Ok(CallbackResponse::ok(ReceiptStatus::Delivered))
},
);
let invoker = DefaultCallbackInvoker::new(cb);
let resp = invoker.invoke(&plan, &[payload_clone]).unwrap();
assert_eq!(resp.status, ReceiptStatus::Delivered);
assert_eq!(payload.body.as_deref(), Some("hello opaque"));
}
#[test]
fn fake_client_can_return_failure() {
let plan = build_plan();
let cb = FnClientCallback::<_, std::convert::Infallible>::new(
|_req: &CallbackRequest, _payloads: &[PayloadEnvelope]| {
Ok(CallbackResponse::failed(FailureClass::TransportError))
},
);
let invoker = DefaultCallbackInvoker::new(cb);
let resp = invoker.invoke(&plan, &[]).unwrap();
assert_eq!(resp.status, ReceiptStatus::Failed);
assert_eq!(resp.failure_class, Some(FailureClass::TransportError));
}
#[test]
fn fake_client_error_propagates_through_invoker() {
#[derive(Debug, PartialEq)]
struct ClientErr(&'static str);
let plan = build_plan();
let cb = FnClientCallback::<_, ClientErr>::new(|_, _| Err(ClientErr("simulated")));
let invoker = DefaultCallbackInvoker::new(cb);
let err = invoker.invoke(&plan, &[]).unwrap_err();
assert_eq!(err, ClientErr("simulated"));
}