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(|a, b| b.created_at.cmp(&a.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
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 fn new_user(email: &str, username: &str) -> NewUser {
121 NewUser {
122 email: email.to_string(),
123 username: username.to_string(),
124 password_hash: "argon2hash".to_string(),
125 is_admin: None,
126 }
127 }
128
129 #[tokio::test]
130 async fn create_user_first_user_is_admin() {
131 let store = InMemoryStore::new();
132 let user = store
133 .create_user(new_user("alice@example.com", "alice"))
134 .await
135 .unwrap();
136
137 assert_eq!(user.email, "alice@example.com");
138 assert_eq!(user.username, "alice");
139 assert_eq!(user.password_hash, "argon2hash");
140 assert!(user.is_admin);
141 }
142
143 #[tokio::test]
144 async fn create_user_second_user_is_not_admin() {
145 let store = InMemoryStore::new();
146 store
147 .create_user(new_user("alice@example.com", "alice"))
148 .await
149 .unwrap();
150
151 let second = store
152 .create_user(new_user("bob@example.com", "bob"))
153 .await
154 .unwrap();
155
156 assert!(!second.is_admin);
157 }
158
159 #[tokio::test]
160 async fn create_user_explicit_admin_flag() {
161 let store = InMemoryStore::new();
162 let first = store
164 .create_user(NewUser {
165 email: "alice@example.com".to_string(),
166 username: "alice".to_string(),
167 password_hash: "argon2hash".to_string(),
168 is_admin: Some(false),
169 })
170 .await
171 .unwrap();
172 assert!(!first.is_admin);
173
174 let second = store
176 .create_user(NewUser {
177 email: "bob@example.com".to_string(),
178 username: "bob".to_string(),
179 password_hash: "argon2hash".to_string(),
180 is_admin: Some(true),
181 })
182 .await
183 .unwrap();
184 assert!(second.is_admin);
185 }
186
187 #[tokio::test]
188 async fn create_user_duplicate_email_returns_error() {
189 let store = InMemoryStore::new();
190 store
191 .create_user(new_user("alice@example.com", "alice"))
192 .await
193 .unwrap();
194
195 let err = store
196 .create_user(new_user("alice@example.com", "bob"))
197 .await
198 .unwrap_err();
199
200 assert!(
201 matches!(err, StoreError::DuplicateEmail(ref e) if e == "alice@example.com"),
202 "expected DuplicateEmail, got: {err}"
203 );
204 }
205
206 #[tokio::test]
207 async fn create_user_duplicate_username_returns_error() {
208 let store = InMemoryStore::new();
209 store
210 .create_user(new_user("alice@example.com", "alice"))
211 .await
212 .unwrap();
213
214 let err = store
215 .create_user(new_user("bob@example.com", "alice"))
216 .await
217 .unwrap_err();
218
219 assert!(
220 matches!(err, StoreError::DuplicateUsername(ref u) if u == "alice"),
221 "expected DuplicateUsername, got: {err}"
222 );
223 }
224
225 #[tokio::test]
226 async fn find_user_by_email_existing() {
227 let store = InMemoryStore::new();
228 let created = store
229 .create_user(new_user("alice@example.com", "alice"))
230 .await
231 .unwrap();
232
233 let found = store
234 .find_user_by_email("alice@example.com")
235 .await
236 .unwrap()
237 .expect("user should exist");
238
239 assert_eq!(found.id, created.id);
240 assert_eq!(found.email, "alice@example.com");
241 }
242
243 #[tokio::test]
244 async fn find_user_by_email_missing_returns_none() {
245 let store = InMemoryStore::new();
246 let found = store
247 .find_user_by_email("nobody@example.com")
248 .await
249 .unwrap();
250
251 assert!(found.is_none());
252 }
253
254 #[tokio::test]
255 async fn find_user_by_id_existing() {
256 let store = InMemoryStore::new();
257 let created = store
258 .create_user(new_user("alice@example.com", "alice"))
259 .await
260 .unwrap();
261
262 let found = store
263 .find_user_by_id(created.id)
264 .await
265 .unwrap()
266 .expect("user should exist");
267
268 assert_eq!(found.email, "alice@example.com");
269 assert_eq!(found.username, "alice");
270 }
271
272 #[tokio::test]
273 async fn find_user_by_id_missing_returns_none() {
274 let store = InMemoryStore::new();
275 let found = store.find_user_by_id(Uuid::now_v7()).await.unwrap();
276 assert!(found.is_none());
277 }
278
279 #[tokio::test]
280 async fn count_users_empty_store() {
281 let store = InMemoryStore::new();
282 assert_eq!(store.count_users().await.unwrap(), 0);
283 }
284
285 #[tokio::test]
286 async fn count_users_with_users() {
287 let store = InMemoryStore::new();
288 store
289 .create_user(new_user("alice@example.com", "alice"))
290 .await
291 .unwrap();
292 store
293 .create_user(new_user("bob@example.com", "bob"))
294 .await
295 .unwrap();
296 assert_eq!(store.count_users().await.unwrap(), 2);
297 }
298
299 #[tokio::test]
300 async fn list_users_paginated() {
301 let store = InMemoryStore::new();
302 for i in 0..5 {
303 store
304 .create_user(new_user(
305 &format!("user{i}@example.com"),
306 &format!("user{i}"),
307 ))
308 .await
309 .unwrap();
310 }
311
312 let page = store.list_users(1, 2).await.unwrap();
313 assert_eq!(page.items.len(), 2);
314 assert_eq!(page.total, 5);
315 assert_eq!(page.page, 1);
316 assert_eq!(page.per_page, 2);
317
318 let page2 = store.list_users(3, 2).await.unwrap();
319 assert_eq!(page2.items.len(), 1);
320 }
321
322 #[tokio::test]
323 async fn list_users_empty_store() {
324 let store = InMemoryStore::new();
325 let page = store.list_users(1, 20).await.unwrap();
326 assert!(page.items.is_empty());
327 assert_eq!(page.total, 0);
328 }
329
330 #[tokio::test]
331 async fn delete_user_existing() {
332 let store = InMemoryStore::new();
333 let user = store
334 .create_user(new_user("alice@example.com", "alice"))
335 .await
336 .unwrap();
337
338 store.delete_user(user.id).await.unwrap();
339 assert_eq!(store.count_users().await.unwrap(), 0);
340 }
341
342 #[tokio::test]
343 async fn delete_user_not_found() {
344 let store = InMemoryStore::new();
345 let err = store.delete_user(Uuid::now_v7()).await.unwrap_err();
346 assert!(matches!(err, StoreError::UserNotFound(_)));
347 }
348
349 #[tokio::test]
350 async fn update_user_role_promote() {
351 let store = InMemoryStore::new();
352 let _admin = store
354 .create_user(new_user("admin@example.com", "admin"))
355 .await
356 .unwrap();
357 let member = store
358 .create_user(new_user("member@example.com", "member"))
359 .await
360 .unwrap();
361 assert!(!member.is_admin);
362
363 let promoted = store.update_user_role(member.id, true).await.unwrap();
364 assert!(promoted.is_admin);
365 }
366
367 #[tokio::test]
368 async fn update_user_role_demote() {
369 let store = InMemoryStore::new();
370 let admin = store
371 .create_user(new_user("admin@example.com", "admin"))
372 .await
373 .unwrap();
374 assert!(admin.is_admin);
375
376 let demoted = store.update_user_role(admin.id, false).await.unwrap();
377 assert!(!demoted.is_admin);
378 }
379
380 #[tokio::test]
381 async fn update_user_role_not_found() {
382 let store = InMemoryStore::new();
383 let err = store
384 .update_user_role(Uuid::now_v7(), true)
385 .await
386 .unwrap_err();
387 assert!(matches!(err, StoreError::UserNotFound(_)));
388 }
389}