Files
boxmaker/docs/plans/M4a/files/crates/gatewayd/tests/sessions.rs
T
kyleandClaude Opus 5.5 0339dc13b2 Plan M4a: gatewayd in 15 tasks, with skeletons and given tests
Each task's tests were run against a reference at its end state; the end states were replayed
from master in order with the gate at each step (650 to 762 tests); each skeleton compiles
against its tests and fails them. The reference is kept off this machine. Lessons T27 (every
wait in a test has a limit) and T28 (mutate the reference before hand-over) come from this work.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-23 19:05:44 -07:00

299 lines
8.8 KiB
Rust

//! Which posts become turns, in which session, and how messages wait for a running turn (M4a spec,
//! section 7, including its table of examples). Do not edit.
use gatewayd::mm::Post;
use gatewayd::sessions::{
BUSY, Batch, Ignored, M4B_COMMAND, Message, Pushed, Queues, Route, Router, Thread,
UNKNOWN_COMMAND, named,
};
use proto::SessionId;
const BOT: &str = "b0000000000000000000000000";
const KYLE: &str = "k0000000000000000000000000";
const EVE: &str = "e0000000000000000000000000";
const DM: &str = "d0000000000000000000000000";
const SHARED: &str = "c0000000000000000000000000";
const OTHER: &str = "o0000000000000000000000000";
const ROOT: &str = "r0000000000000000000000000";
const POST: &str = "p0000000000000000000000000";
fn router() -> Router {
Router::new(
BOT,
"boxmaker-straylight",
&[KYLE.to_string()],
&[SHARED.to_string()],
)
}
fn post(channel: &str, root: &str, message: &str) -> Post {
Post {
id: POST.to_string(),
user_id: KYLE.to_string(),
channel_id: channel.to_string(),
root_id: root.to_string(),
message: message.to_string(),
create_at: 5,
delete_at: 0,
kind: String::new(),
}
}
fn known(root: &str) -> bool {
root == ROOT
}
fn unknown(_: &str) -> bool {
false
}
fn queued(route: Route) -> Message {
match route {
Route::Queue(m) => m,
other => panic!("not queued: {other:?}"),
}
}
#[test]
fn the_examples_in_the_spec() {
let r = router();
let cases = [
("", "@boxmaker-straylight summarise the audit log", true),
(ROOT, "and the older files?", true),
(ROOT, "@hermes what do you think?", false),
("", "@boxmaker-straylight @hermes compare notes", true),
("", "@boxmaker-straylightx hello", false),
("", "@channel standup in five", false),
];
for (root, message, yes) in cases {
let got = r.route(&post(SHARED, root, message), "O", &known);
assert_eq!(matches!(got, Route::Queue(_)), yes, "{message}: {got:?}");
if !yes {
assert_eq!(got, Route::Ignore(Ignored::NotForUs), "{message}");
}
}
}
#[test]
fn naming() {
assert_eq!(named("@Boxmaker-Straylight, look"), ["boxmaker-straylight"]);
assert_eq!(named("ask @boxmaker-straylight."), ["boxmaker-straylight"]);
assert_eq!(named("@a.b_c-d... and @e"), ["a.b_c-d", "e"]);
assert_eq!(named("@ alone, @@x, trailing @"), ["x"]);
assert_eq!(named("ünïcødé @ʙob @bob"), ["bob"]);
assert!(named("no names here").is_empty());
let r = router();
for message in [
"hi @BOXMAKER-STRAYLIGHT",
"@boxmaker-straylight.",
"(@boxmaker-straylight)",
] {
assert!(
matches!(
r.route(&post(SHARED, "", message), "P", &unknown),
Route::Queue(_)
),
"{message}"
);
}
}
#[test]
fn replies_in_our_thread_that_name_everyone_are_still_ours() {
let r = router();
for message in ["@here any news?", "@all done", "thanks @channel"] {
assert!(
matches!(
r.route(&post(SHARED, ROOT, message), "O", &known),
Route::Queue(_)
),
"{message}"
);
}
let got = r.route(
&post(
SHARED,
"q0000000000000000000000000",
"a reply in someone else's thread",
),
"O",
&known,
);
assert_eq!(got, Route::Ignore(Ignored::NotForUs));
}
#[test]
fn who_and_where() {
let r = router();
let mut own = post(DM, "", "hello");
own.user_id = BOT.to_string();
assert_eq!(r.route(&own, "D", &unknown), Route::Ignore(Ignored::Own));
let mut system = post(DM, "", "joined");
system.kind = "system_join_channel".to_string();
assert_eq!(
r.route(&system, "D", &unknown),
Route::Ignore(Ignored::System)
);
let mut stranger = post(DM, "", "@boxmaker-straylight hello");
stranger.user_id = EVE.to_string();
assert_eq!(
r.route(&stranger, "D", &unknown),
Route::Ignore(Ignored::NotAllowed)
);
let mut stranger_cmd = post(DM, "", "!approve 1");
stranger_cmd.user_id = EVE.to_string();
assert_eq!(
r.route(&stranger_cmd, "D", &unknown),
Route::Ignore(Ignored::NotAllowed)
);
let naming = "@boxmaker-straylight hello";
assert_eq!(
r.route(&post(OTHER, "", naming), "O", &unknown),
Route::Ignore(Ignored::NotForUs)
);
assert_eq!(
r.route(&post(SHARED, "", naming), "X", &unknown),
Route::Ignore(Ignored::NotForUs)
);
assert!(matches!(
r.route(&post(SHARED, "", naming), "G", &unknown),
Route::Queue(_)
));
assert!(matches!(
r.route(&post(DM, "", "no name needed"), "D", &unknown),
Route::Queue(_)
));
}
#[test]
fn sessions_and_threads() {
let r = router();
let top = queued(r.route(&post(DM, "", "hello"), "D", &unknown));
assert_eq!(top.session.as_str(), format!("mm-{POST}"));
assert_eq!(
top.thread,
Thread {
channel: DM.to_string(),
root: POST.to_string()
}
);
assert!(!top.resume && !top.joins_thread);
assert_eq!(top.text, "hello");
let reply = queued(r.route(&post(DM, ROOT, "more"), "D", &unknown));
assert_eq!(reply.session.as_str(), format!("mm-{ROOT}"));
assert_eq!(reply.thread.root, ROOT);
assert!(reply.resume);
let channel = queued(r.route(&post(SHARED, "", "@boxmaker-straylight hi"), "O", &unknown));
assert!(channel.joins_thread && !channel.resume);
assert_eq!(channel.text, "@boxmaker-straylight hi");
}
#[test]
fn commands() {
let r = router();
let thread = Thread {
channel: DM.to_string(),
root: ROOT.to_string(),
};
let reply = |text: &str| Route::Reply {
thread: thread.clone(),
text: text.to_string(),
};
for (message, answer) in [
("!approve 42", M4B_COMMAND),
("!deny 42 not now", M4B_COMMAND),
("!deny", M4B_COMMAND),
("!approved", UNKNOWN_COMMAND),
("!help", UNKNOWN_COMMAND),
("!", UNKNOWN_COMMAND),
] {
assert_eq!(
r.route(&post(DM, ROOT, message), "D", &unknown),
reply(answer),
"{message}"
);
}
assert_eq!(
queued(r.route(&post(DM, ROOT, "!!approve is a word"), "D", &unknown)).text,
"!approve is a word"
);
assert_eq!(
queued(r.route(&post(DM, ROOT, "!!"), "D", &unknown)).text,
"!"
);
assert_eq!(
queued(r.route(&post(DM, ROOT, " !help"), "D", &unknown)).text,
" !help"
);
}
fn message(root: &str, resume: bool, text: &str) -> Message {
Message {
session: SessionId::new(&format!("mm-{root}")).unwrap(),
thread: Thread {
channel: DM.to_string(),
root: root.to_string(),
},
resume,
text: text.to_string(),
joins_thread: false,
}
}
#[test]
fn messages_wait_for_the_running_turn_and_go_together() {
let mut q = Queues::new(3);
let first = message(ROOT, false, "one");
let Pushed::Start(batch) = q.push(first.clone()) else {
panic!("not started")
};
assert_eq!(
batch,
Batch {
session: first.session.clone(),
thread: first.thread.clone(),
resume: false,
text: "one".to_string()
}
);
assert_eq!(q.push(message(ROOT, true, "two")), Pushed::Waiting);
assert_eq!(q.push(message(ROOT, true, "three\nlines")), Pushed::Waiting);
let other = message(POST, false, "elsewhere");
assert!(
matches!(q.push(other.clone()), Pushed::Start(_)),
"another session starts at once"
);
assert_eq!(q.running(), 2);
let mut roots: Vec<String> = q.threads().into_iter().map(|t| t.root).collect();
roots.sort();
assert_eq!(roots, [POST, ROOT]);
let next = q.finish(&first.session).unwrap();
assert_eq!(
(next.text.as_str(), next.resume),
("two\n\nthree\nlines", true)
);
assert_eq!(q.finish(&first.session), None);
assert_eq!(q.finish(&other.session), None);
assert_eq!(q.running(), 0);
assert!(
matches!(q.push(message(ROOT, true, "later")), Pushed::Start(_)),
"idle again"
);
}
#[test]
fn a_full_queue_drops_the_message() {
let mut q = Queues::new(2);
let m = message(ROOT, false, "run");
assert!(matches!(q.push(m.clone()), Pushed::Start(_)));
assert_eq!(q.push(message(ROOT, true, "a")), Pushed::Waiting);
assert_eq!(q.push(message(ROOT, true, "b")), Pushed::Waiting);
assert_eq!(
q.push(message(ROOT, true, "c")),
Pushed::Full(m.thread.clone())
);
assert_eq!(q.finish(&m.session).unwrap().text, "a\n\nb");
assert!(!BUSY.is_empty());
assert_eq!(q.finish(&SessionId::new("mm-never").unwrap()), None);
}