Got session tests working with a test clock.
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
Some checks failed
Gitea Actions Demo / Explore-Gitea-Actions (push) Has been cancelled
This commit is contained in:
@@ -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(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user