use super::*;
fn test_registry() -> Arc<SessionRegistry> {
let (registry, _) = test_registry_with_session();
registry
}
fn test_registry_with_session() -> (Arc<SessionRegistry>, Arc<Session>) {
let registry = Arc::new(SessionRegistry::new());
let session = Arc::new(Session::new(SessionId::new("test")));
registry.insert(&session);
(registry, session)
}
fn test_registry_with_buffer_manager() -> (Arc<SessionRegistry>, Arc<Session>) {
use {
parking_lot::RwLock as ParkingLotRwLock,
reovim_driver_buffer::TestBufferManager,
reovim_kernel::api::v1::{
EventBus, KernelContext, MarkBank, MotionEngine, OptionRegistry, ServiceRegistry,
TextObjectEngine,
},
};
let kernel = KernelContext::new(
Arc::new(EventBus::new()),
Arc::new(TestBufferManager::new()),
Arc::new(MotionEngine),
Arc::new(TextObjectEngine),
Arc::new(ParkingLotRwLock::new(MarkBank::new())),
Arc::new(OptionRegistry::new()),
Arc::new(ServiceRegistry::new()),
);
let state = crate::session::SessionState::with_kernel(kernel);
let session = Arc::new(Session::from_state(SessionId::new("test"), state));
let registry = Arc::new(SessionRegistry::new());
registry.insert(&session);
(registry, session)
}
fn authed_request<T>(body: T, client_id: ClientId) -> Request<T> {
let mut request = Request::new(body);
request.extensions_mut().insert(client_id);
request
}
#[tokio::test]
async fn test_get_mode_returns_current_mode() {
let (registry, session) = test_registry_with_session();
let service = StateServiceImpl::new(Arc::clone(®istry), SessionId::new("test"));
session.add_client(ClientId::new(1));
let request = authed_request(GetModeRequest { client_id: 1 }, ClientId::new(1));
let response = service.get_mode(request).await;
assert!(response.is_ok());
let resp = response.unwrap().into_inner();
assert_eq!(resp.name, "normal");
assert_eq!(resp.display, "NORMAL");
assert!(!resp.is_insert);
}
#[tokio::test]
async fn test_get_mode_rejects_unauthenticated() {
let registry = test_registry();
let service = StateServiceImpl::new(registry, SessionId::new("test"));
let request = Request::new(GetModeRequest { client_id: 0 });
let response = service.get_mode(request).await;
assert!(response.is_err());
assert_eq!(response.unwrap_err().code(), tonic::Code::Unauthenticated);
}
#[tokio::test]
async fn test_get_mode_unknown_client_returns_not_found() {
let registry = test_registry();
let service = StateServiceImpl::new(registry, SessionId::new("test"));
let request = authed_request(GetModeRequest { client_id: 999 }, ClientId::new(999));
let response = service.get_mode(request).await;
assert!(response.is_err());
assert_eq!(response.unwrap_err().code(), tonic::Code::NotFound);
}
#[tokio::test]
async fn test_get_cursor_rejects_unauthenticated() {
let registry = test_registry();
let service = StateServiceImpl::new(registry, SessionId::new("test"));
let request = Request::new(GetCursorRequest {
window_id: None,
client_id: 0,
});
let response = service.get_cursor(request).await;
assert!(response.is_err());
assert_eq!(response.unwrap_err().code(), tonic::Code::Unauthenticated);
}
#[tokio::test]
async fn test_get_cursor_no_active_window() {
let (registry, session) = test_registry_with_session();
let service = StateServiceImpl::new(Arc::clone(®istry), SessionId::new("test"));
session.add_client(ClientId::new(1));
let request = authed_request(
GetCursorRequest {
window_id: None,
client_id: 1,
},
ClientId::new(1),
);
let response = service.get_cursor(request).await;
assert!(response.is_err());
assert_eq!(response.unwrap_err().code(), tonic::Code::NotFound);
}
#[tokio::test]
async fn test_get_cursor_with_buffer() {
let (registry, session) = test_registry_with_buffer_manager();
session
.with_state_mut(|state| {
state.create_buffer("hello world");
})
.await;
session.add_client(ClientId::new(1));
let service = StateServiceImpl::new(registry, SessionId::new("test"));
let request = authed_request(
GetCursorRequest {
window_id: None,
client_id: 1,
},
ClientId::new(1),
);
let response = service.get_cursor(request).await;
assert!(response.is_ok());
let resp = response.unwrap().into_inner();
assert!(resp.position.is_some());
let pos = resp.position.unwrap();
assert_eq!(pos.line, 0);
assert_eq!(pos.column, 0);
}
#[tokio::test]
async fn test_get_options_returns_ok() {
let registry = test_registry();
let service = StateServiceImpl::new(registry, SessionId::new("test"));
let request = Request::new(GetOptionsRequest { names: vec![] });
let response = service.get_options(request).await;
assert!(response.is_ok());
}
#[tokio::test]
async fn test_get_layout_rejects_unauthenticated() {
let registry = test_registry();
let service = StateServiceImpl::new(registry, SessionId::new("test"));
let request = Request::new(GetLayoutRequest { client_id: 0 });
let response = service.get_layout(request).await;
assert!(response.is_err());
assert_eq!(response.unwrap_err().code(), tonic::Code::Unauthenticated);
}
#[tokio::test]
async fn test_get_layout_empty() {
let (registry, session) = test_registry_with_session();
let service = StateServiceImpl::new(Arc::clone(®istry), SessionId::new("test"));
session.add_client(ClientId::new(1));
let request = authed_request(GetLayoutRequest { client_id: 1 }, ClientId::new(1));
let response = service.get_layout(request).await;
assert!(response.is_ok());
let resp = response.unwrap().into_inner();
assert!(resp.root.is_none());
assert_eq!(resp.focused_window_id, None);
}
#[tokio::test]
async fn test_get_layout_single_window() {
let (registry, session) = test_registry_with_buffer_manager();
session
.with_state_mut(|state| {
let _buffer_id = state.create_buffer("hello world");
})
.await;
session.add_client(ClientId::new(1));
let service = StateServiceImpl::new(registry, SessionId::new("test"));
let request = authed_request(GetLayoutRequest { client_id: 1 }, ClientId::new(1));
let response = service.get_layout(request).await;
assert!(response.is_ok());
let resp = response.unwrap().into_inner();
assert!(resp.root.is_some());
let root = resp.root.unwrap();
match root.node {
Some(reovim_protocol::v2::window_node::Node::Leaf(leaf)) => {
assert_eq!(leaf.rect.as_ref().unwrap().width, 80);
assert_eq!(leaf.rect.as_ref().unwrap().height, 24);
}
_ => panic!("Expected a leaf node"),
}
}
#[tokio::test]
async fn test_get_visible_lines_rejects_unauthenticated() {
let registry = test_registry();
let service = StateServiceImpl::new(registry, SessionId::new("test"));
let request = Request::new(GetVisibleLinesRequest {
window_id: None,
client_id: 0,
});
let response = service.get_visible_lines(request).await;
assert!(response.is_err());
assert_eq!(response.unwrap_err().code(), tonic::Code::Unauthenticated);
}
#[tokio::test]
async fn test_get_visible_lines_no_window() {
let (registry, session) = test_registry_with_session();
let service = StateServiceImpl::new(Arc::clone(®istry), SessionId::new("test"));
session.add_client(ClientId::new(1));
let request = authed_request(
GetVisibleLinesRequest {
window_id: None,
client_id: 1,
},
ClientId::new(1),
);
let response = service.get_visible_lines(request).await;
assert!(response.is_err());
assert_eq!(response.unwrap_err().code(), tonic::Code::NotFound);
}
#[tokio::test]
async fn test_get_visible_lines_with_window() {
let (registry, session) = test_registry_with_buffer_manager();
session
.with_state_mut(|state| {
let _buffer_id = state.create_buffer("line0\nline1\nline2\nline3");
})
.await;
session.add_client(ClientId::new(1));
let service = StateServiceImpl::new(registry, SessionId::new("test"));
let request = authed_request(
GetVisibleLinesRequest {
window_id: None,
client_id: 1,
},
ClientId::new(1),
);
let response = service.get_visible_lines(request).await;
assert!(response.is_ok());
let resp = response.unwrap().into_inner();
assert_eq!(resp.first_line, 0);
assert_eq!(resp.last_line, 23); assert_eq!(resp.viewport_height, 24);
}
#[tokio::test]
async fn test_get_visible_lines_with_scroll() {
use reovim_driver_session::Viewport;
let (registry, session) = test_registry_with_buffer_manager();
session
.with_state_mut(|state| {
state.create_buffer("content");
})
.await;
let client_id = ClientId::new(1);
session.add_client(client_id);
session.update_client_state(client_id, |state| {
if let Some(window) = state.windows.active_mut() {
let mut viewport = Viewport::new(80, 24);
viewport.scroll_top = 10; window.viewport = viewport;
}
});
let service = StateServiceImpl::new(registry, SessionId::new("test"));
let request = authed_request(
GetVisibleLinesRequest {
window_id: None,
client_id: 1,
},
ClientId::new(1),
);
let response = service.get_visible_lines(request).await;
assert!(response.is_ok());
let resp = response.unwrap().into_inner();
assert_eq!(resp.first_line, 10);
assert_eq!(resp.last_line, 33); assert_eq!(resp.viewport_height, 24);
}
#[tokio::test]
async fn test_get_mode_no_session() {
let registry = Arc::new(SessionRegistry::new());
let service = StateServiceImpl::new(registry, SessionId::new("nonexistent"));
let request = Request::new(GetModeRequest { client_id: 0 });
let response = service.get_mode(request).await;
assert!(response.is_err());
assert_eq!(response.unwrap_err().code(), tonic::Code::NotFound);
}
#[tokio::test]
async fn test_get_registers_empty() {
let (registry, session) = test_registry_with_session();
session.add_client(ClientId::new(1));
let service = StateServiceImpl::new(registry, SessionId::new("test"));
let request = authed_request(
GetRegistersRequest {
names: vec![],
client_id: 1,
},
ClientId::new(1),
);
let response = service.get_registers(request).await;
assert!(response.is_ok());
let resp = response.unwrap().into_inner();
assert!(resp.registers.is_empty());
}
#[tokio::test]
async fn test_get_registers_with_content() {
use reovim_kernel::api::v1::RegisterContent;
let (registry, session) = test_registry_with_buffer_manager();
session.add_client(ClientId::new(1));
session.update_client_state(ClientId::new(1), |state| {
state.registers.set(RegisterContent::characterwise("hello"));
});
let service = StateServiceImpl::new(registry, SessionId::new("test"));
let request = authed_request(
GetRegistersRequest {
names: vec![],
client_id: 1,
},
ClientId::new(1),
);
let response = service.get_registers(request).await;
assert!(response.is_ok());
let resp = response.unwrap().into_inner();
assert_eq!(resp.registers.len(), 1);
assert_eq!(resp.registers[0].name, "\"");
assert_eq!(resp.registers[0].content, "hello");
assert_eq!(resp.registers[0].yank_type, "char");
}
#[tokio::test]
async fn test_get_registers_specific_register() {
use reovim_kernel::api::v1::RegisterContent;
let (registry, session) = test_registry_with_buffer_manager();
session.add_client(ClientId::new(1));
session.update_client_state(ClientId::new(1), |state| {
state
.registers
.set(RegisterContent::characterwise("unnamed"));
state
.registers
.set_named('a', RegisterContent::linewise("alpha"));
state
.registers
.set_named('b', RegisterContent::characterwise("beta"));
});
let service = StateServiceImpl::new(registry, SessionId::new("test"));
let request = authed_request(
GetRegistersRequest {
names: vec!["a".to_string()],
client_id: 1,
},
ClientId::new(1),
);
let response = service.get_registers(request).await;
assert!(response.is_ok());
let resp = response.unwrap().into_inner();
assert_eq!(resp.registers.len(), 1);
assert_eq!(resp.registers[0].name, "a");
assert_eq!(resp.registers[0].content, "alpha");
assert_eq!(resp.registers[0].yank_type, "line");
}
#[tokio::test]
async fn test_get_selection_rejects_unauthenticated() {
let registry = test_registry();
let service = StateServiceImpl::new(registry, SessionId::new("test"));
let request = Request::new(GetSelectionRequest {
window_id: None,
client_id: 0,
});
let response = service.get_selection(request).await;
assert!(response.is_err());
assert_eq!(response.unwrap_err().code(), tonic::Code::Unauthenticated);
}
#[tokio::test]
async fn test_get_selection_no_selection() {
let (registry, session) = test_registry_with_buffer_manager();
session
.with_state_mut(|state| {
state.create_buffer("hello world");
})
.await;
session.add_client(ClientId::new(1));
let service = StateServiceImpl::new(registry, SessionId::new("test"));
let request = authed_request(
GetSelectionRequest {
window_id: None,
client_id: 1,
},
ClientId::new(1),
);
let response = service.get_selection(request).await;
assert!(response.is_ok());
let resp = response.unwrap().into_inner();
assert!(!resp.has_selection);
assert!(resp.selection.is_none());
assert!(resp.visual_mode.is_none());
}
#[tokio::test]
async fn test_get_selection_no_active_window() {
let (registry, session) = test_registry_with_session();
let service = StateServiceImpl::new(Arc::clone(®istry), SessionId::new("test"));
session.add_client(ClientId::new(1));
let request = authed_request(
GetSelectionRequest {
window_id: None,
client_id: 1,
},
ClientId::new(1),
);
let response = service.get_selection(request).await;
assert!(response.is_err());
assert_eq!(response.unwrap_err().code(), tonic::Code::NotFound);
}
#[tokio::test]
async fn test_get_selection_with_char_selection() {
use {
reovim_driver_session::{Viewport, api::Selection},
reovim_kernel::api::v1::Position as KernelPosition,
};
let (registry, session) = test_registry_with_buffer_manager();
session
.with_state_mut(|state| {
state.create_buffer("hello world");
})
.await;
let client_id = ClientId::new(1);
session.add_client(client_id);
session.update_client_state(client_id, |state| {
if let Some(window) = state.windows.active_mut() {
window.viewport = Viewport::new(80, 24);
window.selection =
Some(Selection::character(KernelPosition::new(0, 0), KernelPosition::new(0, 5)));
}
});
let service = StateServiceImpl::new(registry, SessionId::new("test"));
let request = authed_request(
GetSelectionRequest {
window_id: None,
client_id: 1,
},
ClientId::new(1),
);
let response = service.get_selection(request).await;
assert!(response.is_ok());
let resp = response.unwrap().into_inner();
assert!(resp.has_selection);
assert!(resp.selection.is_some());
let sel = resp.selection.unwrap();
assert_eq!(sel.start.as_ref().unwrap().line, 0);
assert_eq!(sel.start.as_ref().unwrap().column, 0);
assert_eq!(sel.end.as_ref().unwrap().line, 0);
assert_eq!(sel.end.as_ref().unwrap().column, 5); assert_eq!(resp.visual_mode, Some("char".to_string()));
}
#[tokio::test]
async fn test_get_selection_line_mode() {
use {
reovim_driver_session::{Viewport, api::Selection},
reovim_kernel::api::v1::Position as KernelPosition,
};
let (registry, session) = test_registry_with_buffer_manager();
session
.with_state_mut(|state| {
state.create_buffer("line1\nline2\nline3");
})
.await;
let client_id = ClientId::new(1);
session.add_client(client_id);
session.update_client_state(client_id, |state| {
if let Some(window) = state.windows.active_mut() {
window.viewport = Viewport::new(80, 24);
window.selection = Some(Selection::line(
KernelPosition::new(0, 0),
KernelPosition::new(2, 0), ));
}
});
let service = StateServiceImpl::new(registry, SessionId::new("test"));
let request = authed_request(
GetSelectionRequest {
window_id: None,
client_id: 1,
},
ClientId::new(1),
);
let response = service.get_selection(request).await;
assert!(response.is_ok());
let resp = response.unwrap().into_inner();
assert!(resp.has_selection);
assert_eq!(resp.visual_mode, Some("line".to_string()));
}
#[tokio::test]
async fn test_get_selection_block_mode() {
use {
reovim_driver_session::{Viewport, api::Selection},
reovim_kernel::api::v1::Position as KernelPosition,
};
let (registry, session) = test_registry_with_buffer_manager();
session
.with_state_mut(|state| {
state.create_buffer("ABC\nDEF\nGHI");
})
.await;
let client_id = ClientId::new(1);
session.add_client(client_id);
session.update_client_state(client_id, |state| {
if let Some(window) = state.windows.active_mut() {
window.viewport = Viewport::new(80, 24);
window.selection = Some(Selection::block(
KernelPosition::new(0, 0),
KernelPosition::new(2, 2), ));
}
});
let service = StateServiceImpl::new(registry, SessionId::new("test"));
let request = authed_request(
GetSelectionRequest {
window_id: None,
client_id: 1,
},
ClientId::new(1),
);
let response = service.get_selection(request).await;
assert!(response.is_ok());
let resp = response.unwrap().into_inner();
assert!(resp.has_selection);
assert_eq!(resp.visual_mode, Some("block".to_string()));
}
#[tokio::test]
async fn test_get_selection_reverse() {
use {
reovim_driver_session::{Viewport, api::Selection},
reovim_kernel::api::v1::Position as KernelPosition,
};
let (registry, session) = test_registry_with_buffer_manager();
session
.with_state_mut(|state| {
state.create_buffer("hello world");
})
.await;
let client_id = ClientId::new(1);
session.add_client(client_id);
session.update_client_state(client_id, |state| {
if let Some(window) = state.windows.active_mut() {
window.viewport = Viewport::new(80, 24);
window.selection =
Some(Selection::character(KernelPosition::new(0, 2), KernelPosition::new(0, 5)));
}
});
let service = StateServiceImpl::new(registry, SessionId::new("test"));
let request = authed_request(
GetSelectionRequest {
window_id: None,
client_id: 1,
},
ClientId::new(1),
);
let response = service.get_selection(request).await;
assert!(response.is_ok());
let resp = response.unwrap().into_inner();
assert!(resp.has_selection);
let sel = resp.selection.unwrap();
assert!(sel.start.as_ref().unwrap().column < sel.end.as_ref().unwrap().column);
assert_eq!(sel.start.as_ref().unwrap().column, 2);
assert_eq!(sel.end.as_ref().unwrap().column, 5);
}
#[tokio::test]
async fn test_get_mode_per_client_returns_client_mode() {
use reovim_kernel::api::v1::{ModeId, ModuleId};
let (registry, session) = test_registry_with_buffer_manager();
let client_id = crate::session::ClientId::new(42);
session.add_client(client_id);
let module = ModuleId::new("editor");
session.update_client_state(client_id, |editing_state| {
editing_state
.mode_stack
.push(ModeId::new(module.clone(), "insert"));
});
let service = StateServiceImpl::new(registry, SessionId::new("test"));
let request = authed_request(GetModeRequest { client_id: 42 }, ClientId::new(42));
let response = service.get_mode(request).await;
assert!(response.is_ok());
let resp = response.unwrap().into_inner();
assert_eq!(resp.name, "insert");
assert_eq!(resp.display, "INSERT");
assert!(resp.is_insert);
}
#[tokio::test]
async fn test_cursor_isolation_between_clients() {
use reovim_driver_session::CursorPosition;
let (registry, session) = test_registry_with_buffer_manager();
session
.with_state_mut(|state| {
state.create_buffer("line1\nline2\nline3\nline4\nline5");
})
.await;
let client_a = crate::session::ClientId::new(1);
let client_b = crate::session::ClientId::new(2);
session.add_client(client_a);
session.add_client(client_b);
session.update_client_state(client_a, |state| {
if let Some(window) = state.windows.active_mut() {
window.cursor = CursorPosition { line: 3, column: 5 };
}
});
let state_b = session.client_state(client_b).unwrap();
let cursor_b = state_b.windows.active().unwrap().cursor;
assert_eq!(cursor_b.line, 0, "Client B cursor line should be 0");
assert_eq!(cursor_b.column, 0, "Client B cursor column should be 0");
let service = StateServiceImpl::new(registry, SessionId::new("test"));
let request = authed_request(
GetCursorRequest {
window_id: None,
client_id: 1,
},
ClientId::new(1),
);
let response = service.get_cursor(request).await.unwrap().into_inner();
let pos = response.position.unwrap();
assert_eq!(pos.line, 3, "Client A gRPC cursor line");
assert_eq!(pos.column, 5, "Client A gRPC cursor column");
let request = authed_request(
GetCursorRequest {
window_id: None,
client_id: 2,
},
ClientId::new(2),
);
let response = service.get_cursor(request).await.unwrap().into_inner();
let pos = response.position.unwrap();
assert_eq!(pos.line, 0, "Client B gRPC cursor line");
assert_eq!(pos.column, 0, "Client B gRPC cursor column");
}
#[tokio::test]
async fn test_mode_isolation_between_clients() {
use reovim_kernel::api::v1::{ModeId, ModuleId};
let (registry, session) = test_registry_with_buffer_manager();
let client_a = crate::session::ClientId::new(10);
let client_b = crate::session::ClientId::new(20);
session.add_client(client_a);
session.add_client(client_b);
let module = ModuleId::new("editor");
session.update_client_state(client_a, |state| {
state.mode_stack.push(ModeId::new(module.clone(), "insert"));
});
let mode_b = session.client_current_mode(client_b).unwrap();
assert_eq!(mode_b.name(), "normal", "Client B should remain in normal mode");
let service = StateServiceImpl::new(registry, SessionId::new("test"));
let request = authed_request(GetModeRequest { client_id: 10 }, ClientId::new(10));
let response = service.get_mode(request).await.unwrap().into_inner();
assert_eq!(response.name, "insert", "Client A gRPC mode");
assert!(response.is_insert, "Client A should be in insert mode");
let request = authed_request(GetModeRequest { client_id: 20 }, ClientId::new(20));
let response = service.get_mode(request).await.unwrap().into_inner();
assert_eq!(response.name, "normal", "Client B gRPC mode");
assert!(!response.is_insert, "Client B should NOT be in insert mode");
}
#[tokio::test]
async fn test_layout_isolation_per_client() {
use reovim_driver_session::Viewport;
let (registry, session) = test_registry_with_buffer_manager();
session
.with_state_mut(|state| {
state.create_buffer("test content");
})
.await;
let client_a = crate::session::ClientId::new(100);
session.add_client(client_a);
session.update_client_state(client_a, |state| {
if let Some(window) = state.windows.active_mut() {
window.viewport = Viewport::new(120, 40);
}
});
let service = StateServiceImpl::new(registry, SessionId::new("test"));
let request = authed_request(GetLayoutRequest { client_id: 100 }, ClientId::new(100));
let response = service.get_layout(request).await.unwrap().into_inner();
assert!(response.root.is_some(), "Should have a root node");
if let Some(node) = response.root
&& let Some(Node::Leaf(leaf)) = node.node
&& let Some(rect) = leaf.rect
{
assert_eq!(rect.width, 120, "Per-client window width");
assert_eq!(rect.height, 40, "Per-client window height");
} else {
panic!("Expected a leaf node with rect");
}
}
#[tokio::test]
async fn test_get_visible_lines_with_specific_window_id() {
let (registry, session) = test_registry_with_buffer_manager();
session
.with_state_mut(|state| {
state.create_buffer("line0\nline1\nline2");
})
.await;
let client_id = ClientId::new(1);
session.add_client(client_id);
let window_id = session
.client_state(client_id)
.unwrap()
.windows
.active()
.unwrap()
.id
.as_usize() as u64;
let service = StateServiceImpl::new(registry, SessionId::new("test"));
let request = authed_request(
GetVisibleLinesRequest {
window_id: Some(window_id),
client_id: 1,
},
ClientId::new(1),
);
let response = service.get_visible_lines(request).await;
assert!(response.is_ok());
let resp = response.unwrap().into_inner();
assert_eq!(resp.window_id, window_id);
assert_eq!(resp.first_line, 0);
}
#[tokio::test]
async fn test_get_visible_lines_with_invalid_window_id() {
let (registry, session) = test_registry_with_buffer_manager();
session
.with_state_mut(|state| {
state.create_buffer("content");
})
.await;
let client_id = ClientId::new(1);
session.add_client(client_id);
let service = StateServiceImpl::new(registry, SessionId::new("test"));
let request = authed_request(
GetVisibleLinesRequest {
window_id: Some(99999), client_id: 1,
},
ClientId::new(1),
);
let response = service.get_visible_lines(request).await;
assert!(response.is_err());
assert_eq!(response.unwrap_err().code(), tonic::Code::NotFound);
}
#[tokio::test]
async fn test_get_layout_multi_window() {
use reovim_driver_session::Window;
let (registry, session) = test_registry_with_buffer_manager();
session
.with_state_mut(|state| {
state.create_buffer("content");
})
.await;
let client_id = ClientId::new(1);
session.add_client(client_id);
let buffer_id2 = reovim_kernel::api::v1::BufferId::from_raw(99);
session.update_client_state(client_id, |state| {
let window2 = Window::with_buffer(buffer_id2);
state.windows.add(window2);
});
let service = StateServiceImpl::new(registry, SessionId::new("test"));
let request = authed_request(GetLayoutRequest { client_id: 1 }, ClientId::new(1));
let response = service.get_layout(request).await;
assert!(response.is_ok());
let resp = response.unwrap().into_inner();
assert!(resp.root.is_some());
let root = resp.root.unwrap();
match root.node {
Some(Node::Split(split)) => {
assert_eq!(split.direction, SplitDirection::Vertical as i32);
assert_eq!(split.children.len(), 2);
}
_ => panic!("Expected split node for multi-window layout"),
}
}
#[tokio::test]
async fn test_submit_capture_response() {
let registry = test_registry();
let service = StateServiceImpl::new(Arc::clone(®istry), SessionId::new("test"));
let session = registry.get(&SessionId::new("test")).unwrap();
let (request_id, _rx) = session.capture_tracker().create_pending();
let req = Request::new(SubmitCaptureRequest {
request_id,
width: 80,
height: 24,
format: "plain_text".to_string(),
content: "Hello World".to_string(),
});
let response = service.submit_capture_response(req).await;
assert!(response.is_ok());
let resp = response.unwrap().into_inner();
assert!(resp.ok);
}
#[tokio::test]
async fn test_submit_capture_response_no_pending() {
let registry = test_registry();
let service = StateServiceImpl::new(registry, SessionId::new("test"));
let req = Request::new(SubmitCaptureRequest {
request_id: 99999, width: 80,
height: 24,
format: "plain_text".to_string(),
content: "data".to_string(),
});
let response = service.submit_capture_response(req).await;
assert!(response.is_ok());
let resp = response.unwrap().into_inner();
assert!(!resp.ok); }
#[tokio::test]
async fn test_get_registers_specific_nonexistent_register() {
let (registry, session) = test_registry_with_session();
session.add_client(ClientId::new(1));
let service = StateServiceImpl::new(registry, SessionId::new("test"));
let request = authed_request(
GetRegistersRequest {
names: vec!["z".to_string()],
client_id: 1,
},
ClientId::new(1),
);
let response = service.get_registers(request).await;
assert!(response.is_ok());
let resp = response.unwrap().into_inner();
assert!(resp.registers.is_empty());
}
#[tokio::test]
async fn test_get_registers_linewise() {
use reovim_kernel::api::v1::RegisterContent;
let (registry, session) = test_registry_with_buffer_manager();
session.add_client(ClientId::new(1));
session.update_client_state(ClientId::new(1), |state| {
state
.registers
.set(RegisterContent::linewise("line content\n"));
});
let service = StateServiceImpl::new(registry, SessionId::new("test"));
let request = authed_request(
GetRegistersRequest {
names: vec![],
client_id: 1,
},
ClientId::new(1),
);
let response = service.get_registers(request).await;
assert!(response.is_ok());
let resp = response.unwrap().into_inner();
assert_eq!(resp.registers.len(), 1);
assert_eq!(resp.registers[0].yank_type, "line");
}
#[tokio::test]
async fn test_get_screen_content_invalid_format() {
let registry = test_registry();
let service = StateServiceImpl::new(registry, SessionId::new("test"));
let request = Request::new(GetScreenContentRequest {
format: "invalid_format".to_string(),
client_id: 0,
});
let response = service.get_screen_content(request).await;
assert!(response.is_err());
assert_eq!(response.unwrap_err().code(), tonic::Code::InvalidArgument);
}
#[tokio::test]
async fn test_get_layout_unknown_client() {
let registry = test_registry();
let service = StateServiceImpl::new(registry, SessionId::new("test"));
let request = authed_request(GetLayoutRequest { client_id: 999 }, ClientId::new(999));
let response = service.get_layout(request).await;
assert!(response.is_err());
assert_eq!(response.unwrap_err().code(), tonic::Code::NotFound);
}
#[tokio::test]
async fn test_get_visible_lines_unknown_client() {
let registry = test_registry();
let service = StateServiceImpl::new(registry, SessionId::new("test"));
let request = authed_request(
GetVisibleLinesRequest {
window_id: None,
client_id: 999,
},
ClientId::new(999),
);
let response = service.get_visible_lines(request).await;
assert!(response.is_err());
assert_eq!(response.unwrap_err().code(), tonic::Code::NotFound);
}
#[tokio::test]
async fn test_get_selection_unknown_client() {
let registry = test_registry();
let service = StateServiceImpl::new(registry, SessionId::new("test"));
let request = authed_request(
GetSelectionRequest {
window_id: None,
client_id: 999,
},
ClientId::new(999),
);
let response = service.get_selection(request).await;
assert!(response.is_err());
assert_eq!(response.unwrap_err().code(), tonic::Code::NotFound);
}
#[tokio::test]
async fn test_get_mode_following_client_triggers_ring_buffer_log() {
use crate::session::ClientRelation;
let (registry, session) = test_registry_with_session();
let service = StateServiceImpl::new(Arc::clone(®istry), SessionId::new("test"));
let owner_id = ClientId::new(1);
let follower_id = ClientId::new(2);
session.add_client(owner_id);
session.add_client(follower_id);
let _ = session
.set_client_relation(follower_id, Some(ClientRelation::Following { target: owner_id }));
let request = authed_request(GetModeRequest { client_id: 2 }, ClientId::new(2));
let response = service.get_mode(request).await;
assert!(response.is_err());
assert_eq!(response.unwrap_err().code(), tonic::Code::NotFound);
}
#[tokio::test]
async fn test_get_cursor_following_client_triggers_ring_buffer_log() {
use crate::session::ClientRelation;
let (registry, session) = test_registry_with_session();
let service = StateServiceImpl::new(Arc::clone(®istry), SessionId::new("test"));
let owner_id = ClientId::new(1);
let follower_id = ClientId::new(2);
session.add_client(owner_id);
session.add_client(follower_id);
let _ = session
.set_client_relation(follower_id, Some(ClientRelation::Following { target: owner_id }));
let request = authed_request(
GetCursorRequest {
window_id: None,
client_id: 999,
},
ClientId::new(999),
);
let response = service.get_cursor(request).await;
assert!(response.is_err());
assert_eq!(response.unwrap_err().code(), tonic::Code::NotFound);
}
#[tokio::test]
async fn test_get_layout_following_client_not_found_logs() {
let (registry, session) = test_registry_with_session();
let service = StateServiceImpl::new(Arc::clone(®istry), SessionId::new("test"));
session.add_client(ClientId::new(1));
let request = authed_request(GetLayoutRequest { client_id: 888 }, ClientId::new(888));
let response = service.get_layout(request).await;
assert!(response.is_err());
assert_eq!(response.unwrap_err().code(), tonic::Code::NotFound);
}
#[tokio::test]
async fn test_get_visible_lines_client_not_found_logs() {
let (registry, session) = test_registry_with_session();
let service = StateServiceImpl::new(Arc::clone(®istry), SessionId::new("test"));
session.add_client(ClientId::new(1));
let request = authed_request(
GetVisibleLinesRequest {
window_id: None,
client_id: 777,
},
ClientId::new(777),
);
let response = service.get_visible_lines(request).await;
assert!(response.is_err());
assert_eq!(response.unwrap_err().code(), tonic::Code::NotFound);
}
#[tokio::test]
async fn test_get_selection_client_not_found_logs() {
let (registry, session) = test_registry_with_session();
let service = StateServiceImpl::new(Arc::clone(®istry), SessionId::new("test"));
session.add_client(ClientId::new(1));
let request = authed_request(
GetSelectionRequest {
window_id: None,
client_id: 666,
},
ClientId::new(666),
);
let response = service.get_selection(request).await;
assert!(response.is_err());
assert_eq!(response.unwrap_err().code(), tonic::Code::NotFound);
}
#[test]
fn test_capture_error_to_status_all_variants() {
use {super::capture_error_to_status, crate::session::CaptureError};
let status = capture_error_to_status(CaptureError::NoTuiClient);
assert_eq!(status.code(), tonic::Code::Unavailable);
let status = capture_error_to_status(CaptureError::Timeout);
assert_eq!(status.code(), tonic::Code::DeadlineExceeded);
let status = capture_error_to_status(CaptureError::Disconnected);
assert_eq!(status.code(), tonic::Code::Aborted);
let status = capture_error_to_status(CaptureError::InvalidResponse("bad data".into()));
assert_eq!(status.code(), tonic::Code::Internal);
assert!(status.message().contains("bad data"));
}
#[tokio::test]
async fn test_get_screen_content_default_format() {
let (registry, _session) = test_registry_with_session();
let service = StateServiceImpl::new(Arc::clone(®istry), SessionId::new("test"));
let request = Request::new(GetScreenContentRequest {
format: String::new(), client_id: 0,
});
let _response = service.get_screen_content(request).await;
}
#[tokio::test]
async fn test_get_screen_content_valid_formats() {
let (registry, _session) = test_registry_with_session();
let service = StateServiceImpl::new(Arc::clone(®istry), SessionId::new("test"));
for format in &["plain_text", "raw_ansi", "cell_grid"] {
let request = Request::new(GetScreenContentRequest {
format: (*format).to_string(),
client_id: 0,
});
let _response = service.get_screen_content(request).await;
}
}
#[tokio::test]
async fn test_get_registers_specific_register_with_content() {
use reovim_kernel::api::v1::RegisterContent;
let (registry, session) = test_registry_with_buffer_manager();
session.add_client(ClientId::new(1));
session.update_client_state(ClientId::new(1), |state| {
state
.registers
.set_named('a', RegisterContent::characterwise("hello world"));
});
let service = StateServiceImpl::new(registry, SessionId::new("test"));
let request = authed_request(
GetRegistersRequest {
names: vec!["a".to_string()],
client_id: 1,
},
ClientId::new(1),
);
let response = service.get_registers(request).await;
assert!(response.is_ok());
let resp = response.unwrap().into_inner();
assert_eq!(resp.registers.len(), 1);
assert_eq!(resp.registers[0].name, "a");
assert_eq!(resp.registers[0].content, "hello world");
assert_eq!(resp.registers[0].yank_type, "char");
}
#[tokio::test]
async fn test_get_registers_specific_linewise_register() {
use reovim_kernel::api::v1::RegisterContent;
let (registry, session) = test_registry_with_buffer_manager();
session.add_client(ClientId::new(1));
session.update_client_state(ClientId::new(1), |state| {
state
.registers
.set_named('b', RegisterContent::linewise("a full line\n"));
});
let service = StateServiceImpl::new(registry, SessionId::new("test"));
let request = authed_request(
GetRegistersRequest {
names: vec!["b".to_string()],
client_id: 1,
},
ClientId::new(1),
);
let response = service.get_registers(request).await;
assert!(response.is_ok());
let resp = response.unwrap().into_inner();
assert_eq!(resp.registers.len(), 1);
assert_eq!(resp.registers[0].yank_type, "line");
}
#[tokio::test]
async fn test_get_registers_multiple_specific() {
use reovim_kernel::api::v1::RegisterContent;
let (registry, session) = test_registry_with_buffer_manager();
session.add_client(ClientId::new(1));
session.update_client_state(ClientId::new(1), |state| {
state
.registers
.set_named('a', RegisterContent::characterwise("alpha"));
state
.registers
.set_named('b', RegisterContent::linewise("beta\n"));
});
let service = StateServiceImpl::new(registry, SessionId::new("test"));
let request = authed_request(
GetRegistersRequest {
names: vec!["a".to_string(), "b".to_string(), "c".to_string()],
client_id: 1,
},
ClientId::new(1),
);
let response = service.get_registers(request).await;
assert!(response.is_ok());
let resp = response.unwrap().into_inner();
assert_eq!(resp.registers.len(), 2);
}
#[tokio::test]
async fn test_get_registers_specific_empty_register_filtered_out() {
use reovim_kernel::api::v1::RegisterContent;
let (registry, session) = test_registry_with_buffer_manager();
session.add_client(ClientId::new(1));
session.update_client_state(ClientId::new(1), |state| {
state
.registers
.set_named('x', RegisterContent::characterwise(""));
state
.registers
.set_named('y', RegisterContent::characterwise("visible"));
});
let service = StateServiceImpl::new(registry, SessionId::new("test"));
let request = authed_request(
GetRegistersRequest {
names: vec!["x".to_string(), "y".to_string()],
client_id: 1,
},
ClientId::new(1),
);
let response = service.get_registers(request).await;
assert!(response.is_ok());
let resp = response.unwrap().into_inner();
assert_eq!(resp.registers.len(), 1);
assert_eq!(resp.registers[0].name, "y");
}
#[tokio::test]
async fn test_get_registers_client_not_found() {
let registry = test_registry();
let service = StateServiceImpl::new(registry, SessionId::new("test"));
let request = authed_request(
GetRegistersRequest {
client_id: 999,
names: vec![],
},
ClientId::new(999),
);
let response = service.get_registers(request).await;
assert!(response.is_err());
assert_eq!(response.unwrap_err().code(), tonic::Code::NotFound);
}
#[tokio::test]
async fn test_selection_isolation_per_client() {
use {
reovim_driver_session::{Viewport, api::Selection},
reovim_kernel::api::v1::Position as KernelPosition,
};
let (registry, session) = test_registry_with_buffer_manager();
session
.with_state_mut(|state| {
state.create_buffer("hello world");
})
.await;
let client_a = crate::session::ClientId::new(50);
let client_b = crate::session::ClientId::new(60);
session.add_client(client_a);
session.add_client(client_b);
session.update_client_state(client_a, |state| {
if let Some(window) = state.windows.active_mut() {
window.viewport = Viewport::new(80, 24);
window.selection =
Some(Selection::character(KernelPosition::new(0, 0), KernelPosition::new(0, 5)));
}
});
session.update_client_state(client_b, |state| {
if let Some(window) = state.windows.active_mut() {
window.viewport = Viewport::new(80, 24);
window.selection = None;
}
});
let service = StateServiceImpl::new(registry, SessionId::new("test"));
let request = authed_request(
GetSelectionRequest {
window_id: None,
client_id: 50,
},
ClientId::new(50),
);
let response = service.get_selection(request).await.unwrap().into_inner();
assert!(response.has_selection, "Client A should have selection");
assert_eq!(response.visual_mode, Some("char".to_string()), "Client A selection mode");
let request = authed_request(
GetSelectionRequest {
window_id: None,
client_id: 60,
},
ClientId::new(60),
);
let response = service.get_selection(request).await.unwrap().into_inner();
assert!(!response.has_selection, "Client B should NOT have selection");
}
fn test_registry_with_dangling_follower(client_id: ClientId) -> Arc<SessionRegistry> {
use {
crate::session::{Client, ClientMetadata, ClientRelation},
reovim_kernel::api::v1::{ModeId, ModeStack, ModuleId},
};
let (registry, session) = test_registry_with_session();
let mode = ModeId::new(ModuleId::new("test"), "normal");
let mode_stack = ModeStack::new(mode);
let metadata = ClientMetadata::default();
let mut client = Client::with_mode_stack(client_id, metadata, mode_stack);
client.relation = Some(ClientRelation::Following {
target: ClientId::new(99999),
});
session.add_client_with_state(client);
registry
}
#[tokio::test]
async fn test_get_cursor_dangling_follower_not_found() {
let cid = ClientId::new(50);
let registry = test_registry_with_dangling_follower(cid);
let service = StateServiceImpl::new(registry, SessionId::new("test"));
let request = authed_request(
GetCursorRequest {
client_id: cid.as_usize() as u64,
window_id: None,
},
cid,
);
let err = service.get_cursor(request).await.unwrap_err();
assert_eq!(err.code(), tonic::Code::NotFound);
}
#[tokio::test]
async fn test_get_layout_dangling_follower_not_found() {
let cid = ClientId::new(51);
let registry = test_registry_with_dangling_follower(cid);
let service = StateServiceImpl::new(registry, SessionId::new("test"));
let request = authed_request(
GetLayoutRequest {
client_id: cid.as_usize() as u64,
},
cid,
);
let err = service.get_layout(request).await.unwrap_err();
assert_eq!(err.code(), tonic::Code::NotFound);
}
#[tokio::test]
async fn test_get_visible_lines_dangling_follower_not_found() {
let cid = ClientId::new(52);
let registry = test_registry_with_dangling_follower(cid);
let service = StateServiceImpl::new(registry, SessionId::new("test"));
let request = authed_request(
GetVisibleLinesRequest {
client_id: cid.as_usize() as u64,
window_id: None,
},
cid,
);
let err = service.get_visible_lines(request).await.unwrap_err();
assert_eq!(err.code(), tonic::Code::NotFound);
}
#[tokio::test]
async fn test_get_selection_dangling_follower_not_found() {
let cid = ClientId::new(53);
let registry = test_registry_with_dangling_follower(cid);
let service = StateServiceImpl::new(registry, SessionId::new("test"));
let request = authed_request(
GetSelectionRequest {
client_id: cid.as_usize() as u64,
window_id: None,
},
cid,
);
let err = service.get_selection(request).await.unwrap_err();
assert_eq!(err.code(), tonic::Code::NotFound);
}