172
crates/application/tests/cycle/cycle_test.rs
Normal file
172
crates/application/tests/cycle/cycle_test.rs
Normal file
@@ -0,0 +1,172 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use domain::entry::Date;
|
||||
use domain::ports::UserCommandPort;
|
||||
use domain::testing::{InMemoryStore, test_user};
|
||||
use domain::user::{Timezone, User};
|
||||
|
||||
use application::cycle::use_cases::read_cycle::CycleView;
|
||||
use application::cycle::use_cases::{forget_cycle_start, read_cycle, record_cycle_start};
|
||||
use application::user::use_cases::set_preferences;
|
||||
|
||||
async fn a_user_in_warsaw(store: &Arc<InMemoryStore>) -> User {
|
||||
let mut user = test_user("alice");
|
||||
user.update_timezone(Some(Timezone::new("Europe/Warsaw").unwrap()));
|
||||
UserCommandPort::save(store.as_ref(), &user).await.unwrap();
|
||||
user
|
||||
}
|
||||
|
||||
fn on(day: &str) -> Date {
|
||||
Date::from_persistence(day.parse().unwrap())
|
||||
}
|
||||
|
||||
async fn turn_tracking(store: &Arc<InMemoryStore>, user: &User, on: bool) {
|
||||
let deps = set_preferences::Deps {
|
||||
command: store.clone(),
|
||||
query: store.clone(),
|
||||
};
|
||||
|
||||
set_preferences::execute(user.id().clone(), on, &deps)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
async fn record(
|
||||
store: &Arc<InMemoryStore>,
|
||||
user: &User,
|
||||
day: &str,
|
||||
) -> Result<(), application::errors::ApplicationError> {
|
||||
let deps = record_cycle_start::Deps {
|
||||
command: store.clone(),
|
||||
preferences: store.clone(),
|
||||
};
|
||||
|
||||
record_cycle_start::execute(user.id().clone(), on(day), &deps).await
|
||||
}
|
||||
|
||||
async fn forget(store: &Arc<InMemoryStore>, user: &User, day: &str) {
|
||||
let deps = forget_cycle_start::Deps {
|
||||
command: store.clone(),
|
||||
};
|
||||
|
||||
forget_cycle_start::execute(user.id().clone(), on(day), &deps)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
async fn read(store: &Arc<InMemoryStore>, user: &User) -> CycleView {
|
||||
let deps = read_cycle::Deps {
|
||||
query: store.clone(),
|
||||
preferences: store.clone(),
|
||||
users: store.clone(),
|
||||
};
|
||||
|
||||
read_cycle::execute(user.id().clone(), &deps).await.unwrap()
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn tracking_is_off_until_it_is_turned_on() {
|
||||
let store = Arc::new(InMemoryStore::new());
|
||||
let user = a_user_in_warsaw(&store).await;
|
||||
|
||||
let view = read(&store, &user).await;
|
||||
|
||||
assert!(!view.tracking);
|
||||
assert!(view.starts.is_empty());
|
||||
assert!(view.today.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn nothing_can_be_recorded_while_tracking_is_off() {
|
||||
let store = Arc::new(InMemoryStore::new());
|
||||
let user = a_user_in_warsaw(&store).await;
|
||||
|
||||
let refused = record(&store, &user, "2026-01-01").await.unwrap_err();
|
||||
|
||||
assert!(refused.to_string().contains("off"), "got {refused}");
|
||||
assert!(read(&store, &user).await.starts.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn a_recorded_start_comes_back_in_order() {
|
||||
let store = Arc::new(InMemoryStore::new());
|
||||
let user = a_user_in_warsaw(&store).await;
|
||||
turn_tracking(&store, &user, true).await;
|
||||
|
||||
record(&store, &user, "2026-01-29").await.unwrap();
|
||||
record(&store, &user, "2026-01-01").await.unwrap();
|
||||
|
||||
let view = read(&store, &user).await;
|
||||
|
||||
assert_eq!(
|
||||
view.starts
|
||||
.iter()
|
||||
.map(|d| d.to_string())
|
||||
.collect::<Vec<_>>(),
|
||||
["2026-01-01", "2026-01-29"]
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn recording_the_same_day_twice_records_it_once() {
|
||||
let store = Arc::new(InMemoryStore::new());
|
||||
let user = a_user_in_warsaw(&store).await;
|
||||
turn_tracking(&store, &user, true).await;
|
||||
|
||||
record(&store, &user, "2026-01-01").await.unwrap();
|
||||
record(&store, &user, "2026-01-01").await.unwrap();
|
||||
|
||||
assert_eq!(read(&store, &user).await.starts.len(), 1);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn a_start_can_be_taken_back() {
|
||||
let store = Arc::new(InMemoryStore::new());
|
||||
let user = a_user_in_warsaw(&store).await;
|
||||
turn_tracking(&store, &user, true).await;
|
||||
record(&store, &user, "2026-01-01").await.unwrap();
|
||||
|
||||
forget(&store, &user, "2026-01-01").await;
|
||||
|
||||
assert!(read(&store, &user).await.starts.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn turning_tracking_off_hides_the_cycle_without_forgetting_it() {
|
||||
let store = Arc::new(InMemoryStore::new());
|
||||
let user = a_user_in_warsaw(&store).await;
|
||||
turn_tracking(&store, &user, true).await;
|
||||
record(&store, &user, "2026-01-01").await.unwrap();
|
||||
|
||||
turn_tracking(&store, &user, false).await;
|
||||
let hidden = read(&store, &user).await;
|
||||
|
||||
turn_tracking(&store, &user, true).await;
|
||||
let shown = read(&store, &user).await;
|
||||
|
||||
assert!(!hidden.tracking);
|
||||
assert!(
|
||||
hidden.starts.is_empty(),
|
||||
"nothing is offered while it is off"
|
||||
);
|
||||
assert_eq!(shown.starts.len(), 1, "and nothing was thrown away");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn one_accounts_cycle_is_not_anothers() {
|
||||
let store = Arc::new(InMemoryStore::new());
|
||||
let mine = a_user_in_warsaw(&store).await;
|
||||
|
||||
let mut theirs = test_user("bob");
|
||||
theirs.update_timezone(Some(Timezone::new("Europe/Warsaw").unwrap()));
|
||||
UserCommandPort::save(store.as_ref(), &theirs)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
turn_tracking(&store, &mine, true).await;
|
||||
turn_tracking(&store, &theirs, true).await;
|
||||
record(&store, &mine, "2026-01-01").await.unwrap();
|
||||
|
||||
assert_eq!(read(&store, &mine).await.starts.len(), 1);
|
||||
assert!(read(&store, &theirs).await.starts.is_empty());
|
||||
}
|
||||
Reference in New Issue
Block a user