Skip to main content

max / goingson

4.9 KB · 177 lines History Blame Raw
1 //! Integration tests for SqliteUserRepository.
2
3 #[allow(dead_code)]
4 mod common;
5
6 use goingson_core::UserRepository;
7 use goingson_db_sqlite::SqliteUserRepository;
8
9 #[tokio::test]
10 async fn test_create_hashes_password_and_lowercases_email() {
11 let pool = common::setup_test_db().await;
12 let repo = SqliteUserRepository::new(pool);
13
14 let user = repo
15 .create("Alice@Example.COM", "correct horse battery staple", "Alice")
16 .await
17 .expect("Failed to create user");
18
19 assert_eq!(
20 user.email, "alice@example.com",
21 "email must be stored lowercased"
22 );
23 assert_eq!(user.display_name, "Alice");
24 assert_ne!(
25 user.password_hash, "correct horse battery staple",
26 "password must not be stored in plaintext"
27 );
28 assert!(
29 user.password_hash.starts_with("$argon2"),
30 "stored hash should be a PHC Argon2 string, got: {}",
31 user.password_hash
32 );
33 assert!(
34 user.last_login_at.is_none(),
35 "a freshly created user has never logged in"
36 );
37 }
38
39 #[tokio::test]
40 async fn test_find_by_email_found_and_not_found() {
41 let pool = common::setup_test_db().await;
42 let repo = SqliteUserRepository::new(pool);
43
44 repo.create("bob@example.com", "hunter2hunter2", "Bob")
45 .await
46 .expect("create");
47
48 let found = repo.find_by_email("bob@example.com").await.expect("find");
49 assert!(found.is_some());
50 assert_eq!(found.unwrap().display_name, "Bob");
51
52 let missing = repo
53 .find_by_email("nobody@example.com")
54 .await
55 .expect("find");
56 assert!(missing.is_none(), "unknown email must return None");
57 }
58
59 #[tokio::test]
60 async fn test_find_by_email_is_case_insensitive() {
61 let pool = common::setup_test_db().await;
62 let repo = SqliteUserRepository::new(pool);
63
64 let created = repo
65 .create("carol@example.com", "passwordpassword", "Carol")
66 .await
67 .expect("create");
68
69 let found = repo
70 .find_by_email("CAROL@EXAMPLE.COM")
71 .await
72 .expect("find")
73 .expect("should match despite case");
74 assert_eq!(
75 found.id, created.id,
76 "lookup must fold case to match the stored lowercase email"
77 );
78 }
79
80 #[tokio::test]
81 async fn test_authenticate_correct_password_succeeds() {
82 let pool = common::setup_test_db().await;
83 let repo = SqliteUserRepository::new(pool);
84
85 let created = repo
86 .create("dave@example.com", "s3cr3t-passphrase", "Dave")
87 .await
88 .expect("create");
89 assert!(created.last_login_at.is_none());
90
91 let authed = repo
92 .authenticate("dave@example.com", "s3cr3t-passphrase")
93 .await
94 .expect("authenticate");
95 assert!(authed.is_some(), "correct password must authenticate");
96 assert_eq!(authed.unwrap().id, created.id);
97
98 // authenticate() records a successful login timestamp.
99 let after = repo
100 .find_by_email("dave@example.com")
101 .await
102 .expect("find")
103 .unwrap();
104 assert!(
105 after.last_login_at.is_some(),
106 "successful authentication must stamp last_login_at"
107 );
108 }
109
110 #[tokio::test]
111 async fn test_authenticate_wrong_password_fails() {
112 let pool = common::setup_test_db().await;
113 let repo = SqliteUserRepository::new(pool);
114
115 repo.create("erin@example.com", "the-right-password", "Erin")
116 .await
117 .expect("create");
118
119 let result = repo
120 .authenticate("erin@example.com", "the-wrong-password")
121 .await
122 .expect("authenticate");
123 assert!(result.is_none(), "wrong password must not authenticate");
124
125 // A failed login must not stamp last_login_at.
126 let after = repo
127 .find_by_email("erin@example.com")
128 .await
129 .expect("find")
130 .unwrap();
131 assert!(
132 after.last_login_at.is_none(),
133 "failed authentication must leave last_login_at untouched"
134 );
135 }
136
137 #[tokio::test]
138 async fn test_authenticate_unknown_email_returns_none() {
139 let pool = common::setup_test_db().await;
140 let repo = SqliteUserRepository::new(pool);
141
142 let result = repo
143 .authenticate("ghost@example.com", "whatever")
144 .await
145 .expect("authenticate");
146 assert!(
147 result.is_none(),
148 "authenticating an unknown email must return None, not error"
149 );
150 }
151
152 #[tokio::test]
153 async fn test_update_last_login_sets_timestamp() {
154 let pool = common::setup_test_db().await;
155 let repo = SqliteUserRepository::new(pool);
156
157 let user = repo
158 .create("frank@example.com", "frankspassword", "Frank")
159 .await
160 .expect("create");
161 assert!(user.last_login_at.is_none());
162
163 repo.update_last_login(user.id)
164 .await
165 .expect("update_last_login");
166
167 let refreshed = repo
168 .find_by_email("frank@example.com")
169 .await
170 .expect("find")
171 .unwrap();
172 assert!(
173 refreshed.last_login_at.is_some(),
174 "update_last_login must persist a timestamp"
175 );
176 }
177