186 lines
5.4 KiB
Rust
186 lines
5.4 KiB
Rust
//! `bxctl reindex` and `bxctl memory`: parsing, what is sent to `memoryd`, and what is printed
|
|
//! (M5a spec, section 10). Do not edit.
|
|
|
|
use std::os::unix::net::UnixListener;
|
|
use std::path::{Path, PathBuf};
|
|
use std::sync::mpsc;
|
|
use std::time::Duration;
|
|
|
|
use bxctl::cli::{Command, UsageError, parse};
|
|
use bxctl::memory::{Ask, run};
|
|
use proto::{
|
|
Envelope, ErrorCode, MemoryAdminReply, MemoryCommand, Message, Reindexed, WireError,
|
|
read_frame, write_frame,
|
|
};
|
|
|
|
fn args(line: &str) -> Vec<String> {
|
|
line.split_whitespace().map(str::to_string).collect()
|
|
}
|
|
|
|
fn home() -> PathBuf {
|
|
PathBuf::from("/srv/bx")
|
|
}
|
|
|
|
fn memory(ask: Ask) -> Result<Command, UsageError> {
|
|
Ok(Command::Memory {
|
|
socket: PathBuf::from("/srv/bx/run/owner-memory/admin.sock"),
|
|
ask,
|
|
})
|
|
}
|
|
|
|
#[test]
|
|
fn the_commands_parse() {
|
|
let h = home();
|
|
assert_eq!(parse(&args("reindex"), &h), memory(Ask::Reindex));
|
|
assert_eq!(
|
|
parse(&args("memory log"), &h),
|
|
memory(Ask::Admin(MemoryCommand::Log, String::new()))
|
|
);
|
|
assert_eq!(
|
|
parse(&args("memory log 3"), &h),
|
|
memory(Ask::Admin(MemoryCommand::Log, "3".to_string()))
|
|
);
|
|
assert_eq!(
|
|
parse(&args("memory undo 2026-09-24-0007"), &h),
|
|
memory(Ask::Admin(
|
|
MemoryCommand::Undo,
|
|
"2026-09-24-0007".to_string()
|
|
))
|
|
);
|
|
assert_eq!(
|
|
parse(&args("memory pending"), &h),
|
|
memory(Ask::Admin(MemoryCommand::Pending, String::new()))
|
|
);
|
|
assert_eq!(
|
|
parse(&args("memory accept p-1"), &h),
|
|
memory(Ask::Admin(MemoryCommand::Accept, "p-1".to_string()))
|
|
);
|
|
assert_eq!(
|
|
parse(&args("memory reject p-1"), &h),
|
|
memory(Ask::Admin(MemoryCommand::Reject, "p-1".to_string()))
|
|
);
|
|
assert_eq!(
|
|
parse(&args("reindex --memory-socket /r/m.sock"), &h),
|
|
Ok(Command::Memory {
|
|
socket: PathBuf::from("/r/m.sock"),
|
|
ask: Ask::Reindex
|
|
})
|
|
);
|
|
assert_eq!(
|
|
parse(&args("memory --memory-socket /r/m.sock undo x"), &h),
|
|
Ok(Command::Memory {
|
|
socket: PathBuf::from("/r/m.sock"),
|
|
ask: Ask::Admin(MemoryCommand::Undo, "x".to_string())
|
|
})
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn bad_lines_are_usage_errors() {
|
|
for line in [
|
|
"memory",
|
|
"memory log ten",
|
|
"memory log 1 2",
|
|
"memory undo",
|
|
"memory undo a b",
|
|
"memory forget x",
|
|
"memory pending x",
|
|
"reindex now",
|
|
"reindex --memory-socket",
|
|
"reindex --memory-socket /a --memory-socket /b",
|
|
] {
|
|
assert_eq!(parse(&args(line), &home()), Err(UsageError), "{line}");
|
|
}
|
|
}
|
|
|
|
/// A fake `memoryd` answering one request with `answer`; hands back what it was sent.
|
|
fn fake(socket: &Path, answer: Message) -> mpsc::Receiver<Message> {
|
|
let listener = UnixListener::bind(socket).unwrap();
|
|
let (tx, rx) = mpsc::channel();
|
|
std::thread::spawn(move || {
|
|
let (mut stream, _) = listener.accept().unwrap();
|
|
let env = read_frame(&mut stream).unwrap();
|
|
tx.send(env.msg).unwrap();
|
|
let reply = Envelope {
|
|
v: 1,
|
|
id: env.id,
|
|
r#final: true,
|
|
msg: answer,
|
|
};
|
|
write_frame(&mut stream, &reply).unwrap();
|
|
});
|
|
rx
|
|
}
|
|
|
|
fn dir(tag: &str) -> PathBuf {
|
|
let path = std::env::temp_dir().join(format!("bx-mem-{tag}-{}", std::process::id()));
|
|
let _ = std::fs::remove_dir_all(&path);
|
|
std::fs::create_dir_all(&path).unwrap();
|
|
path
|
|
}
|
|
|
|
#[test]
|
|
fn reindex_prints_the_counts() {
|
|
let d = dir("reindex");
|
|
let socket = d.join("admin.sock");
|
|
let seen = fake(
|
|
&socket,
|
|
Message::Reindexed(Reindexed {
|
|
logs: 42,
|
|
records: 1208,
|
|
notes: 7,
|
|
}),
|
|
);
|
|
let mut out = Vec::new();
|
|
run(&socket, &Ask::Reindex, Duration::from_secs(5), &mut out).unwrap();
|
|
assert_eq!(
|
|
String::from_utf8(out).unwrap(),
|
|
"reindexed: 42 logs, 1,208 records, 7 notes\n"
|
|
);
|
|
assert!(matches!(seen.recv().unwrap(), Message::Reindex(_)));
|
|
}
|
|
|
|
#[test]
|
|
fn an_admin_command_prints_the_reply_and_an_error_is_escaped() {
|
|
let d = dir("admin");
|
|
let socket = d.join("admin.sock");
|
|
let seen = fake(
|
|
&socket,
|
|
Message::MemoryAdminReply(MemoryAdminReply {
|
|
text: "undone: 2026-09-24-0007".to_string(),
|
|
}),
|
|
);
|
|
let mut out = Vec::new();
|
|
let ask = Ask::Admin(MemoryCommand::Undo, "2026-09-24-0007".to_string());
|
|
run(&socket, &ask, Duration::from_secs(5), &mut out).unwrap();
|
|
assert_eq!(String::from_utf8(out).unwrap(), "undone: 2026-09-24-0007\n");
|
|
match seen.recv().unwrap() {
|
|
Message::MemoryAdmin(m) => {
|
|
assert_eq!(
|
|
(m.command, m.arg.as_str()),
|
|
(MemoryCommand::Undo, "2026-09-24-0007")
|
|
)
|
|
}
|
|
other => panic!("{other:?}"),
|
|
}
|
|
let socket = d.join("refuses.sock");
|
|
let _seen = fake(
|
|
&socket,
|
|
Message::Error(WireError {
|
|
code: ErrorCode::Internal,
|
|
detail: "user.md has changed\u{1b}[31m".to_string(),
|
|
}),
|
|
);
|
|
let err = run(&socket, &ask, Duration::from_secs(5), &mut Vec::new()).unwrap_err();
|
|
assert!(err.starts_with("memoryd: user.md has changed"), "{err}");
|
|
assert!(!err.contains('\u{1b}'), "escaped: {err}");
|
|
let err = run(
|
|
&d.join("nobody.sock"),
|
|
&ask,
|
|
Duration::from_secs(1),
|
|
&mut Vec::new(),
|
|
)
|
|
.unwrap_err();
|
|
assert!(err.starts_with("cannot reach memoryd at "), "{err}");
|
|
}
|