1use chrono::Utc;
4use uuid::Uuid;
5
6use crate::entities::{NewUser, Page, User};
7use crate::error::StoreError;
8use crate::store::StoreFuture;
9use crate::user_store::UserStore;
10
11use super::InMemoryStore;
12
13impl UserStore for InMemoryStore {
14 fn create_user(&self, req: NewUser) -> StoreFuture<'_, User> {
15 Box::pin(async move {
16 let mut state = self.state.write().await;
17
18 let email_exists = state.users.values().any(|u| u.email == req.email);
19 if email_exists {
20 return Err(StoreError::DuplicateEmail(req.email));
21 }
22
23 let username_exists = state.users.values().any(|u| u.username == req.username);
24 if username_exists {
25 return Err(StoreError::DuplicateUsername(req.username));
26 }
27
28 let is_admin = req.is_admin.unwrap_or(state.users.is_empty());
29
30 let now = Utc::now();
31 let user = User {
32 id: Uuid::now_v7(),
33 email: req.email,
34 username: req.username,
35 password_hash: req.password_hash,
36 is_admin,
37 created_at: now,
38 updated_at: now,
39 };
40
41 state.users.insert(user.id, user.clone());
42 Ok(user)
43 })
44 }
45
46 fn find_user_by_email(&self, email: &str) -> StoreFuture<'_, Option<User>> {
47 let email = email.to_string();
48 Box::pin(async move {
49 let state = self.state.read().await;
50 Ok(state.users.values().find(|u| u.email == email).cloned())
51 })
52 }
53
54 fn find_user_by_username(&self, username: &str) -> StoreFuture<'_, Option<User>> {
55 let username = username.to_string();
56 Box::pin(async move {
57 let state = self.state.read().await;
58 Ok(state
59 .users
60 .values()
61 .find(|u| u.username == username)
62 .cloned())
63 })
64 }
65
66 fn find_user_by_id(&self, id: Uuid) -> StoreFuture<'_, Option<User>> {
67 Box::pin(async move {
68 let state = self.state.read().await;
69 Ok(state.users.get(&id).cloned())
70 })
71 }
72
73 fn count_users(&self) -> StoreFuture<'_, u64> {
74 Box::pin(async move {
75 let state = self.state.read().await;
76 Ok(state.users.len() as u64)
77 })
78 }
79
80 fn list_users(&self, page: u32, per_page: u32) -> StoreFuture<'_, Page<User>> {
81 Box::pin(async move {
82 let state = self.state.read().await;
83 let mut users: Vec<User> = state.users.values().cloned().collect();
84 users.sort_by_key(|u| std::cmp::Reverse(u.created_at));
85
86 let total = users.len() as u64;
87 let offset = ((page.saturating_sub(1)) as usize) * (per_page as usize);
88 let items: Vec<User> = users
89 .into_iter()
90 .skip(offset)
91 .take(per_page as usize)
92 .collect();
93
94 Ok(Page {
95 items,
96 total,
97 page,
98 per_page,
99 })
100 })
101 }
102
103 fn delete_user(&self, id: Uuid) -> StoreFuture<'_, ()> {
104 Box::pin(async move {
105 let mut state = self.state.write().await;
106 state
107 .users
108 .remove(&id)
109 .ok_or(StoreError::UserNotFound(id))?;
110 Ok(())
111 })
112 }
113
114 fn update_user_role(&self, id: Uuid, is_admin: bool) -> StoreFuture<'_, User> {
115 Box::pin(async move {
116 let mut state = self.state.write().await;
117 let user = state
118 .users
119 .get_mut(&id)
120 .ok_or(StoreError::UserNotFound(id))?;
121 user.is_admin = is_admin;
122 user.updated_at = Utc::now();
123 Ok(user.clone())
124 })
125 }
126
127 fn update_user_password(&self, id: Uuid, password_hash: String) -> StoreFuture<'_, ()> {
128 Box::pin(async move {
129 let mut state = self.state.write().await;
130 let user = state
131 .users
132 .get_mut(&id)
133 .ok_or(StoreError::UserNotFound(id))?;
134 user.password_hash = password_hash;
135 user.updated_at = Utc::now();
136 Ok(())
137 })
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 fn new_user(email: &str, username: &str) -> NewUser {
146 NewUser {
147 email: email.to_string(),
148 username: username.to_string(),
149 password_hash: "argon2hash".to_string(),
150 is_admin: None,
151 }
152 }
153
154 #[tokio::test]
155 async fn create_user_first_user_is_admin() {
156 let store = InMemoryStore::new();
157 let user = store
158 .create_user(new_user("alice@example.com", "alice"))
159 .await
160 .unwrap();
161
162 assert_eq!(user.email, "alice@example.com");
163 assert_eq!(user.username, "alice");
164 assert_eq!(user.password_hash, "argon2hash");
165 assert!(user.is_admin);
166 }
167
168 #[tokio::test]
169 async fn create_user_second_user_is_not_admin() {
170 let store = InMemoryStore::new();
171 store
172 .create_user(new_user("alice@example.com", "alice"))
173 .await
174 .unwrap();
175
176 let second = store
177 .create_user(new_user("bob@example.com", "bob"))
178 .await
179 .unwrap();
180
181 assert!(!second.is_admin);
182 }
183
184 #[tokio::test]
185 async fn create_user_explicit_admin_flag() {
186 let store = InMemoryStore::new();
187 let first = store
189 .create_user(NewUser {
190 email: "alice@example.com".to_string(),
191 username: "alice".to_string(),
192 password_hash: "argon2hash".to_string(),
193 is_admin: Some(false),
194 })
195 .await
196 .unwrap();
197 assert!(!first.is_admin);
198
199 let second = store
201 .create_user(NewUser {
202 email: "bob@example.com".to_string(),
203 username: "bob".to_string(),
204 password_hash: "argon2hash".to_string(),
205 is_admin: Some(true),
206 })
207 .await
208 .unwrap();
209 assert!(second.is_admin);
210 }
211
212 #[tokio::test]
213 async fn create_user_duplicate_email_returns_error() {
214 let store = InMemoryStore::new();
215 store
216 .create_user(new_user("alice@example.com", "alice"))
217 .await
218 .unwrap();
219
220 let err = store
221 .create_user(new_user("alice@example.com", "bob"))
222 .await
223 .unwrap_err();
224
225 assert!(
226 matches!(err, StoreError::DuplicateEmail(ref e) if e == "alice@example.com"),
227 "expected DuplicateEmail, got: {err}"
228 );
229 }
230
231 #[tokio::test]
232 async fn create_user_duplicate_username_returns_error() {
233 let store = InMemoryStore::new();
234 store
235 .create_user(new_user("alice@example.com", "alice"))
236 .await
237 .unwrap();
238
239 let err = store
240 .create_user(new_user("bob@example.com", "alice"))
241 .await
242 .unwrap_err();
243
244 assert!(
245 matches!(err, StoreError::DuplicateUsername(ref u) if u == "alice"),
246 "expected DuplicateUsername, got: {err}"
247 );
248 }
249
250 #[tokio::test]
251 async fn find_user_by_email_existing() {
252 let store = InMemoryStore::new();
253 let created = store
254 .create_user(new_user("alice@example.com", "alice"))
255 .await
256 .unwrap();
257
258 let found = store
259 .find_user_by_email("alice@example.com")
260 .await
261 .unwrap()
262 .expect("user should exist");
263
264 assert_eq!(found.id, created.id);
265 assert_eq!(found.email, "alice@example.com");
266 }
267
268 #[tokio::test]
269 async fn find_user_by_email_missing_returns_none() {
270 let store = InMemoryStore::new();
271 let found = store
272 .find_user_by_email("nobody@example.com")
273 .await
274 .unwrap();
275
276 assert!(found.is_none());
277 }
278
279 #[tokio::test]
280 async fn find_user_by_username_existing() {
281 let store = InMemoryStore::new();
282 let created = store
283 .create_user(new_user("alice@example.com", "alice"))
284 .await
285 .unwrap();
286
287 let found = store
288 .find_user_by_username("alice")
289 .await
290 .unwrap()
291 .expect("user should exist");
292
293 assert_eq!(found.id, created.id);
294 }
295
296 #[tokio::test]
297 async fn find_user_by_username_missing_returns_none() {
298 let store = InMemoryStore::new();
299 store
300 .create_user(new_user("alice@example.com", "alice"))
301 .await
302 .unwrap();
303
304 let found = store.find_user_by_username("Alice").await.unwrap();
305
306 assert!(found.is_none());
307 }
308
309 #[tokio::test]
310 async fn find_user_by_id_existing() {
311 let store = InMemoryStore::new();
312 let created = store
313 .create_user(new_user("alice@example.com", "alice"))
314 .await
315 .unwrap();
316
317 let found = store
318 .find_user_by_id(created.id)
319 .await
320 .unwrap()
321 .expect("user should exist");
322
323 assert_eq!(found.email, "alice@example.com");
324 assert_eq!(found.username, "alice");
325 }
326
327 #[tokio::test]
328 async fn find_user_by_id_missing_returns_none() {
329 let store = InMemoryStore::new();
330 let found = store.find_user_by_id(Uuid::now_v7()).await.unwrap();
331 assert!(found.is_none());
332 }
333
334 #[tokio::test]
335 async fn count_users_empty_store() {
336 let store = InMemoryStore::new();
337 assert_eq!(store.count_users().await.unwrap(), 0);
338 }
339
340 #[tokio::test]
341 async fn count_users_with_users() {
342 let store = InMemoryStore::new();
343 store
344 .create_user(new_user("alice@example.com", "alice"))
345 .await
346 .unwrap();
347 store
348 .create_user(new_user("bob@example.com", "bob"))
349 .await
350 .unwrap();
351 assert_eq!(store.count_users().await.unwrap(), 2);
352 }
353
354 #[tokio::test]
355 async fn list_users_paginated() {
356 let store = InMemoryStore::new();
357 for i in 0..5 {
358 store
359 .create_user(new_user(
360 &format!("user{i}@example.com"),
361 &format!("user{i}"),
362 ))
363 .await
364 .unwrap();
365 }
366
367 let page = store.list_users(1, 2).await.unwrap();
368 assert_eq!(page.items.len(), 2);
369 assert_eq!(page.total, 5);
370 assert_eq!(page.page, 1);
371 assert_eq!(page.per_page, 2);
372
373 let page2 = store.list_users(3, 2).await.unwrap();
374 assert_eq!(page2.items.len(), 1);
375 }
376
377 #[tokio::test]
378 async fn list_users_empty_store() {
379 let store = InMemoryStore::new();
380 let page = store.list_users(1, 20).await.unwrap();
381 assert!(page.items.is_empty());
382 assert_eq!(page.total, 0);
383 }
384
385 #[tokio::test]
386 async fn delete_user_existing() {
387 let store = InMemoryStore::new();
388 let user = store
389 .create_user(new_user("alice@example.com", "alice"))
390 .await
391 .unwrap();
392
393 store.delete_user(user.id).await.unwrap();
394 assert_eq!(store.count_users().await.unwrap(), 0);
395 }
396
397 #[tokio::test]
398 async fn delete_user_not_found() {
399 let store = InMemoryStore::new();
400 let err = store.delete_user(Uuid::now_v7()).await.unwrap_err();
401 assert!(matches!(err, StoreError::UserNotFound(_)));
402 }
403
404 #[tokio::test]
405 async fn update_user_role_promote() {
406 let store = InMemoryStore::new();
407 let _admin = store
409 .create_user(new_user("admin@example.com", "admin"))
410 .await
411 .unwrap();
412 let member = store
413 .create_user(new_user("member@example.com", "member"))
414 .await
415 .unwrap();
416 assert!(!member.is_admin);
417
418 let promoted = store.update_user_role(member.id, true).await.unwrap();
419 assert!(promoted.is_admin);
420 }
421
422 #[tokio::test]
423 async fn update_user_role_demote() {
424 let store = InMemoryStore::new();
425 let admin = store
426 .create_user(new_user("admin@example.com", "admin"))
427 .await
428 .unwrap();
429 assert!(admin.is_admin);
430
431 let demoted = store.update_user_role(admin.id, false).await.unwrap();
432 assert!(!demoted.is_admin);
433 }
434
435 #[tokio::test]
436 async fn update_user_role_not_found() {
437 let store = InMemoryStore::new();
438 let err = store
439 .update_user_role(Uuid::now_v7(), true)
440 .await
441 .unwrap_err();
442 assert!(matches!(err, StoreError::UserNotFound(_)));
443 }
444}