Skip to main content

max / goingson

3.4 KB · 89 lines History Blame Raw
1 //! Integration tests for SqliteMonthlyReviewRepository.
2 //!
3 //! Focus: the goal upsert-by-position path (a transaction-guarded
4 //! get-then-insert, since `(user_id, month, position)` has no unique key) and
5 //! the reflection `ON CONFLICT(user_id, month)` upsert — the review-repo
6 //! atomicity the audit flagged as untested.
7
8 mod common;
9
10 use goingson_core::{MonthlyGoalStatus, MonthlyReviewRepository};
11 use goingson_db_sqlite::SqliteMonthlyReviewRepository;
12
13 const MONTH: &str = "2026-06";
14
15 #[tokio::test]
16 async fn test_upsert_goal_by_position_replaces_in_place() {
17 let pool = common::setup_test_db().await;
18 let user_id = common::create_test_user(&pool).await;
19 let repo = SqliteMonthlyReviewRepository::new(pool);
20
21 let first = repo.upsert_goal(user_id, MONTH, "ship v1", 1).await.unwrap();
22 assert_eq!(first.text, "ship v1");
23 assert_eq!(first.position, 1);
24
25 // Same (user, month, position) must update the existing goal, not add a second at position 1.
26 let second = repo.upsert_goal(user_id, MONTH, "ship v2", 1).await.unwrap();
27 assert_eq!(second.id, first.id, "upsert must reuse the goal at that position");
28 assert_eq!(second.text, "ship v2");
29
30 let goals = repo.list_goals(user_id, MONTH).await.unwrap();
31 assert_eq!(goals.len(), 1, "repeated upsert at one position must not duplicate");
32 }
33
34 #[tokio::test]
35 async fn test_upsert_goal_distinct_positions_coexist() {
36 let pool = common::setup_test_db().await;
37 let user_id = common::create_test_user(&pool).await;
38 let repo = SqliteMonthlyReviewRepository::new(pool);
39
40 repo.upsert_goal(user_id, MONTH, "goal one", 1).await.unwrap();
41 repo.upsert_goal(user_id, MONTH, "goal two", 2).await.unwrap();
42 repo.upsert_goal(user_id, MONTH, "goal three", 3).await.unwrap();
43
44 let goals = repo.list_goals(user_id, MONTH).await.unwrap();
45 assert_eq!(goals.len(), 3);
46 assert_eq!(goals[0].position, 1);
47 assert_eq!(goals[2].text, "goal three");
48 }
49
50 #[tokio::test]
51 async fn test_update_goal_status_and_delete() {
52 let pool = common::setup_test_db().await;
53 let user_id = common::create_test_user(&pool).await;
54 let repo = SqliteMonthlyReviewRepository::new(pool);
55
56 let goal = repo.upsert_goal(user_id, MONTH, "finish audit", 1).await.unwrap();
57 assert_eq!(goal.status, MonthlyGoalStatus::Active);
58
59 let updated = repo
60 .update_goal_status(goal.id, user_id, &MonthlyGoalStatus::Done)
61 .await
62 .unwrap()
63 .expect("goal exists");
64 assert_eq!(updated.status, MonthlyGoalStatus::Done);
65
66 assert!(repo.delete_goal(goal.id, user_id).await.unwrap());
67 assert!(repo.list_goals(user_id, MONTH).await.unwrap().is_empty());
68 }
69
70 #[tokio::test]
71 async fn test_upsert_reflection_is_idempotent() {
72 let pool = common::setup_test_db().await;
73 let user_id = common::create_test_user(&pool).await;
74 let repo = SqliteMonthlyReviewRepository::new(pool);
75
76 repo.upsert_reflection(user_id, MONTH, "highlight A", "change A").await.unwrap();
77 // Second upsert for the same month must overwrite, not create a second reflection row.
78 let second = repo
79 .upsert_reflection(user_id, MONTH, "highlight B", "change B")
80 .await
81 .unwrap();
82 assert_eq!(second.highlight_text, "highlight B");
83 assert_eq!(second.change_text, "change B");
84
85 let got = repo.get_reflection(user_id, MONTH).await.unwrap().unwrap();
86 assert_eq!(got.highlight_text, "highlight B");
87 assert_eq!(repo.list_all_reflections(user_id).await.unwrap().len(), 1);
88 }
89