wabot-testing 0.1.0

Test harnesses for Wabot: a scriptable LLM adapter plus chat-bot and agent harnesses that drive the real production paths.
Documentation
//! An in-RAM chat memory that a test can read back. Port of
//! `wabot-ts/src/testing/TestChatMemory.ts`.

use async_trait::async_trait;
use parking_lot::Mutex;
use wabot_feature_chat_bot::{ChatItem, ChatMemory};

/// Like `InMemoryChatMemory`, but it exposes the whole item list.
///
/// The production one only offers `find_last_items`, which is all the
/// bot needs and not enough for a test that wants to assert on what a
/// turn recorded.
#[derive(Debug, Default)]
pub struct TestChatMemory {
    items: Mutex<Vec<ChatItem>>,
}

impl TestChatMemory {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn all(&self) -> Vec<ChatItem> {
        self.items.lock().clone()
    }

    /// Items recorded from `index` onwards — how a harness isolates
    /// one turn from the history before it.
    pub fn items_from(&self, index: usize) -> Vec<ChatItem> {
        let items = self.items.lock();
        items
            .get(index..)
            .map(<[ChatItem]>::to_vec)
            .unwrap_or_default()
    }

    pub fn len(&self) -> usize {
        self.items.lock().len()
    }

    pub fn is_empty(&self) -> bool {
        self.items.lock().is_empty()
    }

    pub fn clear(&self) {
        self.items.lock().clear();
    }
}

#[async_trait]
impl ChatMemory for TestChatMemory {
    async fn find_last_items(&self, n: usize) -> Vec<ChatItem> {
        let items = self.items.lock();
        let start = items.len().saturating_sub(n);
        items[start..].to_vec()
    }

    async fn create(&self, item: ChatItem) {
        self.items.lock().push(item);
    }
}