use std::sync::Arc;
use axum::body::Body;
use axum::http::{Request, StatusCode};
use tower::ServiceExt;
use super::*;
use crate::daemon::api::router;
use crate::daemon::state::DaemonState;
fn bus() -> (tempfile::TempDir, PeerBus) {
let dir = tempfile::tempdir().unwrap();
let bus = PeerBus::new(dir.path());
(dir, bus)
}
fn peer_caller(instance_id: &str, definition_id: &str) -> CallerIdentity {
CallerIdentity {
kind: CallerKind::AssistantInstance,
instance_id: Some(instance_id.into()),
definition_id: Some(definition_id.into()),
channel_origin: None,
}
}
fn caller_for(meta: &InstanceMeta) -> CallerIdentity {
CallerIdentity {
kind: CallerKind::AssistantInstance,
instance_id: Some(meta.instance_id.clone()),
definition_id: Some(meta.definition_id.clone()),
channel_origin: None,
}
}
fn registered_sender(bus: &PeerBus, definition_id: &str) -> CallerIdentity {
let meta = bus.registry().register(definition_id, None).unwrap();
caller_for(&meta)
}
fn chat(text: &str) -> BusPayload {
BusPayload::ChatText { text: text.into() }
}
fn read_log(bus: &PeerBus) -> Vec<BusEnvelope> {
let contents = std::fs::read_to_string(bus.log_path()).unwrap_or_default();
contents
.lines()
.map(|l| serde_json::from_str(l).expect("every bus log line must parse as an envelope"))
.collect()
}
#[test]
fn envelope_round_trips_json() {
let env = BusEnvelope::new(
BusEdge::AssistantAssistant,
peer_caller("izzie~aaaa1111", "izzie"),
Recipient {
instance_id: Some("cto-assistant~bbbb2222".into()),
definition_id: Some("cto-assistant".into()),
},
BusPayload::PeerRequest {
text: "review this ADR".into(),
},
Some("prior-message".into()),
DeliveryState::Delivered,
);
let json = serde_json::to_string(&env).unwrap();
let back: BusEnvelope = serde_json::from_str(&json).unwrap();
assert_eq!(back, env);
}
#[test]
fn envelope_matches_doc60_field_names() {
let env = BusEnvelope::new(
BusEdge::AssistantAssistant,
peer_caller("izzie~aaaa1111", "izzie"),
Recipient {
instance_id: Some("cto-assistant~bbbb2222".into()),
definition_id: Some("cto-assistant".into()),
},
chat("hello"),
None,
DeliveryState::Delivered,
);
let v = serde_json::to_value(&env).unwrap();
assert!(v.get("message_id").is_some());
assert!(v.get("ts").is_some());
assert_eq!(v["edge"], "assistant_assistant");
assert_eq!(v["from"]["kind"], "assistant_instance");
assert_eq!(v["delivery_state"], "delivered");
assert_eq!(v["payload"]["type"], "chat_text");
assert_eq!(v["to"]["definition_id"], "cto-assistant");
}
#[test]
fn envelope_ids_are_unique() {
let mk = || {
BusEnvelope::new(
BusEdge::AssistantAssistant,
peer_caller("izzie~aaaa1111", "izzie"),
Recipient {
instance_id: None,
definition_id: Some("cto-assistant".into()),
},
chat("x"),
None,
DeliveryState::Dropped,
)
};
assert_ne!(mk().message_id, mk().message_id);
}
#[test]
fn peer_request_is_declinable() {
let decline = BusPayload::PeerResponse {
accepted: false,
text: "declining — out of my scope".into(),
};
let v = serde_json::to_value(&decline).unwrap();
assert_eq!(v["type"], "peer_response");
assert_eq!(v["accepted"], false);
let req = serde_json::to_value(BusPayload::PeerRequest {
text: "do x".into(),
})
.unwrap();
let info = serde_json::to_value(chat("do x")).unwrap();
assert_ne!(req["type"], info["type"]);
}
#[test]
fn caller_validation_rejects_bare_peer() {
let bare = CallerIdentity {
kind: CallerKind::AssistantInstance,
instance_id: None,
definition_id: Some("izzie".into()),
channel_origin: None,
};
assert!(matches!(bare.validate(), Err(BusError::InvalidCaller(_))));
}
#[test]
fn caller_validation_accepts_user() {
let user = CallerIdentity {
kind: CallerKind::User,
instance_id: None,
definition_id: None,
channel_origin: None,
};
assert!(user.validate().is_ok());
}
#[test]
fn channel_caller_requires_origin() {
let bad = CallerIdentity {
kind: CallerKind::Channel,
instance_id: None,
definition_id: None,
channel_origin: None,
};
assert!(matches!(bad.validate(), Err(BusError::InvalidCaller(_))));
let good = CallerIdentity {
kind: CallerKind::Channel,
instance_id: None,
definition_id: None,
channel_origin: Some(ChannelOrigin {
connector: "slack".into(),
human_sender: "U012ABC".into(),
channel: "C0123".into(),
workspace: "T0123".into(),
}),
};
assert!(good.validate().is_ok());
}
#[test]
fn register_mints_prefixed_id() {
let reg = InstanceRegistry::default();
let meta = reg.register("izzie", None).unwrap();
assert!(meta.instance_id.starts_with("izzie~"));
assert_eq!(meta.definition_id, "izzie");
assert!(!meta.registered_at.is_empty());
let second = reg.register("izzie", None).unwrap();
assert_ne!(meta.instance_id, second.instance_id);
assert_eq!(reg.len(), 2);
}
#[test]
fn register_rejects_bad_definition() {
let reg = InstanceRegistry::default();
for bad in [
"",
"has~tilde",
"has space",
"has#hash",
"has/slash",
"has?q",
] {
assert!(
matches!(
reg.register(bad, None),
Err(BusError::InvalidDefinitionId { .. })
),
"{bad:?} must be rejected"
);
}
assert!(reg.is_empty());
}
#[tokio::test]
async fn instance_id_is_url_path_safe() {
let (_dir, state) = test_state();
let meta = state
.bus()
.registry()
.register("cto-assistant", None)
.unwrap();
assert!(
!meta.instance_id.contains('#'),
"minted id must not carry the URI fragment delimiter"
);
let (status, _) = call(
&state,
Request::builder()
.method("DELETE")
.uri(format!("/api/v1/bus/instances/{}", meta.instance_id))
.body(Body::empty())
.unwrap(),
)
.await;
assert_eq!(status, StatusCode::NO_CONTENT);
assert!(state.bus().registry().is_empty());
}
#[test]
fn resolve_definition_picks_most_recent() {
let reg = InstanceRegistry::default();
let _first = reg.register("izzie", None).unwrap();
let second = reg.register("izzie", None).unwrap();
let resolved = reg.resolve_definition("izzie").unwrap();
assert_eq!(resolved.meta.instance_id, second.instance_id);
}
#[test]
fn resolve_definition_missing_errors() {
let reg = InstanceRegistry::default();
assert!(matches!(
reg.resolve_definition("nobody-home"),
Err(BusError::NoLiveInstance { .. })
));
}
#[test]
fn deregister_makes_instance_gone() {
let reg = InstanceRegistry::default();
let meta = reg.register("izzie", None).unwrap();
assert!(reg.resolve_instance(&meta.instance_id).is_ok());
assert!(reg.deregister(&meta.instance_id));
assert!(reg.is_empty());
assert!(matches!(
reg.resolve_instance(&meta.instance_id),
Err(BusError::InstanceGone { .. })
));
assert!(!reg.deregister(&meta.instance_id));
}
#[test]
fn list_returns_live_instances() {
let reg = InstanceRegistry::default();
let a = reg.register("izzie", Some("trusty-tools".into())).unwrap();
let b = reg.register("cto-assistant", None).unwrap();
let live = reg.live();
assert_eq!(live.len(), 2);
assert_eq!(live[0].instance_id, a.instance_id);
assert_eq!(live[1].instance_id, b.instance_id);
assert_eq!(live[0].project.as_deref(), Some("trusty-tools"));
}
#[test]
fn bypass_to_dead_instance_errors_not_falls_back() {
let reg = InstanceRegistry::default();
let dead = reg.register("izzie", None).unwrap();
reg.deregister(&dead.instance_id);
let sibling = reg.register("izzie", None).unwrap();
assert!(reg.resolve_definition("izzie").is_ok());
let err = reg
.resolve(&PeerTarget::Instance(dead.instance_id.clone()))
.unwrap_err();
assert_eq!(
err,
BusError::InstanceGone {
instance_id: dead.instance_id.clone()
}
);
assert_ne!(
err,
BusError::NoLiveInstance {
definition_id: "izzie".into()
}
);
assert!(reg.resolve_instance(&sibling.instance_id).is_ok());
}
#[test]
fn bypass_failure_is_distinguishable_from_never_existed() {
assert_eq!(
BusError::InstanceGone {
instance_id: "izzie~dead".into()
}
.status(),
StatusCode::GONE
);
assert_eq!(
BusError::NoLiveInstance {
definition_id: "izzie".into()
}
.status(),
StatusCode::NOT_FOUND
);
}
#[test]
fn publish_bypass_to_dead_instance_does_not_deliver_to_sibling() {
let (_dir, bus) = bus();
let dead = bus.registry().register("izzie", None).unwrap();
bus.registry().deregister(&dead.instance_id);
let sibling = bus.registry().register("izzie", None).unwrap();
let mut sibling_rx = bus.subscribe(&sibling.instance_id).unwrap();
let err = bus
.publish(
registered_sender(&bus, "cto-assistant"),
&PeerTarget::Instance(dead.instance_id.clone()),
chat("continue our thread"),
None,
)
.unwrap_err();
assert!(matches!(err, BusError::InstanceGone { .. }));
assert!(
sibling_rx.try_recv().is_err(),
"a live sibling must NOT receive a message addressed to a dead instance"
);
}
#[test]
fn publish_delivers_to_definition_addressed_instance() {
let (_dir, bus) = bus();
let target = bus.registry().register("cto-assistant", None).unwrap();
let mut rx = bus.subscribe(&target.instance_id).unwrap();
let sent = bus
.publish(
registered_sender(&bus, "izzie"),
&PeerTarget::Definition("cto-assistant".into()),
chat("what do you think?"),
None,
)
.unwrap();
let got = rx.try_recv().unwrap();
assert_eq!(got, sent);
assert_eq!(got.delivery_state, DeliveryState::Delivered);
assert_eq!(
got.to.instance_id.as_deref(),
Some(target.instance_id.as_str())
);
}
#[test]
fn bypass_publish_stamps_both_ids() {
let (_dir, bus) = bus();
let target = bus.registry().register("cto-assistant", None).unwrap();
let _rx = bus.subscribe(&target.instance_id).unwrap();
let sent = bus
.publish(
registered_sender(&bus, "izzie"),
&PeerTarget::Instance(target.instance_id.clone()),
chat("same thread"),
None,
)
.unwrap();
assert_eq!(
sent.to.instance_id.as_deref(),
Some(target.instance_id.as_str())
);
assert_eq!(sent.to.definition_id.as_deref(), Some("cto-assistant"));
}
#[test]
fn publish_reaches_only_target_instance() {
let (_dir, bus) = bus();
let a = bus.registry().register("izzie", None).unwrap();
let b = bus.registry().register("cto-assistant", None).unwrap();
let mut rx_a = bus.subscribe(&a.instance_id).unwrap();
let mut rx_b = bus.subscribe(&b.instance_id).unwrap();
bus.publish(
registered_sender(&bus, "izzie"),
&PeerTarget::Instance(b.instance_id.clone()),
chat("for b only"),
None,
)
.unwrap();
assert!(rx_b.try_recv().is_ok());
assert!(
rx_a.try_recv().is_err(),
"a non-target must receive nothing"
);
}
#[test]
fn publish_threads_replies() {
let (_dir, bus) = bus();
let target = bus.registry().register("cto-assistant", None).unwrap();
let _rx = bus.subscribe(&target.instance_id).unwrap();
let sender = registered_sender(&bus, "izzie");
let first = bus
.publish(
sender.clone(),
&PeerTarget::Definition("cto-assistant".into()),
BusPayload::PeerRequest {
text: "please review".into(),
},
None,
)
.unwrap();
let reply = bus
.publish(
sender.clone(),
&PeerTarget::Definition("cto-assistant".into()),
BusPayload::PeerResponse {
accepted: false,
text: "declining".into(),
},
Some(first.message_id.clone()),
)
.unwrap();
assert_eq!(
reply.in_reply_to.as_deref(),
Some(first.message_id.as_str())
);
}
#[test]
fn publish_without_subscriber_errors() {
let (_dir, bus) = bus();
let target = bus.registry().register("cto-assistant", None).unwrap();
let err = bus
.publish(
registered_sender(&bus, "izzie"),
&PeerTarget::Instance(target.instance_id.clone()),
chat("anyone there?"),
None,
)
.unwrap_err();
assert_eq!(
err,
BusError::NoSubscriber {
instance_id: target.instance_id
}
);
}
#[test]
fn publish_rejects_unattributable_caller() {
let (_dir, bus) = bus();
let target = bus.registry().register("cto-assistant", None).unwrap();
let _rx = bus.subscribe(&target.instance_id).unwrap();
let bad = CallerIdentity {
kind: CallerKind::AssistantInstance,
instance_id: None,
definition_id: Some("izzie".into()),
channel_origin: None,
};
assert!(matches!(
bus.publish(
bad,
&PeerTarget::Definition("cto-assistant".into()),
chat("x"),
None
),
Err(BusError::InvalidCaller(_))
));
}
#[test]
fn subscribe_to_dead_instance_errors() {
let (_dir, bus) = bus();
assert!(matches!(
bus.subscribe("izzie~never-existed"),
Err(BusError::InstanceGone { .. })
));
}
#[test]
fn publish_writes_durable_jsonl_record() {
let (dir, bus) = bus();
assert!(bus.log_path().starts_with(dir.path().join("bus")));
let target = bus.registry().register("cto-assistant", None).unwrap();
let _rx = bus.subscribe(&target.instance_id).unwrap();
let sent = bus
.publish(
registered_sender(&bus, "izzie"),
&PeerTarget::Definition("cto-assistant".into()),
chat("durable"),
None,
)
.unwrap();
let log = read_log(&bus);
assert_eq!(log.len(), 1);
assert_eq!(log[0], sent);
assert_eq!(log[0].delivery_state, DeliveryState::Delivered);
}
#[test]
fn failed_publish_logs_dropped_envelope() {
let (_dir, bus) = bus();
let err = bus
.publish(
registered_sender(&bus, "izzie"),
&PeerTarget::Definition("nobody-home".into()),
chat("into the void"),
None,
)
.unwrap_err();
assert!(matches!(err, BusError::NoLiveInstance { .. }));
let log = read_log(&bus);
assert_eq!(log.len(), 1);
assert_eq!(log[0].delivery_state, DeliveryState::Dropped);
assert_eq!(log[0].to.definition_id.as_deref(), Some("nobody-home"));
}
#[test]
fn dropped_bypass_has_no_definition() {
let (_dir, bus) = bus();
let dead = bus.registry().register("izzie", None).unwrap();
bus.registry().deregister(&dead.instance_id);
let _ = bus.publish(
registered_sender(&bus, "cto-assistant"),
&PeerTarget::Instance(dead.instance_id.clone()),
chat("gone"),
None,
);
let log = read_log(&bus);
assert_eq!(log.len(), 1);
assert_eq!(log[0].delivery_state, DeliveryState::Dropped);
assert_eq!(
log[0].to.instance_id.as_deref(),
Some(dead.instance_id.as_str())
);
assert!(log[0].to.definition_id.is_none());
}
#[test]
fn no_subscriber_is_logged_as_dropped_not_delivered() {
let (_dir, bus) = bus();
let target = bus.registry().register("cto-assistant", None).unwrap();
let _ = bus.publish(
registered_sender(&bus, "izzie"),
&PeerTarget::Instance(target.instance_id.clone()),
chat("unheard"),
None,
);
let log = read_log(&bus);
assert_eq!(log.len(), 1);
assert_eq!(log[0].delivery_state, DeliveryState::Dropped);
}
#[test]
fn peer_edge_derives_assistant_assistant() {
assert_eq!(
CallerKind::AssistantInstance.peer_edge().unwrap(),
BusEdge::AssistantAssistant
);
}
#[test]
fn peer_edge_rejects_user_and_channel() {
for kind in [CallerKind::User, CallerKind::Channel] {
assert_eq!(
kind.peer_edge().unwrap_err(),
BusError::CallerKindNotPermitted { kind }
);
}
}
#[test]
fn published_envelope_edge_is_derived() {
let (_dir, bus) = bus();
let target = bus.registry().register("cto-assistant", None).unwrap();
let _rx = bus.subscribe(&target.instance_id).unwrap();
let sent = bus
.publish(
registered_sender(&bus, "izzie"),
&PeerTarget::Definition("cto-assistant".into()),
chat("hi"),
None,
)
.unwrap();
assert_eq!(sent.edge, BusEdge::AssistantAssistant);
assert_eq!(read_log(&bus)[0].edge, BusEdge::AssistantAssistant);
}
#[test]
fn publish_rejects_forged_user_kind() {
let (_dir, bus) = bus();
let target = bus.registry().register("cto-assistant", None).unwrap();
let mut rx = bus.subscribe(&target.instance_id).unwrap();
let forged = CallerIdentity {
kind: CallerKind::User,
instance_id: None,
definition_id: None,
channel_origin: None,
};
assert_eq!(
bus.publish(
forged,
&PeerTarget::Definition("cto-assistant".into()),
chat("do this"),
None
)
.unwrap_err(),
BusError::CallerKindNotPermitted {
kind: CallerKind::User
}
);
assert!(rx.try_recv().is_err());
}
#[test]
fn publish_rejects_unregistered_sender() {
let (_dir, bus) = bus();
let target = bus.registry().register("cto-assistant", None).unwrap();
let mut rx = bus.subscribe(&target.instance_id).unwrap();
assert_eq!(
bus.publish(
peer_caller("izzie~deadbeef", "izzie"),
&PeerTarget::Definition("cto-assistant".into()),
chat("hi"),
None
)
.unwrap_err(),
BusError::UnregisteredSender {
instance_id: "izzie~deadbeef".into()
}
);
assert!(rx.try_recv().is_err());
}
#[test]
fn publish_rejects_deregistered_sender() {
let (_dir, bus) = bus();
let target = bus.registry().register("cto-assistant", None).unwrap();
let _rx = bus.subscribe(&target.instance_id).unwrap();
let sender = registered_sender(&bus, "izzie");
let sender_id = sender.instance_id.clone().unwrap();
assert!(
bus.publish(
sender.clone(),
&PeerTarget::Definition("cto-assistant".into()),
chat("first"),
None
)
.is_ok()
);
bus.registry().deregister(&sender_id);
assert_eq!(
bus.publish(
sender,
&PeerTarget::Definition("cto-assistant".into()),
chat("second"),
None
)
.unwrap_err(),
BusError::UnregisteredSender {
instance_id: sender_id
}
);
}
#[test]
fn publish_restamps_sender_definition_from_registry() {
let (_dir, bus) = bus();
let target = bus.registry().register("cto-assistant", None).unwrap();
let _rx = bus.subscribe(&target.instance_id).unwrap();
let real = bus.registry().register("izzie", None).unwrap();
let lying = CallerIdentity {
kind: CallerKind::AssistantInstance,
instance_id: Some(real.instance_id.clone()),
definition_id: Some("cto-assistant".into()), channel_origin: None,
};
let sent = bus
.publish(
lying,
&PeerTarget::Definition("cto-assistant".into()),
chat("hi"),
None,
)
.unwrap();
assert_eq!(sent.from.definition_id.as_deref(), Some("izzie"));
assert_eq!(
read_log(&bus)[0].from.definition_id.as_deref(),
Some("izzie")
);
}
#[test]
fn rejected_publish_writes_no_durable_record() {
let (_dir, bus) = bus();
let target = bus.registry().register("cto-assistant", None).unwrap();
let _rx = bus.subscribe(&target.instance_id).unwrap();
let to = PeerTarget::Definition("cto-assistant".into());
let _ = bus.publish(
CallerIdentity {
kind: CallerKind::User,
instance_id: None,
definition_id: None,
channel_origin: None,
},
&to,
chat("a"),
None,
);
let _ = bus.publish(
CallerIdentity {
kind: CallerKind::AssistantInstance,
instance_id: None,
definition_id: Some("izzie".into()),
channel_origin: None,
},
&to,
chat("b"),
None,
);
let _ = bus.publish(peer_caller("izzie~deadbeef", "izzie"), &to, chat("c"), None);
assert!(
read_log(&bus).is_empty(),
"pre-verification rejections must leave the durable log untouched"
);
let _ = bus.publish(
registered_sender(&bus, "izzie"),
&PeerTarget::Definition("nobody-home".into()),
chat("d"),
None,
);
let log = read_log(&bus);
assert_eq!(log.len(), 1);
assert_eq!(log[0].delivery_state, DeliveryState::Dropped);
}
#[test]
fn unregistered_sender_is_403() {
assert_eq!(
BusError::UnregisteredSender {
instance_id: "izzie~deadbeef".into()
}
.status(),
StatusCode::FORBIDDEN
);
assert_ne!(
BusError::UnregisteredSender {
instance_id: "x".into()
}
.status(),
BusError::InstanceGone {
instance_id: "x".into()
}
.status()
);
}
#[test]
fn target_prefers_instance_when_both_supplied() {
let t = routes::PublishTarget {
instance_id: Some("izzie~aaaa1111".into()),
definition_id: Some("izzie".into()),
};
assert_eq!(
t.to_peer_target().unwrap(),
PeerTarget::Instance("izzie~aaaa1111".into())
);
}
#[test]
fn target_requires_one_id() {
let t = routes::PublishTarget {
instance_id: None,
definition_id: None,
};
assert!(matches!(
t.to_peer_target(),
Err(BusError::InvalidTarget(_))
));
}
#[test]
fn bus_error_status_codes_map() {
assert_eq!(
BusError::NoSubscriber {
instance_id: "x".into()
}
.status(),
StatusCode::CONFLICT
);
assert_eq!(
BusError::InvalidTarget("x".into()).status(),
StatusCode::BAD_REQUEST
);
assert_eq!(
BusError::InvalidCaller("x".into()).status(),
StatusCode::BAD_REQUEST
);
assert_eq!(
BusError::InvalidDefinitionId {
definition_id: "x".into(),
reason: "y".into()
}
.status(),
StatusCode::BAD_REQUEST
);
}
#[test]
fn instance_gone_is_410() {
assert_eq!(
BusError::InstanceGone {
instance_id: "izzie~dead".into()
}
.status(),
StatusCode::GONE
);
}
fn test_state() -> (tempfile::TempDir, Arc<DaemonState>) {
let dir = tempfile::tempdir().unwrap();
let state = Arc::new(DaemonState::with_root(dir.path().to_path_buf()));
(dir, state)
}
async fn call(state: &Arc<DaemonState>, req: Request<Body>) -> (StatusCode, serde_json::Value) {
let resp = router(Arc::clone(state)).oneshot(req).await.unwrap();
let status = resp.status();
let bytes = axum::body::to_bytes(resp.into_body(), usize::MAX)
.await
.unwrap();
let body = serde_json::from_slice(&bytes).unwrap_or(serde_json::Value::Null);
(status, body)
}
fn post(path: &str, body: serde_json::Value) -> Request<Body> {
Request::builder()
.method("POST")
.uri(path)
.header("content-type", "application/json")
.body(Body::from(body.to_string()))
.unwrap()
}
#[tokio::test]
async fn route_register_returns_instance_id() {
let (_dir, state) = test_state();
let (status, body) = call(
&state,
post(
"/api/v1/bus/instances",
serde_json::json!({ "definition_id": "izzie" }),
),
)
.await;
assert_eq!(status, StatusCode::CREATED);
assert_eq!(body["definition_id"], "izzie");
assert!(body["instance_id"].as_str().unwrap().starts_with("izzie~"));
}
#[tokio::test]
async fn route_register_rejects_bad_definition() {
let (_dir, state) = test_state();
let (status, _) = call(
&state,
post(
"/api/v1/bus/instances",
serde_json::json!({ "definition_id": "bad#id" }),
),
)
.await;
assert_eq!(status, StatusCode::BAD_REQUEST);
}
#[tokio::test]
async fn route_list_instances() {
let (_dir, state) = test_state();
call(
&state,
post(
"/api/v1/bus/instances",
serde_json::json!({ "definition_id": "izzie" }),
),
)
.await;
let (status, body) = call(
&state,
Request::builder()
.uri("/api/v1/bus/instances")
.body(Body::empty())
.unwrap(),
)
.await;
assert_eq!(status, StatusCode::OK);
assert_eq!(body["instances"].as_array().unwrap().len(), 1);
}
#[tokio::test]
async fn route_deregister_removes_instance() {
let (_dir, state) = test_state();
let (_, created) = call(
&state,
post(
"/api/v1/bus/instances",
serde_json::json!({ "definition_id": "izzie" }),
),
)
.await;
let id = created["instance_id"].as_str().unwrap();
let (status, _) = call(
&state,
Request::builder()
.method("DELETE")
.uri(format!("/api/v1/bus/instances/{id}"))
.body(Body::empty())
.unwrap(),
)
.await;
assert_eq!(status, StatusCode::NO_CONTENT);
assert!(state.bus().registry().is_empty());
}
#[tokio::test]
async fn route_publish_delivers_and_returns_envelope() {
let (_dir, state) = test_state();
let target = state
.bus()
.registry()
.register("cto-assistant", None)
.unwrap();
let mut rx = state.bus().subscribe(&target.instance_id).unwrap();
let sender = state.bus().registry().register("izzie", None).unwrap();
let (status, body) = call(
&state,
post(
"/api/v1/bus/publish",
serde_json::json!({
"from": { "kind": "assistant_instance",
"instance_id": sender.instance_id,
"definition_id": "izzie" },
"to": { "definition_id": "cto-assistant" },
"payload": { "type": "peer_request", "text": "review please" }
}),
),
)
.await;
assert_eq!(status, StatusCode::ACCEPTED);
assert_eq!(body["delivery_state"], "delivered");
assert!(body["message_id"].as_str().is_some());
assert!(rx.try_recv().is_ok());
}
#[tokio::test]
async fn route_publish_to_dead_instance_is_410() {
let (_dir, state) = test_state();
let dead = state.bus().registry().register("izzie", None).unwrap();
state.bus().registry().deregister(&dead.instance_id);
let sibling = state.bus().registry().register("izzie", None).unwrap();
let mut sibling_rx = state.bus().subscribe(&sibling.instance_id).unwrap();
let sender = state
.bus()
.registry()
.register("cto-assistant", None)
.unwrap();
let (status, body) = call(
&state,
post(
"/api/v1/bus/publish",
serde_json::json!({
"from": { "kind": "assistant_instance",
"instance_id": sender.instance_id,
"definition_id": "cto-assistant" },
"to": { "instance_id": dead.instance_id, "definition_id": "izzie" },
"payload": { "type": "chat_text", "text": "continue" }
}),
),
)
.await;
assert_eq!(status, StatusCode::GONE);
assert!(body["error"].as_str().unwrap().contains("no longer live"));
assert!(
sibling_rx.try_recv().is_err(),
"supplying definition_id alongside a dead instance_id must NOT fall back"
);
}
#[tokio::test]
async fn route_publish_to_unknown_definition_is_404() {
let (_dir, state) = test_state();
let sender = state.bus().registry().register("izzie", None).unwrap();
let (status, _) = call(
&state,
post(
"/api/v1/bus/publish",
serde_json::json!({
"from": { "kind": "assistant_instance",
"instance_id": sender.instance_id,
"definition_id": "izzie" },
"to": { "definition_id": "nobody-home" },
"payload": { "type": "chat_text", "text": "hi" }
}),
),
)
.await;
assert_eq!(status, StatusCode::NOT_FOUND);
}
#[tokio::test]
async fn route_publish_forged_user_kind_is_400() {
let (_dir, state) = test_state();
let target = state
.bus()
.registry()
.register("cto-assistant", None)
.unwrap();
let mut rx = state.bus().subscribe(&target.instance_id).unwrap();
let (status, body) = call(
&state,
post(
"/api/v1/bus/publish",
serde_json::json!({
"from": { "kind": "user" },
"to": { "definition_id": "cto-assistant" },
"payload": { "type": "chat_text", "text": "delete everything" }
}),
),
)
.await;
assert_eq!(status, StatusCode::BAD_REQUEST);
assert!(body["error"].as_str().unwrap().contains("may not publish"));
assert!(
rx.try_recv().is_err(),
"a forged user-kind message must never reach the recipient"
);
}
#[tokio::test]
async fn route_publish_unregistered_sender_is_403() {
let (_dir, state) = test_state();
let target = state
.bus()
.registry()
.register("cto-assistant", None)
.unwrap();
let mut rx = state.bus().subscribe(&target.instance_id).unwrap();
let (status, body) = call(
&state,
post(
"/api/v1/bus/publish",
serde_json::json!({
"from": { "kind": "assistant_instance",
"instance_id": "izzie~deadbeef",
"definition_id": "izzie" },
"to": { "definition_id": "cto-assistant" },
"payload": { "type": "chat_text", "text": "hi" }
}),
),
)
.await;
assert_eq!(status, StatusCode::FORBIDDEN);
assert!(
body["error"]
.as_str()
.unwrap()
.contains("not a live registration")
);
assert!(rx.try_recv().is_err());
}
#[tokio::test]
async fn route_publish_absent_sender_ids_is_400() {
let (_dir, state) = test_state();
let (status, _) = call(
&state,
post(
"/api/v1/bus/publish",
serde_json::json!({
"from": { "kind": "assistant_instance" },
"to": { "definition_id": "cto-assistant" },
"payload": { "type": "chat_text", "text": "hi" }
}),
),
)
.await;
assert_eq!(status, StatusCode::BAD_REQUEST);
}
#[tokio::test]
async fn route_subscribe_to_dead_instance_is_410() {
let (_dir, state) = test_state();
let (status, _) = call(
&state,
Request::builder()
.uri("/api/v1/bus/subscribe/izzie~never")
.body(Body::empty())
.unwrap(),
)
.await;
assert_eq!(status, StatusCode::GONE);
}