turnframe_store/
replay.rs1use async_trait::async_trait;
11use turnframe_core::ids::{AccountId, ConversationId, TurnId};
12use turnframe_core::replay::ReplayRecord;
13
14use crate::error::StoreError;
15
16#[async_trait]
18pub trait ReplayReader: Send + Sync {
19 async fn get(&self, account: &AccountId, turn_id: &TurnId) -> Result<ReplayRecord, StoreError>;
24
25 async fn list_for_conversation(
31 &self,
32 account: &AccountId,
33 conversation: &ConversationId,
34 limit: usize,
35 ) -> Result<Vec<ReplayRecord>, StoreError>;
36}
37
38#[async_trait]
40pub trait ReplayWriter: Send + Sync {
41 async fn put(&self, record: ReplayRecord) -> Result<(), StoreError>;
46}
47
48pub trait ReplayStore: ReplayReader + ReplayWriter {}
53
54impl<T: ReplayReader + ReplayWriter + ?Sized> ReplayStore for T {}
55
56#[cfg(test)]
57mod tests {
58 use chrono::{DateTime, TimeDelta, Utc};
59 use turnframe_core::ids::EventId;
60 use turnframe_core::replay::TurnPhase;
61
62 use super::*;
63 use crate::memory::MemoryStores;
64
65 fn record(
66 account: &AccountId,
67 conversation: ConversationId,
68 turn: TurnId,
69 at: DateTime<Utc>,
70 ) -> ReplayRecord {
71 ReplayRecord::received(turn, conversation, account.clone(), at)
72 }
73
74 #[tokio::test]
75 async fn put_replaces_and_get_is_account_scoped() {
76 let store = MemoryStores::new();
77 let account = AccountId::from("a");
78 let conversation = ConversationId::new();
79 let turn = TurnId::new();
80 let epoch = DateTime::<Utc>::UNIX_EPOCH;
81
82 store
83 .put(record(&account, conversation, turn, epoch))
84 .await
85 .unwrap();
86
87 let mut advanced = record(&account, conversation, turn, epoch + TimeDelta::seconds(1));
90 advanced.phase = TurnPhase::Composed;
91 advanced.event_ids = vec![EventId::new()];
92 store.put(advanced.clone()).await.unwrap();
93
94 assert_eq!(
95 ReplayReader::get(&store, &account, &turn).await.unwrap(),
96 advanced
97 );
98 assert_eq!(
99 store
100 .list_for_conversation(&account, &conversation, 10)
101 .await
102 .unwrap(),
103 vec![advanced]
104 );
105 assert_eq!(
106 ReplayReader::get(&store, &AccountId::from("b"), &turn).await,
107 Err(StoreError::NotFound)
108 );
109 assert!(
110 store
111 .list_for_conversation(&AccountId::from("b"), &conversation, 10)
112 .await
113 .unwrap()
114 .is_empty()
115 );
116 }
117
118 #[tokio::test]
119 async fn list_returns_the_most_recent_records_chronologically() {
120 let store = MemoryStores::new();
121 let account = AccountId::from("a");
122 let conversation = ConversationId::new();
123 let epoch = DateTime::<Utc>::UNIX_EPOCH;
124 let mut turns = Vec::new();
125 for seconds in 0..4 {
126 let turn = TurnId::new();
127 turns.push(turn);
128 store
129 .put(record(
130 &account,
131 conversation,
132 turn,
133 epoch + TimeDelta::seconds(seconds),
134 ))
135 .await
136 .unwrap();
137 }
138 let recent = store
139 .list_for_conversation(&account, &conversation, 2)
140 .await
141 .unwrap();
142 assert_eq!(
143 recent
144 .iter()
145 .map(|record| record.turn_id)
146 .collect::<Vec<_>>(),
147 vec![turns[2], turns[3]]
148 );
149 }
150}