use super::*;
#[test]
fn tls_loader_rejects_empty_pem_files() {
let file = tempfile::NamedTempFile::new().expect("temporary PEM");
let error = load_certificates(file.path()).expect_err("empty PEM must fail");
assert!(error.to_string().contains("certificate file is empty"));
}
#[tokio::test]
async fn paired_client_creates_workspace_directory_and_receives_its_listing() {
let root = tempfile::tempdir().expect("root");
let parent = root.path().join("projects");
fs::create_dir(&parent).expect("parent");
let (server, grant) = GatewayServer::bootstrap(
root.path().join("state"),
std::net::SocketAddr::from(([127, 0, 0, 1], 0)),
)
.await
.expect("bootstrap gateway");
let listen = server.listen_addr();
let (shutdown, signal) = tokio::sync::oneshot::channel();
let serving = tokio::spawn(server.serve_until(async move {
let _ = signal.await;
}));
let endpoint = format!("tcp://{listen}")
.parse::<Endpoint>()
.expect("endpoint");
let (connection, _) =
GatewayClient::pair(&endpoint, grant.code, "directory test", ClientKind::Ios)
.await
.expect("connect client");
let (sender, mut events) = connection.into_parts();
wait_gateway_ready(&mut events).await;
let request_id = "create-directory".to_string();
sender
.send(ClientMessage::CreateWorkspaceDirectory {
request_id: request_id.clone(),
parent: parent.clone(),
name: "new-project".into(),
})
.await
.expect("create workspace directory");
loop {
match next_gateway_message(&mut events).await {
ServerMessage::Directories {
request_id: actual,
listing,
} if actual == request_id => {
assert_eq!(
listing.path,
fs::canonicalize(parent.join("new-project")).expect("created path")
);
assert!(listing.entries.is_empty());
break;
}
ServerMessage::Rejected {
request_id: actual,
code,
message,
..
} if actual == request_id => {
panic!("directory creation rejected ({code}): {message}");
}
_ => {}
}
}
assert!(parent.join("new-project").is_dir());
shutdown.send(()).expect("stop gateway");
serving.await.expect("gateway task").expect("gateway stop");
}
#[test]
fn directory_listing_is_sorted_and_excludes_files() {
let root = tempfile::tempdir().expect("temporary directory");
fs::create_dir(root.path().join("zeta")).expect("create directory");
fs::create_dir(root.path().join("Alpha")).expect("create directory");
fs::write(root.path().join("notes.txt"), b"not a folder").expect("create file");
let listing = list_directories(root.path(), false).expect("list directories");
assert_eq!(
listing
.entries
.iter()
.map(|entry| entry.name.as_str())
.collect::<Vec<_>>(),
["Alpha", "zeta"]
);
}
#[test]
fn directory_listing_can_include_files() {
let root = tempfile::tempdir().expect("temporary directory");
fs::create_dir(root.path().join("tasks")).expect("create directory");
fs::create_dir(root.path().join(".git")).expect("create Git metadata");
fs::write(root.path().join("daily.md"), b"task").expect("create file");
let listing = list_directories(root.path(), true).expect("list directory entries");
assert_eq!(listing.entries.len(), 2);
assert!(
listing
.entries
.iter()
.any(|entry| { entry.name == "tasks" && entry.is_directory })
);
assert!(
listing
.entries
.iter()
.any(|entry| { entry.name == "daily.md" && !entry.is_directory })
);
}
#[test]
fn history_frame_bound_rejects_one_oversized_turn() {
let frame = ServerFrame::new(ServerMessage::SessionHistory {
request_id: "history".into(),
session_id: "session".into(),
records: vec![crate::wire::RecordedEvent {
sequence: 1,
recorded_at_ms: 1,
event: Event {
submission_id: None,
msg: EventMsg::Warning(mobius::protocol::WarningEvent {
message: "x".repeat(MAX_FRAME_BYTES),
}),
},
stream_metrics: Vec::new(),
blocks: Vec::new(),
preview: None,
}],
next_before_sequence: None,
});
assert!(!encoded_frame_fits(&frame).expect("measure history frame"));
}
#[tokio::test]
async fn rejection_frames_preserve_request_correlation() {
let (mut writer, reader) = tokio::io::duplex(1024);
let mut reader = FrameReader::new(reader);
write_rejection(
&mut writer,
"request-7".into(),
Rejection {
code: "agent_busy",
message: "busy".into(),
fatal: false,
},
)
.await
.expect("write rejection");
let frame = read_frame::<ServerFrame>(&mut reader)
.await
.expect("read rejection")
.expect("frame");
assert!(matches!(
frame.message,
ServerMessage::Rejected { request_id, .. } if request_id == "request-7"
));
}