diff --git a/src/lib.rs b/src/lib.rs index b6a1dca..01b8146 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -404,6 +404,43 @@ mod tests { assert_eq!(plan.status, "planned"); } + + #[test] + fn session_idempotency_is_scoped_per_tenant_user_session() { + let mut store = TestMemoryStore::new(); + + append_session_message( + &mut store, + AppendSessionMessageRequest { + tenant_id: "tenant_1".to_string(), + user_id: "usr_1".to_string(), + session_id: "sess_1".to_string(), + role: "user".to_string(), + content: "first".to_string(), + idempotency_key: Some("same-key".to_string()), + }, + 20, + ) + .unwrap(); + + let appended = append_session_message( + &mut store, + AppendSessionMessageRequest { + tenant_id: "tenant_2".to_string(), + user_id: "usr_2".to_string(), + session_id: "sess_2".to_string(), + role: "user".to_string(), + content: "second".to_string(), + idempotency_key: Some("same-key".to_string()), + }, + 20, + ) + .unwrap(); + + assert_eq!(appended.hot_tail_count, 1); + assert_eq!(store.session_messages.len(), 2); + } + #[test] fn retrieval_telemetry_contains_contract_fields() { let mut store = TestMemoryStore::new(); diff --git a/src/testkit/mod.rs b/src/testkit/mod.rs index 85eb963..c5d94c4 100644 --- a/src/testkit/mod.rs +++ b/src/testkit/mod.rs @@ -213,11 +213,12 @@ impl AuditSink for TestMemoryStore { impl SessionStore for TestMemoryStore { fn append_session_message(&mut self, message: SessionMessage) -> CoreResult<()> { - if self - .session_messages - .iter() - .any(|existing| existing.idempotency_key == message.idempotency_key) - { + if self.session_messages.iter().any(|existing| { + existing.tenant_id == message.tenant_id + && existing.user_id == message.user_id + && existing.session_id == message.session_id + && existing.idempotency_key == message.idempotency_key + }) { return Ok(()); } self.session_messages.push(message);