From 89715320cbdeaeb63b83307139a6ed7f47a0eb4e Mon Sep 17 00:00:00 2001 From: Jeff Baskin Date: Mon, 11 May 2026 10:26:54 -0400 Subject: [PATCH] Got session tests working with a test clock. --- src/message.rs | 5 +- src/queue/data_director.rs | 179 +++++++++++++++++++------------------ src/queue/router.rs | 31 +++++-- 3 files changed, 119 insertions(+), 96 deletions(-) diff --git a/src/message.rs b/src/message.rs index 4810ac4..66012c9 100644 --- a/src/message.rs +++ b/src/message.rs @@ -3,6 +3,7 @@ use crate::{ name::NameType, queue::data_director::{Include, Path, Route, Session}, }; +use chrono::Utc; use uuid::Uuid; pub trait MessageAction { @@ -71,7 +72,7 @@ impl Message { F: Into, { let mut output = self.clone(); - output.session = Session::new(session); + output.session = Session::new(session, Utc::now()); output } @@ -115,7 +116,7 @@ impl Default for Message { msg_id: MessageID::new(), action: MsgAction::None, route: Route::default(), - session: Session::new(Field::None), + session: Session::default(), } } } diff --git a/src/queue/data_director.rs b/src/queue/data_director.rs index fc6364d..f19e592 100644 --- a/src/queue/data_director.rs +++ b/src/queue/data_director.rs @@ -9,6 +9,7 @@ use crate::{ use chrono::{DateTime, Utc}; use std::{ collections::{HashMap, HashSet}, + default, sync::mpsc::Receiver, thread::spawn, time::Duration, @@ -495,13 +496,13 @@ pub struct Session { impl Session { const EXPIRE_IN: Duration = Duration::from_hours(1); - pub fn new(id: F) -> Self + pub fn new(id: F, time: DateTime) -> Self where F: Into, { Self { id: id.into(), - expire_time: Utc::now() + Session::EXPIRE_IN, + expire_time: time + Self::EXPIRE_IN, } } @@ -509,12 +510,21 @@ impl Session { &self.id } - fn extend(&mut self) { - self.expire_time = Utc::now() + Session::EXPIRE_IN; + fn extend(&mut self, time: DateTime) { + self.expire_time = time + Session::EXPIRE_IN; } - fn is_expired(&self) -> bool { - Utc::now() > self.expire_time + fn is_expired(&self, time: DateTime) -> bool { + time > self.expire_time + } +} + +impl Default for Session { + fn default() -> Self { + Self { + id: Field::None, + expire_time: Utc::now(), + } } } @@ -524,67 +534,56 @@ mod session_entries { use chrono::Utc; use std::time::Duration; + #[test] + fn is_there_a_default() { + let session = Session::default(); + assert_eq!(session.id(), &Field::None); + } + #[test] fn does_entry_return_id() { let id: Field = Uuid::new_v4().into(); - let start = Utc::now() + Session::EXPIRE_IN; - let entry = Session::new(id.clone()); - let end = Utc::now() + Session::EXPIRE_IN; - assert_eq!(entry.id(), &id); - assert!( - start < entry.expire_time, - "{:?} should have been after {:?}", - entry.expire_time, - start - ); - assert!( - end > entry.expire_time, - "{:?} should have been after {:?}", - entry.expire_time, - end - ); + let time = Utc::now(); + let entry = Session::new(id.clone(), time.clone()); + assert_eq!(entry.id, id); + assert_eq!(entry.expire_time, time + Session::EXPIRE_IN); } #[test] fn can_determine_if_entry_expired() { - let mut entry = Session::new(Uuid::nil()); - let data = Utc::now(); - entry.expire_time = data + Duration::from_secs(1); - assert!(!entry.is_expired(), "entry should not be expired"); - entry.expire_time = data - Duration::from_secs(1); - assert!(entry.is_expired(), "entry should be expired"); + let id: Field = Uuid::new_v4().into(); + let time = Utc::now(); + let entry = Session::new(id.clone(), time.clone()); + assert!( + !entry.is_expired(time + Session::EXPIRE_IN), + "should not be expired" + ); + assert!( + entry.is_expired(time + Session::EXPIRE_IN + Duration::from_secs(1)), + "should be expired" + ); } #[test] fn can_expiration_be_reset() { - let mut entry = Session::new(Uuid::nil()); - entry.expire_time = Utc::now(); - let start = Utc::now() + Session::EXPIRE_IN; - entry.extend(); - let end = Utc::now() + Session::EXPIRE_IN; - assert!( - start < entry.expire_time, - "{:?} should have been after {:?}", - entry.expire_time, - start - ); - assert!( - end > entry.expire_time, - "{:?} should have been after {:?}", - entry.expire_time, - end - ); + let id: Field = Uuid::new_v4().into(); + let mut entry = Session::new(id.clone(), Utc::now()); + let time = Utc::now(); + entry.extend(time.clone()); + assert_eq!(entry.expire_time, time + Session::EXPIRE_IN); } } struct SessionStorage { entries: HashMap, + queue: Queue, } impl SessionStorage { - fn new() -> Self { + fn new(queue: Queue) -> Self { Self { entries: HashMap::new(), + queue: queue, } } @@ -599,7 +598,7 @@ impl SessionStorage { }; match self.entries.get_mut(&converted) { Some(data) => { - data.extend(); + data.extend(self.queue.now()); data.clone() } None => { @@ -607,7 +606,7 @@ impl SessionStorage { while self.entries.contains_key(&new_id) { new_id = Uuid::new_v4().into(); } - let output = Session::new(new_id.clone()); + let output = Session::new(new_id.clone(), self.queue.now()); self.entries.insert(new_id, output.clone()); output } @@ -616,8 +615,9 @@ impl SessionStorage { fn expire(&mut self) { let mut remove: Vec = Vec::new(); + let time = self.queue.now(); for (id, session) in self.entries.iter() { - if session.is_expired() { + if session.is_expired(time) { remove.push(id.clone()); } } @@ -630,15 +630,22 @@ impl SessionStorage { #[cfg(test)] mod session_storage { use super::*; - use crate::FieldType; + use crate::{ + queue::{self, TestClock}, + FieldType, + }; #[test] fn are_session_ids_unique() { let count = 10; let mut ids: Vec = Vec::new(); - let mut sess = SessionStorage::new(); + let clock = TestClock::new(); + let queue = Queue::with_clock(clock.clone()); + let mut sess = SessionStorage::new(queue.clone()); + let expire_time = queue.now() + Session::EXPIRE_IN; for _ in 0..count { let result = sess.get(&Field::None); + assert_eq!(result.expire_time, expire_time); let id = result.id().clone(); let id_type: FieldType = (&id).into(); assert_eq!(id_type, FieldType::Uuid); @@ -649,30 +656,20 @@ mod session_storage { #[test] fn are_valid_uuids_returned() { - let mut sess = SessionStorage::new(); + let clock = TestClock::new(); + let queue = Queue::with_clock(clock.clone()); + let mut sess = SessionStorage::new(queue.clone()); let data = sess.get(&Field::None); - let start = Utc::now() + Session::EXPIRE_IN; - let result = sess.get(data.id()); - let end = Utc::now() + Session::EXPIRE_IN; - assert_eq!(result.id(), data.id()); - let entry = sess.entries.get(data.id()).unwrap(); - assert!( - start < entry.expire_time, - "{:?} should have been after {:?}", - entry.expire_time, - start - ); - assert!( - end > entry.expire_time, - "{:?} should have been after {:?}", - entry.expire_time, - end - ); + clock.advance(Duration::from_secs(5)); + let time = queue.now(); + let entry = sess.get(data.id()); + assert_eq!(entry.id(), data.id()); + assert_eq!(entry.expire_time, time + Session::EXPIRE_IN); } #[test] fn do_bad_ids_generate_new_ids() { - let mut sess = SessionStorage::new(); + let mut sess = SessionStorage::new(Queue::new()); let data: Field = Uuid::nil().into(); let result = sess.get(&data); assert_ne!(result.id(), &data); @@ -680,7 +677,7 @@ mod session_storage { #[test] fn can_string_ids_be_accepted() { - let mut sess = SessionStorage::new(); + let mut sess = SessionStorage::new(Queue::new()); let id = sess.get(&Field::None).id().clone(); let text: Field = match id { Field::Uuid(id) => id.to_string().into(), @@ -692,7 +689,7 @@ mod session_storage { #[test] fn does_mismatched_string_produce_new_id() { - let mut sess = SessionStorage::new(); + let mut sess = SessionStorage::new(Queue::new()); let input = Uuid::nil(); let id: Field = input.to_string().into(); let test_data: Field = input.into(); @@ -702,7 +699,7 @@ mod session_storage { #[test] fn does_bad_string_produce_id() { - let mut sess = SessionStorage::new(); + let mut sess = SessionStorage::new(Queue::new()); let id: Field = "not a uuid".into(); let result = sess.get(&id); match result.id() { @@ -713,7 +710,7 @@ mod session_storage { #[test] fn do_other_fields_return_uuid() { - let mut sess = SessionStorage::new(); + let mut sess = SessionStorage::new(Queue::new()); let id: Field = 2.into(); let result = sess.get(&id); match result.id() { @@ -724,23 +721,29 @@ mod session_storage { #[test] fn are_expired_sessions_removed() { - let mut sess = SessionStorage::new(); - let lose = sess.get(&Field::None); - sess.entries.get_mut(&lose.id).unwrap().expire_time = Utc::now() - Session::EXPIRE_IN; - let keep = sess.get(&Field::None); + let clock = TestClock::new(); + let queue = Queue::with_clock(clock.clone()); + let mut sess = SessionStorage::new(queue.clone()); + let data = sess.get(&Field::None); + assert_eq!(sess.entries.len(), 1, "should have one entry"); + clock.advance(Session::EXPIRE_IN); sess.expire(); assert_eq!( sess.entries.len(), 1, - "should only contain one entry, had {:?}", - sess.entries + "entry should not have expired: expire: {:?}, time: {:?}", + data.expire_time, + queue.now() + ); + clock.advance(Duration::from_nanos(1)); + sess.expire(); + assert_eq!( + sess.entries.len(), + 0, + "entry should have expired: expire: {:?}, time: {:?}", + data.expire_time, + queue.now() ); - assert!( - sess.entries.contains_key(&keep.id), - "had {:?}, expected {:?}", - sess.entries.keys(), - &keep.id - ) } } @@ -756,10 +759,10 @@ impl DocRegistry { fn new(queue: Queue, rx: Receiver) -> Self { Self { doc_names: Names::new(), - queue: queue, + queue: queue.clone(), receiver: rx, routes: RouteStorage::new(), - sessions: SessionStorage::new(), + sessions: SessionStorage::new(queue), } } diff --git a/src/queue/router.rs b/src/queue/router.rs index 89ca50f..c1adfd3 100644 --- a/src/queue/router.rs +++ b/src/queue/router.rs @@ -1,6 +1,7 @@ use crate::{ message::Message, queue::data_director::{DocRegistry, RegMsg, Register}, + MTTError, }; use chrono::{DateTime, Utc}; use std::{ @@ -107,28 +108,46 @@ impl SystemClock { impl Now for SystemClock {} +struct TimeData { + time: DateTime, +} + +impl TimeData { + fn new() -> Self { + Self { time: Utc::now() } + } + + fn advance(&mut self, duration: Duration) { + self.time += duration; + } + + fn time(&self) -> DateTime { + self.time.clone() + } +} + #[derive(Clone)] pub struct TestClock { - time: Arc>>, + data: Arc>, } impl TestClock { pub fn new() -> Self { Self { - time: Arc::new(RwLock::new(Utc::now())), + data: Arc::new(RwLock::new(TimeData::new())), } } pub fn advance(&self, duration: Duration) { - let mut current = self.time.write().unwrap(); - *current += duration; + let mut current = self.data.write().unwrap(); + current.advance(duration); } } impl Now for TestClock { fn now(&self) -> DateTime { - let current = self.time.read().unwrap(); - current.clone() + let current = self.data.read().unwrap(); + current.time() } }