Got session tests working with a test clock.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled

This commit is contained in:
2026-05-11 10:26:54 -04:00
parent d5dd36850e
commit 89715320cb
3 changed files with 119 additions and 96 deletions

View File

@@ -3,6 +3,7 @@ use crate::{
name::NameType, name::NameType,
queue::data_director::{Include, Path, Route, Session}, queue::data_director::{Include, Path, Route, Session},
}; };
use chrono::Utc;
use uuid::Uuid; use uuid::Uuid;
pub trait MessageAction { pub trait MessageAction {
@@ -71,7 +72,7 @@ impl Message {
F: Into<Field>, F: Into<Field>,
{ {
let mut output = self.clone(); let mut output = self.clone();
output.session = Session::new(session); output.session = Session::new(session, Utc::now());
output output
} }
@@ -115,7 +116,7 @@ impl Default for Message {
msg_id: MessageID::new(), msg_id: MessageID::new(),
action: MsgAction::None, action: MsgAction::None,
route: Route::default(), route: Route::default(),
session: Session::new(Field::None), session: Session::default(),
} }
} }
} }

View File

@@ -9,6 +9,7 @@ use crate::{
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use std::{ use std::{
collections::{HashMap, HashSet}, collections::{HashMap, HashSet},
default,
sync::mpsc::Receiver, sync::mpsc::Receiver,
thread::spawn, thread::spawn,
time::Duration, time::Duration,
@@ -495,13 +496,13 @@ pub struct Session {
impl Session { impl Session {
const EXPIRE_IN: Duration = Duration::from_hours(1); const EXPIRE_IN: Duration = Duration::from_hours(1);
pub fn new<F>(id: F) -> Self pub fn new<F>(id: F, time: DateTime<Utc>) -> Self
where where
F: Into<Field>, F: Into<Field>,
{ {
Self { Self {
id: id.into(), id: id.into(),
expire_time: Utc::now() + Session::EXPIRE_IN, expire_time: time + Self::EXPIRE_IN,
} }
} }
@@ -509,12 +510,21 @@ impl Session {
&self.id &self.id
} }
fn extend(&mut self) { fn extend(&mut self, time: DateTime<Utc>) {
self.expire_time = Utc::now() + Session::EXPIRE_IN; self.expire_time = time + Session::EXPIRE_IN;
} }
fn is_expired(&self) -> bool { fn is_expired(&self, time: DateTime<Utc>) -> bool {
Utc::now() > self.expire_time 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 chrono::Utc;
use std::time::Duration; use std::time::Duration;
#[test]
fn is_there_a_default() {
let session = Session::default();
assert_eq!(session.id(), &Field::None);
}
#[test] #[test]
fn does_entry_return_id() { fn does_entry_return_id() {
let id: Field = Uuid::new_v4().into(); let id: Field = Uuid::new_v4().into();
let start = Utc::now() + Session::EXPIRE_IN; let time = Utc::now();
let entry = Session::new(id.clone()); let entry = Session::new(id.clone(), time.clone());
let end = Utc::now() + Session::EXPIRE_IN; assert_eq!(entry.id, id);
assert_eq!(entry.id(), &id); assert_eq!(entry.expire_time, time + 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
);
} }
#[test] #[test]
fn can_determine_if_entry_expired() { fn can_determine_if_entry_expired() {
let mut entry = Session::new(Uuid::nil()); let id: Field = Uuid::new_v4().into();
let data = Utc::now(); let time = Utc::now();
entry.expire_time = data + Duration::from_secs(1); let entry = Session::new(id.clone(), time.clone());
assert!(!entry.is_expired(), "entry should not be expired"); assert!(
entry.expire_time = data - Duration::from_secs(1); !entry.is_expired(time + Session::EXPIRE_IN),
assert!(entry.is_expired(), "entry should be expired"); "should not be expired"
);
assert!(
entry.is_expired(time + Session::EXPIRE_IN + Duration::from_secs(1)),
"should be expired"
);
} }
#[test] #[test]
fn can_expiration_be_reset() { fn can_expiration_be_reset() {
let mut entry = Session::new(Uuid::nil()); let id: Field = Uuid::new_v4().into();
entry.expire_time = Utc::now(); let mut entry = Session::new(id.clone(), Utc::now());
let start = Utc::now() + Session::EXPIRE_IN; let time = Utc::now();
entry.extend(); entry.extend(time.clone());
let end = Utc::now() + Session::EXPIRE_IN; assert_eq!(entry.expire_time, time + 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
);
} }
} }
struct SessionStorage { struct SessionStorage {
entries: HashMap<Field, Session>, entries: HashMap<Field, Session>,
queue: Queue,
} }
impl SessionStorage { impl SessionStorage {
fn new() -> Self { fn new(queue: Queue) -> Self {
Self { Self {
entries: HashMap::new(), entries: HashMap::new(),
queue: queue,
} }
} }
@@ -599,7 +598,7 @@ impl SessionStorage {
}; };
match self.entries.get_mut(&converted) { match self.entries.get_mut(&converted) {
Some(data) => { Some(data) => {
data.extend(); data.extend(self.queue.now());
data.clone() data.clone()
} }
None => { None => {
@@ -607,7 +606,7 @@ impl SessionStorage {
while self.entries.contains_key(&new_id) { while self.entries.contains_key(&new_id) {
new_id = Uuid::new_v4().into(); 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()); self.entries.insert(new_id, output.clone());
output output
} }
@@ -616,8 +615,9 @@ impl SessionStorage {
fn expire(&mut self) { fn expire(&mut self) {
let mut remove: Vec<Field> = Vec::new(); let mut remove: Vec<Field> = Vec::new();
let time = self.queue.now();
for (id, session) in self.entries.iter() { for (id, session) in self.entries.iter() {
if session.is_expired() { if session.is_expired(time) {
remove.push(id.clone()); remove.push(id.clone());
} }
} }
@@ -630,15 +630,22 @@ impl SessionStorage {
#[cfg(test)] #[cfg(test)]
mod session_storage { mod session_storage {
use super::*; use super::*;
use crate::FieldType; use crate::{
queue::{self, TestClock},
FieldType,
};
#[test] #[test]
fn are_session_ids_unique() { fn are_session_ids_unique() {
let count = 10; let count = 10;
let mut ids: Vec<Field> = Vec::new(); let mut ids: Vec<Field> = 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 { for _ in 0..count {
let result = sess.get(&Field::None); let result = sess.get(&Field::None);
assert_eq!(result.expire_time, expire_time);
let id = result.id().clone(); let id = result.id().clone();
let id_type: FieldType = (&id).into(); let id_type: FieldType = (&id).into();
assert_eq!(id_type, FieldType::Uuid); assert_eq!(id_type, FieldType::Uuid);
@@ -649,30 +656,20 @@ mod session_storage {
#[test] #[test]
fn are_valid_uuids_returned() { 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 data = sess.get(&Field::None);
let start = Utc::now() + Session::EXPIRE_IN; clock.advance(Duration::from_secs(5));
let result = sess.get(data.id()); let time = queue.now();
let end = Utc::now() + Session::EXPIRE_IN; let entry = sess.get(data.id());
assert_eq!(result.id(), data.id()); assert_eq!(entry.id(), data.id());
let entry = sess.entries.get(data.id()).unwrap(); assert_eq!(entry.expire_time, time + 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
);
} }
#[test] #[test]
fn do_bad_ids_generate_new_ids() { 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 data: Field = Uuid::nil().into();
let result = sess.get(&data); let result = sess.get(&data);
assert_ne!(result.id(), &data); assert_ne!(result.id(), &data);
@@ -680,7 +677,7 @@ mod session_storage {
#[test] #[test]
fn can_string_ids_be_accepted() { 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 id = sess.get(&Field::None).id().clone();
let text: Field = match id { let text: Field = match id {
Field::Uuid(id) => id.to_string().into(), Field::Uuid(id) => id.to_string().into(),
@@ -692,7 +689,7 @@ mod session_storage {
#[test] #[test]
fn does_mismatched_string_produce_new_id() { fn does_mismatched_string_produce_new_id() {
let mut sess = SessionStorage::new(); let mut sess = SessionStorage::new(Queue::new());
let input = Uuid::nil(); let input = Uuid::nil();
let id: Field = input.to_string().into(); let id: Field = input.to_string().into();
let test_data: Field = input.into(); let test_data: Field = input.into();
@@ -702,7 +699,7 @@ mod session_storage {
#[test] #[test]
fn does_bad_string_produce_id() { 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 id: Field = "not a uuid".into();
let result = sess.get(&id); let result = sess.get(&id);
match result.id() { match result.id() {
@@ -713,7 +710,7 @@ mod session_storage {
#[test] #[test]
fn do_other_fields_return_uuid() { fn do_other_fields_return_uuid() {
let mut sess = SessionStorage::new(); let mut sess = SessionStorage::new(Queue::new());
let id: Field = 2.into(); let id: Field = 2.into();
let result = sess.get(&id); let result = sess.get(&id);
match result.id() { match result.id() {
@@ -724,23 +721,29 @@ mod session_storage {
#[test] #[test]
fn are_expired_sessions_removed() { fn are_expired_sessions_removed() {
let mut sess = SessionStorage::new(); let clock = TestClock::new();
let lose = sess.get(&Field::None); let queue = Queue::with_clock(clock.clone());
sess.entries.get_mut(&lose.id).unwrap().expire_time = Utc::now() - Session::EXPIRE_IN; let mut sess = SessionStorage::new(queue.clone());
let keep = sess.get(&Field::None); let data = sess.get(&Field::None);
assert_eq!(sess.entries.len(), 1, "should have one entry");
clock.advance(Session::EXPIRE_IN);
sess.expire(); sess.expire();
assert_eq!( assert_eq!(
sess.entries.len(), sess.entries.len(),
1, 1,
"should only contain one entry, had {:?}", "entry should not have expired: expire: {:?}, time: {:?}",
sess.entries 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<Message>) -> Self { fn new(queue: Queue, rx: Receiver<Message>) -> Self {
Self { Self {
doc_names: Names::new(), doc_names: Names::new(),
queue: queue, queue: queue.clone(),
receiver: rx, receiver: rx,
routes: RouteStorage::new(), routes: RouteStorage::new(),
sessions: SessionStorage::new(), sessions: SessionStorage::new(queue),
} }
} }

View File

@@ -1,6 +1,7 @@
use crate::{ use crate::{
message::Message, message::Message,
queue::data_director::{DocRegistry, RegMsg, Register}, queue::data_director::{DocRegistry, RegMsg, Register},
MTTError,
}; };
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use std::{ use std::{
@@ -107,28 +108,46 @@ impl SystemClock {
impl Now for SystemClock {} impl Now for SystemClock {}
struct TimeData {
time: DateTime<Utc>,
}
impl TimeData {
fn new() -> Self {
Self { time: Utc::now() }
}
fn advance(&mut self, duration: Duration) {
self.time += duration;
}
fn time(&self) -> DateTime<Utc> {
self.time.clone()
}
}
#[derive(Clone)] #[derive(Clone)]
pub struct TestClock { pub struct TestClock {
time: Arc<RwLock<DateTime<Utc>>>, data: Arc<RwLock<TimeData>>,
} }
impl TestClock { impl TestClock {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
time: Arc::new(RwLock::new(Utc::now())), data: Arc::new(RwLock::new(TimeData::new())),
} }
} }
pub fn advance(&self, duration: Duration) { pub fn advance(&self, duration: Duration) {
let mut current = self.time.write().unwrap(); let mut current = self.data.write().unwrap();
*current += duration; current.advance(duration);
} }
} }
impl Now for TestClock { impl Now for TestClock {
fn now(&self) -> DateTime<Utc> { fn now(&self) -> DateTime<Utc> {
let current = self.time.read().unwrap(); let current = self.data.read().unwrap();
current.clone() current.time()
} }
} }