Skip to main content

max / goingson

3.6 KB · 113 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
22 .upsert_goal(user_id, MONTH, "ship v1", 1)
23 .await
24 .unwrap();
25 assert_eq!(first.text, "ship v1");
26 assert_eq!(first.position, 1);
27
28 // Same (user, month, position) must update the existing goal, not add a second at position 1.
29 let second = repo
30 .upsert_goal(user_id, MONTH, "ship v2", 1)
31 .await
32 .unwrap();
33 assert_eq!(
34 second.id, first.id,
35 "upsert must reuse the goal at that position"
36 );
37 assert_eq!(second.text, "ship v2");
38
39 let goals = repo.list_goals(user_id, MONTH).await.unwrap();
40 assert_eq!(
41 goals.len(),
42 1,
43 "repeated upsert at one position must not duplicate"
44 );
45 }
46
47 #[tokio::test]
48 async fn test_upsert_goal_distinct_positions_coexist() {
49 let pool = common::setup_test_db().await;
50 let user_id = common::create_test_user(&pool).await;
51 let repo = SqliteMonthlyReviewRepository::new(pool);
52
53 repo.upsert_goal(user_id, MONTH, "goal one", 1)
54 .await
55 .unwrap();
56 repo.upsert_goal(user_id, MONTH, "goal two", 2)
57 .await
58 .unwrap();
59 repo.upsert_goal(user_id, MONTH, "goal three", 3)
60 .await
61 .unwrap();
62
63 let goals = repo.list_goals(user_id, MONTH).await.unwrap();
64 assert_eq!(goals.len(), 3);
65 assert_eq!(goals[0].position, 1);
66 assert_eq!(goals[2].text, "goal three");
67 }
68
69 #[tokio::test]
70 async fn test_update_goal_status_and_delete() {
71 let pool = common::setup_test_db().await;
72 let user_id = common::create_test_user(&pool).await;
73 let repo = SqliteMonthlyReviewRepository::new(pool);
74
75 let goal = repo
76 .upsert_goal(user_id, MONTH, "finish audit", 1)
77 .await
78 .unwrap();
79 assert_eq!(goal.status, MonthlyGoalStatus::Active);
80
81 let updated = repo
82 .update_goal_status(goal.id, user_id, &MonthlyGoalStatus::Done)
83 .await
84 .unwrap()
85 .expect("goal exists");
86 assert_eq!(updated.status, MonthlyGoalStatus::Done);
87
88 assert!(repo.delete_goal(goal.id, user_id).await.unwrap());
89 assert!(repo.list_goals(user_id, MONTH).await.unwrap().is_empty());
90 }
91
92 #[tokio::test]
93 async fn test_upsert_reflection_is_idempotent() {
94 let pool = common::setup_test_db().await;
95 let user_id = common::create_test_user(&pool).await;
96 let repo = SqliteMonthlyReviewRepository::new(pool);
97
98 repo.upsert_reflection(user_id, MONTH, "highlight A", "change A")
99 .await
100 .unwrap();
101 // Second upsert for the same month must overwrite, not create a second reflection row.
102 let second = repo
103 .upsert_reflection(user_id, MONTH, "highlight B", "change B")
104 .await
105 .unwrap();
106 assert_eq!(second.highlight_text, "highlight B");
107 assert_eq!(second.change_text, "change B");
108
109 let got = repo.get_reflection(user_id, MONTH).await.unwrap().unwrap();
110 assert_eq!(got.highlight_text, "highlight B");
111 assert_eq!(repo.list_all_reflections(user_id).await.unwrap().len(), 1);
112 }
113