1pub mod conformance;
8
9use std::sync::Mutex;
10use std::time::Duration;
11
12use jiff::Timestamp;
13use okbot_core::{Clock, ConversationKey, MessageRole, NewMessage, UserId};
14
15#[derive(Debug)]
17pub struct ManualClock(Mutex<Timestamp>);
18
19impl Default for ManualClock {
20 fn default() -> Self {
21 Self(Mutex::new(
22 "2026-01-01T00:00:00Z"
23 .parse()
24 .expect("valid timestamp literal"),
25 ))
26 }
27}
28
29impl ManualClock {
30 pub fn advance(&self, by: Duration) {
32 let mut now = self
33 .0
34 .lock()
35 .unwrap_or_else(std::sync::PoisonError::into_inner);
36 *now = now
37 .checked_add(jiff::SignedDuration::try_from(by).expect("duration fits"))
38 .expect("timestamp in range");
39 }
40}
41
42impl Clock for ManualClock {
43 fn now(&self) -> Timestamp {
44 *self
45 .0
46 .lock()
47 .unwrap_or_else(std::sync::PoisonError::into_inner)
48 }
49}
50
51pub fn conversation(channel: &str, room: &str) -> ConversationKey {
53 ConversationKey::room(channel.parse().expect("valid channel id"), room)
54}
55
56pub fn user_message(key: &ConversationKey, text: &str, at: Timestamp) -> NewMessage {
58 NewMessage {
59 conversation: key.clone(),
60 role: MessageRole::User,
61 author_id: Some(UserId::new("u1")),
62 author_name: "Ana".into(),
63 text: text.into(),
64 message_id: None,
65 at,
66 }
67}
68
69pub fn bot_message(key: &ConversationKey, text: &str, at: Timestamp) -> NewMessage {
71 NewMessage {
72 conversation: key.clone(),
73 role: MessageRole::Bot,
74 author_id: None,
75 author_name: "okbot".into(),
76 text: text.into(),
77 message_id: None,
78 at,
79 }
80}
81
82mod fakes;
83mod memory_store;
84mod memory_tasks;
85pub use fakes::{CollectSink, FakeAgent, FakeChannel, PlainRenderer, fake_cli, inbound, turn};
86pub use memory_store::MemoryStore;
87pub use memory_tasks::MemoryTasks;