use super::*;
#[test]
fn the_layout_is_one_owned_directory_under_the_sidecar() {
let sidecar = std::path::Path::new(
"/Users/dev/projects/-Users-dev-relay/00000000-0000-4000-8000-000000000001",
);
let root = channel_dir(sidecar);
assert!(root.ends_with("csift-channel"));
assert_eq!(root.parent(), Some(sidecar));
assert!(messages_dir(&root).ends_with("messages"));
assert!(outbox_path(&root).ends_with("outbox.jsonl"));
assert!(inbox_path(&root, RECEIVER)
.unwrap()
.ends_with(format!("inbox/{RECEIVER}.jsonl")));
assert!(ledger_path(&root, AGENT)
.unwrap()
.ends_with(format!("ledger/{AGENT}.jsonl")));
assert!(armed_path(&root, TEAMMATE)
.unwrap()
.ends_with(format!("armed/{TEAMMATE}.json")));
assert!(message_path(&root, MSG_ID)
.unwrap()
.ends_with(format!("messages/{MSG_ID}.json")));
}
#[test]
fn a_lane_id_is_a_uuid_or_one_of_the_two_agent_shapes() {
assert!(is_lane_id(SESSION));
assert!(is_lane_id(AGENT));
assert!(is_lane_id(TEAMMATE));
assert!(is_lane_id("aP1-region-0123456789abcdef"));
}
#[test]
fn nothing_that_could_escape_the_directory_is_a_lane_id() {
for bad in [
"",
"..",
"../../etc/passwd",
"a/b",
"main",
"0123456789abcdef.jsonl",
"aRelay-0123456789abcdef/../x",
"C:-Users-dev",
] {
assert!(!is_lane_id(bad), "`{bad}` must not be a lane id");
assert!(validate_lane_id(bad).is_err());
}
}
#[test]
fn a_bad_lane_id_fails_the_path_builders_loudly() {
let root = std::path::Path::new("/Users/dev/channel");
let err = inbox_path(root, "../escape").unwrap_err().to_string();
assert!(err.contains("not a lane id"), "{err}");
assert!(ledger_path(root, "").is_err());
assert!(armed_path(root, "nope").is_err());
}
#[test]
fn a_message_id_is_exactly_sixteen_lowercase_hex() {
assert!(is_message_id(MSG_ID));
for bad in [
"",
"0123456789ABCDEF",
"0123456789abcde",
"0123456789abcdef0",
"0123456789abcdeg",
"../0123456789ab",
] {
assert!(!is_message_id(bad), "`{bad}` must not be a message id");
}
assert!(message_path(std::path::Path::new("/Users/dev"), "nope").is_err());
}
#[test]
fn append_creates_the_directory_and_keeps_append_order() {
let fx = Fixture::new();
let path = ledger_path(&fx.root, AGENT).unwrap();
append_line(&path, "{\"n\":1}").unwrap();
append_line(&path, "{\"n\":2}").unwrap();
let (values, skipped) = read_jsonl(&path).unwrap();
assert_eq!(skipped, 0);
assert_eq!(values.len(), 2);
assert_eq!(values[0]["n"], 1);
assert_eq!(values[1]["n"], 2);
}
#[test]
fn two_writers_appending_at_once_interleave_whole_lines() {
let fx = Fixture::new();
let path = ledger_path(&fx.root, AGENT).unwrap();
append_line(&path, "{\"seed\":true}").unwrap();
let rounds = 200;
let mut handles = Vec::new();
for writer in 0..2 {
let p = path.clone();
handles.push(std::thread::spawn(move || {
for i in 0..rounds {
let line = format!(
"{{\"writer\":{writer},\"i\":{i},\"pad\":\"{}\"}}",
"x".repeat(80)
);
append_line(&p, &line).unwrap();
}
}));
}
for h in handles {
h.join().unwrap();
}
let (values, skipped) = read_jsonl(&path).unwrap();
assert_eq!(skipped, 0, "a torn line appeared");
assert_eq!(values.len(), 1 + 2 * rounds);
for writer in 0..2 {
let count = values
.iter()
.filter(|v| v.get("writer").and_then(serde_json::Value::as_u64) == Some(writer))
.count();
assert_eq!(count, rounds, "writer {writer} lost lines");
}
}
#[test]
fn an_atomic_rewrite_replaces_the_file_and_leaves_no_temp_behind() {
let fx = Fixture::new();
let path = armed_path(&fx.root, AGENT).unwrap();
write_atomic(&path, "{\"v\":1}").unwrap();
write_atomic(&path, "{\"v\":2}").unwrap();
assert_eq!(read_json_object(&path).unwrap()["v"], 2);
let leftovers: Vec<_> = std::fs::read_dir(path.parent().unwrap())
.unwrap()
.flatten()
.map(|e| e.file_name().to_string_lossy().into_owned())
.filter(|n| n.contains(".tmp-"))
.collect();
assert!(leftovers.is_empty(), "temp files left: {leftovers:?}");
}
#[test]
fn a_missing_file_reads_as_empty_and_a_malformed_line_is_counted() {
let fx = Fixture::new();
let path = ledger_path(&fx.root, AGENT).unwrap();
let (values, skipped) = read_jsonl(&path).unwrap();
assert!(values.is_empty());
assert_eq!(skipped, 0);
assert!(read_json_object(&path).is_none());
append_line(&path, "{\"ok\":1}").unwrap();
append_line(&path, "this is not json").unwrap();
append_line(&path, "").unwrap();
append_line(&path, "{\"ok\":2}").unwrap();
let (values, skipped) = read_jsonl(&path).unwrap();
assert_eq!(values.len(), 2);
assert_eq!(skipped, 1);
}