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