use super::*;
fn test_registry() -> Arc<SessionRegistry> {
let registry = Arc::new(SessionRegistry::new());
let session = Arc::new(Session::new(SessionId::new("test")));
registry.insert(&session);
registry
}
fn test_tokens() -> Arc<TokenRegistry> {
Arc::new(TokenRegistry::new())
}
#[tokio::test]
async fn test_subscribe_no_session() {
let registry = Arc::new(SessionRegistry::new());
let service =
NotificationServiceImpl::new(registry, SessionId::new("nonexistent"), test_tokens());
let request = Request::new(SubscribeRequest {
event_types: vec![],
});
let response = service.subscribe(request).await;
assert!(response.is_err());
let err = response.err().unwrap();
assert_eq!(err.code(), tonic::Code::NotFound);
}
#[tokio::test]
async fn test_subscribe_returns_stream() {
let registry = test_registry();
let service = NotificationServiceImpl::new(registry, SessionId::new("test"), test_tokens());
let request = Request::new(SubscribeRequest {
event_types: vec![],
});
let response = service.subscribe(request).await;
assert!(response.is_ok());
}
#[tokio::test]
async fn test_subscribe_with_filter() {
let registry = test_registry();
let service = NotificationServiceImpl::new(registry, SessionId::new("test"), test_tokens());
let request = Request::new(SubscribeRequest {
event_types: vec!["mode_changed".to_string()],
});
let response = service.subscribe(request).await;
assert!(response.is_ok());
}
#[tokio::test]
async fn test_stream_drop_revokes_token() {
let tokens = test_tokens();
let registry = test_registry();
let service = NotificationServiceImpl::new(
Arc::clone(®istry),
SessionId::new("test"),
Arc::clone(&tokens),
);
let client_id = registry.next_client_id();
let token = tokens.register(client_id);
let session = registry.get(&SessionId::new("test")).unwrap();
session.add_client(client_id);
let mut request = Request::new(SubscribeRequest {
event_types: vec![],
});
request.extensions_mut().insert(client_id);
let response = service.subscribe(request).await.unwrap();
assert!(tokens.resolve(&token).is_some());
drop(response);
assert!(tokens.resolve(&token).is_none());
}
#[tokio::test]
async fn test_stream_drop_removes_client() {
let tokens = test_tokens();
let registry = test_registry();
let service = NotificationServiceImpl::new(
Arc::clone(®istry),
SessionId::new("test"),
Arc::clone(&tokens),
);
let client_id = registry.next_client_id();
tokens.register(client_id);
let session = registry.get(&SessionId::new("test")).unwrap();
session.add_client(client_id);
let mut request = Request::new(SubscribeRequest {
event_types: vec![],
});
request.extensions_mut().insert(client_id);
let response = service.subscribe(request).await.unwrap();
assert!(session.has_client(client_id));
drop(response);
assert!(!session.has_client(client_id));
}
#[tokio::test]
async fn test_stream_drop_removes_from_presence() {
use crate::session::ClientPresence;
let tokens = test_tokens();
let registry = test_registry();
let service = NotificationServiceImpl::new(
Arc::clone(®istry),
SessionId::new("test"),
Arc::clone(&tokens),
);
let client_id = registry.next_client_id();
tokens.register(client_id);
let session = registry.get(&SessionId::new("test")).unwrap();
session.add_client(client_id);
session
.presence()
.join(ClientPresence::new(client_id, "test", "cleanup-test"));
let mut request = Request::new(SubscribeRequest {
event_types: vec![],
});
request.extensions_mut().insert(client_id);
let response = service.subscribe(request).await.unwrap();
assert!(session.presence().contains(client_id));
drop(response);
assert!(!session.presence().contains(client_id));
}
#[tokio::test]
async fn test_stream_drop_without_token_no_cleanup() {
let tokens = test_tokens();
let registry = test_registry();
let service = NotificationServiceImpl::new(
Arc::clone(®istry),
SessionId::new("test"),
Arc::clone(&tokens),
);
let session = registry.get(&SessionId::new("test")).unwrap();
let client_id = ClientId::new(99);
session.add_client(client_id);
let request = Request::new(SubscribeRequest {
event_types: vec![],
});
let response = service.subscribe(request).await.unwrap();
drop(response);
assert!(session.has_client(client_id));
}
#[tokio::test]
async fn test_cleanup_idempotent_with_explicit_leave() {
use crate::session::ClientPresence;
let tokens = test_tokens();
let registry = test_registry();
let service = NotificationServiceImpl::new(
Arc::clone(®istry),
SessionId::new("test"),
Arc::clone(&tokens),
);
let client_id = registry.next_client_id();
tokens.register(client_id);
let session = registry.get(&SessionId::new("test")).unwrap();
session.add_client(client_id);
session
.presence()
.join(ClientPresence::new(client_id, "test", "idem-test"));
let mut request = Request::new(SubscribeRequest {
event_types: vec![],
});
request.extensions_mut().insert(client_id);
let response = service.subscribe(request).await.unwrap();
tokens.revoke_by_client(client_id);
session.presence().leave(client_id);
session.remove_client(client_id);
drop(response);
assert!(!session.has_client(client_id));
assert!(!session.presence().contains(client_id));
assert!(tokens.is_empty());
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[test]
fn test_build_presence_left_notification() {
let client_id = ClientId::new(42);
let notification = build_presence_left_notification(client_id, "test-user");
assert_eq!(notification.event_type, "presence_left");
assert!(notification.timestamp_ms > 0);
if let Some(reovim_protocol::v2::notification::Payload::PresenceLeft(payload)) =
notification.payload
{
assert_eq!(payload.client_id, 42);
assert_eq!(payload.display_name, "test-user");
} else {
panic!("Expected PresenceLeft payload");
}
}