179
crates/adapters/sqlite/tests/cycle_test.rs
Normal file
179
crates/adapters/sqlite/tests/cycle_test.rs
Normal file
@@ -0,0 +1,179 @@
|
||||
use sqlx::sqlite::SqlitePoolOptions;
|
||||
|
||||
use domain::entry::Date;
|
||||
use domain::ports::{
|
||||
CascadeDeletePort, CycleStartCommandPort, CycleStartQueryPort, UserCommandPort,
|
||||
UserPreferencesCommandPort, UserPreferencesQueryPort,
|
||||
};
|
||||
use domain::testing::test_user;
|
||||
use domain::user::{User, UserId, UserPreferences};
|
||||
|
||||
use sqlite::repositories::{
|
||||
SqliteCascadeDeleteRepository, SqliteCycleStartRepository, SqliteUserCommandRepository,
|
||||
SqliteUserPreferencesRepository,
|
||||
};
|
||||
|
||||
async fn a_pool_with_a_user() -> (sqlx::SqlitePool, User) {
|
||||
let pool = SqlitePoolOptions::new()
|
||||
.max_connections(1)
|
||||
.connect("sqlite::memory:")
|
||||
.await
|
||||
.unwrap();
|
||||
sqlite::run_migrations(&pool).await.unwrap();
|
||||
|
||||
let user = test_user("alice");
|
||||
SqliteUserCommandRepository::new(pool.clone())
|
||||
.save(&user)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
(pool, user)
|
||||
}
|
||||
|
||||
fn on(day: &str) -> Date {
|
||||
Date::from_persistence(day.parse().unwrap())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn recording_the_same_start_twice_leaves_one_row() {
|
||||
let (pool, user) = a_pool_with_a_user().await;
|
||||
let starts = SqliteCycleStartRepository::new(pool.clone());
|
||||
|
||||
starts.record(user.id(), &on("2026-01-01")).await.unwrap();
|
||||
starts.record(user.id(), &on("2026-01-01")).await.unwrap();
|
||||
|
||||
let rows: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM cycle_starts")
|
||||
.fetch_one(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(rows.0, 1);
|
||||
assert_eq!(starts.find_by_user(user.id()).await.unwrap().len(), 1);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn starts_come_back_oldest_first() {
|
||||
let (pool, user) = a_pool_with_a_user().await;
|
||||
let starts = SqliteCycleStartRepository::new(pool.clone());
|
||||
|
||||
for day in ["2026-02-26", "2026-01-01", "2026-01-29"] {
|
||||
starts.record(user.id(), &on(day)).await.unwrap();
|
||||
}
|
||||
|
||||
let found: Vec<String> = starts
|
||||
.find_by_user(user.id())
|
||||
.await
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|date| date.to_string())
|
||||
.collect();
|
||||
|
||||
assert_eq!(found, ["2026-01-01", "2026-01-29", "2026-02-26"]);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn forgetting_a_start_removes_only_that_one() {
|
||||
let (pool, user) = a_pool_with_a_user().await;
|
||||
let starts = SqliteCycleStartRepository::new(pool.clone());
|
||||
starts.record(user.id(), &on("2026-01-01")).await.unwrap();
|
||||
starts.record(user.id(), &on("2026-01-29")).await.unwrap();
|
||||
|
||||
starts.forget(user.id(), &on("2026-01-01")).await.unwrap();
|
||||
|
||||
let found: Vec<String> = starts
|
||||
.find_by_user(user.id())
|
||||
.await
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|date| date.to_string())
|
||||
.collect();
|
||||
|
||||
assert_eq!(found, ["2026-01-29"]);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn one_accounts_starts_are_not_anothers() {
|
||||
let (pool, mine) = a_pool_with_a_user().await;
|
||||
let starts = SqliteCycleStartRepository::new(pool.clone());
|
||||
|
||||
starts.record(mine.id(), &on("2026-01-01")).await.unwrap();
|
||||
|
||||
assert!(
|
||||
starts
|
||||
.find_by_user(&UserId::generate())
|
||||
.await
|
||||
.unwrap()
|
||||
.is_empty()
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn an_account_with_no_stored_preference_has_none_to_read() {
|
||||
let (pool, user) = a_pool_with_a_user().await;
|
||||
|
||||
let found = SqliteUserPreferencesRepository::new(pool.clone())
|
||||
.find_by_user(user.id())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
found.is_none(),
|
||||
"the default belongs to the domain, not the row"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn a_preference_survives_being_written_twice() {
|
||||
let (pool, user) = a_pool_with_a_user().await;
|
||||
let preferences = SqliteUserPreferencesRepository::new(pool.clone());
|
||||
|
||||
let mut held = UserPreferences::off_by_default(user.id().clone());
|
||||
held.track_cycle(true);
|
||||
preferences.save(&held).await.unwrap();
|
||||
|
||||
held.track_cycle(false);
|
||||
preferences.save(&held).await.unwrap();
|
||||
|
||||
let found = preferences.find_by_user(user.id()).await.unwrap().unwrap();
|
||||
|
||||
assert!(!found.tracks_cycle());
|
||||
|
||||
let rows: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM user_preferences")
|
||||
.fetch_one(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(rows.0, 1);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn deleting_an_account_takes_its_cycle_and_preferences_with_it() {
|
||||
let (pool, user) = a_pool_with_a_user().await;
|
||||
SqliteCycleStartRepository::new(pool.clone())
|
||||
.record(user.id(), &on("2026-01-01"))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut held = UserPreferences::off_by_default(user.id().clone());
|
||||
held.track_cycle(true);
|
||||
SqliteUserPreferencesRepository::new(pool.clone())
|
||||
.save(&held)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
SqliteCascadeDeleteRepository::new(pool.clone())
|
||||
.delete_user_account(user.id())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let starts: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM cycle_starts")
|
||||
.fetch_one(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
let preferences: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM user_preferences")
|
||||
.fetch_one(&pool)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(starts.0, 0);
|
||||
assert_eq!(preferences.0, 0);
|
||||
}
|
||||
Reference in New Issue
Block a user