use ahp::hosts::{HostId, HostSubscriptionEvent};
use ahp::{HostedResourceKey, MultiHostStateMirror, SubscriptionEvent};
use ahp_types::actions::{
ActionEnvelope, RootActiveSessionsChangedAction, RootAgentsChangedAction,
SessionTitleChangedAction, StateAction,
};
use ahp_types::common::ROOT_RESOURCE_URI;
use ahp_types::state::{
AgentInfo, RootState, SessionLifecycle, SessionState, SessionStatus, SessionSummary, Snapshot,
SnapshotState,
};
fn agent(provider: &str) -> AgentInfo {
AgentInfo {
provider: provider.into(),
display_name: provider.into(),
description: String::new(),
models: vec![],
protected_resources: None,
customizations: None,
capabilities: None,
}
}
fn root_snapshot(agents: Vec<AgentInfo>) -> Snapshot {
Snapshot {
resource: ROOT_RESOURCE_URI.to_string(),
state: SnapshotState::Root(Box::new(RootState {
agents,
active_sessions: None,
terminals: None,
config: None,
meta: None,
})),
from_seq: 0,
}
}
fn session_state(title: &str, _resource: &str) -> SessionState {
SessionState {
provider: "copilot".into(),
title: title.into(),
status: SessionStatus::Idle.bits(),
activity: None,
project: None,
working_directory: None,
annotations: None,
lifecycle: SessionLifecycle::Ready,
creation_error: None,
server_tools: None,
active_clients: vec![],
chats: vec![],
default_chat: None,
config: None,
customizations: None,
changesets: None,
input_needed: None,
meta: None,
}
}
fn session_snapshot(title: &str, resource: &str) -> Snapshot {
Snapshot {
resource: resource.into(),
state: SnapshotState::Session(Box::new(session_state(title, resource))),
from_seq: 0,
}
}
fn root_agents_changed_envelope(agents: Vec<AgentInfo>, server_seq: u64) -> ActionEnvelope {
ActionEnvelope {
channel: ROOT_RESOURCE_URI.to_string(),
action: StateAction::RootAgentsChanged(RootAgentsChangedAction { agents }),
server_seq,
origin: None,
rejection_reason: None,
}
}
#[test]
fn root_states_are_isolated_per_host() {
let mut mirror = MultiHostStateMirror::new();
mirror.apply_snapshot(&HostId::new("alpha"), &root_snapshot(vec![agent("a")]));
mirror.apply_snapshot(&HostId::new("beta"), &root_snapshot(vec![agent("b")]));
let roots = mirror.root_states();
assert_eq!(
roots
.get(&HostId::new("alpha"))
.and_then(|r| r.agents.first())
.map(|a| a.provider.as_str()),
Some("a")
);
assert_eq!(
roots
.get(&HostId::new("beta"))
.and_then(|r| r.agents.first())
.map(|a| a.provider.as_str()),
Some("b")
);
}
#[test]
fn session_uri_collision_across_hosts_does_not_clobber() {
let mut mirror = MultiHostStateMirror::new();
mirror.apply_snapshot(
&HostId::new("alpha"),
&session_snapshot("A title", "ahp-session:/s1"),
);
mirror.apply_snapshot(
&HostId::new("beta"),
&session_snapshot("B title", "ahp-session:/s1"),
);
let sessions = mirror.sessions();
assert_eq!(
sessions
.get(&HostedResourceKey::new(
HostId::new("alpha"),
"ahp-session:/s1"
))
.map(|s| s.title.as_str()),
Some("A title")
);
assert_eq!(
sessions
.get(&HostedResourceKey::new(
HostId::new("beta"),
"ahp-session:/s1"
))
.map(|s| s.title.as_str()),
Some("B title")
);
}
#[test]
fn apply_root_action_updates_only_the_target_host() {
let mut mirror = MultiHostStateMirror::new();
mirror.apply_snapshot(&HostId::new("alpha"), &root_snapshot(vec![]));
mirror.apply_snapshot(&HostId::new("beta"), &root_snapshot(vec![]));
let envelope = root_agents_changed_envelope(vec![agent("new")], 5);
mirror.apply_envelope(&HostId::new("alpha"), &envelope);
let roots = mirror.root_states();
assert_eq!(
roots
.get(&HostId::new("alpha"))
.and_then(|r| r.agents.first())
.map(|a| a.provider.as_str()),
Some("new"),
"action targeting alpha should mutate alpha's root state"
);
assert!(
roots
.get(&HostId::new("beta"))
.map(|r| r.agents.is_empty())
.unwrap_or(false),
"action targeting alpha must not touch beta's root state"
);
}
#[test]
fn apply_session_action_updates_only_the_target_session() {
let mut mirror = MultiHostStateMirror::new();
mirror.apply_snapshot(
&HostId::new("alpha"),
&session_snapshot("Old", "ahp-session:/s1"),
);
mirror.apply_snapshot(
&HostId::new("beta"),
&session_snapshot("Old", "ahp-session:/s1"),
);
let envelope = ActionEnvelope {
channel: "ahp-session:/s1".into(),
action: StateAction::SessionTitleChanged(SessionTitleChangedAction {
title: "New on alpha".into(),
}),
server_seq: 7,
origin: None,
rejection_reason: None,
};
mirror.apply_envelope(&HostId::new("alpha"), &envelope);
let sessions = mirror.sessions();
assert_eq!(
sessions
.get(&HostedResourceKey::new(
HostId::new("alpha"),
"ahp-session:/s1"
))
.map(|s| s.title.as_str()),
Some("New on alpha")
);
assert_eq!(
sessions
.get(&HostedResourceKey::new(
HostId::new("beta"),
"ahp-session:/s1"
))
.map(|s| s.title.as_str()),
Some("Old"),
"session-scoped action on alpha must not touch beta's identically-named session"
);
}
#[test]
fn apply_host_subscription_event_forwards_to_per_host_apply() {
let mut mirror = MultiHostStateMirror::new();
mirror.apply_snapshot(&HostId::new("alpha"), &root_snapshot(vec![]));
let envelope = root_agents_changed_envelope(vec![agent("via-event")], 9);
let event = HostSubscriptionEvent {
host_id: HostId::new("alpha"),
channel: ROOT_RESOURCE_URI.into(),
event: SubscriptionEvent::Action(envelope),
};
mirror.apply_event(&event);
let roots = mirror.root_states();
assert_eq!(
roots
.get(&HostId::new("alpha"))
.and_then(|r| r.agents.first())
.map(|a| a.provider.as_str()),
Some("via-event")
);
}
#[test]
fn apply_envelope_for_unknown_channel_is_a_no_op_for_sessions() {
let mut mirror = MultiHostStateMirror::new();
let envelope = ActionEnvelope {
channel: "ahp-session:/unknown".into(),
action: StateAction::SessionTitleChanged(SessionTitleChangedAction {
title: "lost".into(),
}),
server_seq: 1,
origin: None,
rejection_reason: None,
};
mirror.apply_envelope(&HostId::new("alpha"), &envelope);
assert!(mirror.sessions().is_empty());
}
#[test]
fn apply_envelope_with_no_root_seeds_root_via_action() {
let mut mirror = MultiHostStateMirror::new();
let envelope = ActionEnvelope {
channel: ROOT_RESOURCE_URI.into(),
action: StateAction::RootActiveSessionsChanged(RootActiveSessionsChangedAction {
active_sessions: 3,
}),
server_seq: 1,
origin: None,
rejection_reason: None,
};
mirror.apply_envelope(&HostId::new("alpha"), &envelope);
assert_eq!(
mirror
.root_states()
.get(&HostId::new("alpha"))
.and_then(|r| r.active_sessions),
Some(3)
);
}
#[test]
fn reset_host_drops_only_that_host() {
let mut mirror = MultiHostStateMirror::new();
mirror.apply_snapshot(&HostId::new("alpha"), &root_snapshot(vec![]));
mirror.apply_snapshot(&HostId::new("beta"), &root_snapshot(vec![]));
mirror.apply_snapshot(
&HostId::new("alpha"),
&session_snapshot("a", "ahp-session:/x"),
);
mirror.apply_snapshot(
&HostId::new("beta"),
&session_snapshot("b", "ahp-session:/x"),
);
mirror.reset_host(&HostId::new("alpha"));
assert!(mirror.root_states().get(&HostId::new("alpha")).is_none());
assert!(mirror.root_states().get(&HostId::new("beta")).is_some());
assert!(mirror
.sessions()
.get(&HostedResourceKey::new(
HostId::new("alpha"),
"ahp-session:/x"
))
.is_none());
assert!(mirror
.sessions()
.get(&HostedResourceKey::new(
HostId::new("beta"),
"ahp-session:/x"
))
.is_some());
}
#[test]
fn reset_drops_everything() {
let mut mirror = MultiHostStateMirror::new();
mirror.apply_snapshot(&HostId::new("alpha"), &root_snapshot(vec![]));
mirror.apply_snapshot(
&HostId::new("alpha"),
&session_snapshot("a", "ahp-session:/x"),
);
mirror.reset();
assert!(mirror.root_states().is_empty());
assert!(mirror.sessions().is_empty());
}
#[test]
fn non_action_event_is_ignored() {
use ahp_types::notifications::SessionAddedParams;
let mut mirror = MultiHostStateMirror::new();
mirror.apply_snapshot(&HostId::new("alpha"), &root_snapshot(vec![]));
let event = HostSubscriptionEvent {
host_id: HostId::new("alpha"),
channel: ROOT_RESOURCE_URI.into(),
event: SubscriptionEvent::SessionAdded(SessionAddedParams {
channel: ROOT_RESOURCE_URI.into(),
summary: SessionSummary {
resource: "ahp-session:/new".into(),
provider: "copilot".into(),
title: "new".into(),
status: SessionStatus::Idle.bits(),
activity: None,
created_at: "1970-01-01T00:00:00.000Z".into(),
modified_at: "1970-01-01T00:00:00.000Z".into(),
project: None,
working_directory: None,
changes: None,
annotations: None,
meta: None,
},
}),
};
mirror.apply_event(&event);
let roots = mirror.root_states();
assert!(roots
.get(&HostId::new("alpha"))
.map(|r| r.agents.is_empty())
.unwrap_or(false));
assert!(mirror.sessions().is_empty());
}