Skip to main content

okbot_testkit/
lib.rs

1//! Test doubles and fixtures for okbot crates and third-party adapters.
2//!
3//! - [`ManualClock`]: a clock tests move by hand.
4//! - [`conformance`]: behaviour every implementation of a port must show.
5//! - Small builders for common values ([`conversation`], [`user_message`]).
6
7pub 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/// A clock that only moves when told to. Starts at 2026-01-01T00:00:00Z.
16#[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    /// Moves the clock forward.
31    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
51/// A conversation key for `channel` (`platform:instance`) and `room`.
52pub fn conversation(channel: &str, room: &str) -> ConversationKey {
53    ConversationKey::room(channel.parse().expect("valid channel id"), room)
54}
55
56/// A user message in `key`, written by user `u1` ("Ana").
57pub 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
69/// A bot message in `key`.
70pub 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;