Skip to main content

max / goingson

15.4 KB · 549 lines History Blame Raw
1 //! Integration tests for SqliteEmailAccountRepository.
2
3 mod common;
4
5 use chrono::{Duration, Utc};
6 use goingson_core::{EmailAccountRepository, EmailAuthType};
7 use goingson_db_sqlite::SqliteEmailAccountRepository;
8
9 #[tokio::test]
10 async fn test_create_password_account_and_get() {
11 let pool = common::setup_test_db().await;
12 let user_id = common::create_test_user(&pool).await;
13 let repo = SqliteEmailAccountRepository::new(pool.clone());
14
15 let created = repo
16 .create(
17 user_id,
18 "Work Email",
19 "work@example.com",
20 "imap.example.com",
21 993,
22 "smtp.example.com",
23 587,
24 "work@example.com",
25 "secret123",
26 true,
27 Some("Archive"),
28 )
29 .await
30 .expect("Failed to create account");
31
32 assert_eq!(created.account_name, "Work Email");
33 assert_eq!(created.email_address, "work@example.com");
34 assert_eq!(created.imap_server, "imap.example.com");
35 assert_eq!(created.imap_port, 993);
36 assert_eq!(created.smtp_server, "smtp.example.com");
37 assert_eq!(created.smtp_port, 587);
38 assert_eq!(created.username, "work@example.com");
39 assert_eq!(created.password, "secret123");
40 assert!(created.use_tls);
41 assert_eq!(created.archive_folder_name, Some("Archive".to_string()));
42 assert_eq!(created.auth_type, EmailAuthType::Password);
43 assert!(created.last_sync_at.is_none());
44
45 let fetched = repo
46 .get_by_id(created.id, user_id)
47 .await
48 .expect("Failed to get account");
49 assert!(fetched.is_some());
50 let fetched = fetched.unwrap();
51 assert_eq!(fetched.id, created.id);
52 assert_eq!(fetched.account_name, "Work Email");
53 assert_eq!(fetched.auth_type, EmailAuthType::Password);
54 }
55
56 #[tokio::test]
57 async fn test_create_oauth_fastmail_account() {
58 let pool = common::setup_test_db().await;
59 let user_id = common::create_test_user(&pool).await;
60 let repo = SqliteEmailAccountRepository::new(pool.clone());
61
62 let expires = Utc::now() + Duration::hours(1);
63 let created = repo
64 .create_oauth(
65 user_id,
66 "Fastmail",
67 "me@fastmail.com",
68 "access-token-abc",
69 "refresh-token-xyz",
70 expires,
71 "https://api.fastmail.com/jmap/session",
72 "acct-123",
73 )
74 .await
75 .expect("Failed to create OAuth account");
76
77 assert_eq!(created.account_name, "Fastmail");
78 assert_eq!(created.email_address, "me@fastmail.com");
79 assert_eq!(created.auth_type, EmailAuthType::OAuth2Fastmail);
80 assert_eq!(created.oauth2_access_token, Some("access-token-abc".to_string()));
81 assert_eq!(created.oauth2_refresh_token, Some("refresh-token-xyz".to_string()));
82 assert!(created.oauth2_token_expires_at.is_some());
83 assert_eq!(created.jmap_session_url, Some("https://api.fastmail.com/jmap/session".to_string()));
84 assert_eq!(created.jmap_account_id, Some("acct-123".to_string()));
85 }
86
87 #[tokio::test]
88 async fn test_create_oauth_imap_gmail() {
89 let pool = common::setup_test_db().await;
90 let user_id = common::create_test_user(&pool).await;
91 let repo = SqliteEmailAccountRepository::new(pool.clone());
92
93 let expires = Utc::now() + Duration::hours(1);
94 let created = repo
95 .create_oauth_imap(
96 user_id,
97 "Gmail",
98 "me@gmail.com",
99 EmailAuthType::OAuth2Google,
100 "google-access-token",
101 "google-refresh-token",
102 expires,
103 "imap.gmail.com",
104 993,
105 "smtp.gmail.com",
106 587,
107 )
108 .await
109 .expect("Failed to create OAuth IMAP account");
110
111 assert_eq!(created.account_name, "Gmail");
112 assert_eq!(created.email_address, "me@gmail.com");
113 assert_eq!(created.auth_type, EmailAuthType::OAuth2Google);
114 assert_eq!(created.imap_server, "imap.gmail.com");
115 assert_eq!(created.imap_port, 993);
116 assert_eq!(created.smtp_server, "smtp.gmail.com");
117 assert_eq!(created.smtp_port, 587);
118 assert_eq!(created.oauth2_access_token, Some("google-access-token".to_string()));
119 assert_eq!(created.oauth2_refresh_token, Some("google-refresh-token".to_string()));
120 }
121
122 #[tokio::test]
123 async fn test_list_by_user_ordered() {
124 let pool = common::setup_test_db().await;
125 let user_id = common::create_test_user(&pool).await;
126 let repo = SqliteEmailAccountRepository::new(pool.clone());
127
128 repo.create(
129 user_id,
130 "Beta Account",
131 "beta@example.com",
132 "imap.beta.com",
133 993,
134 "smtp.beta.com",
135 587,
136 "beta",
137 "pass",
138 true,
139 None,
140 )
141 .await
142 .expect("Failed to create beta account");
143
144 repo.create(
145 user_id,
146 "Alpha Account",
147 "alpha@example.com",
148 "imap.alpha.com",
149 993,
150 "smtp.alpha.com",
151 587,
152 "alpha",
153 "pass",
154 true,
155 None,
156 )
157 .await
158 .expect("Failed to create alpha account");
159
160 let accounts = repo
161 .list_by_user(user_id)
162 .await
163 .expect("Failed to list accounts");
164 assert_eq!(accounts.len(), 2);
165 assert_eq!(accounts[0].account_name, "Alpha Account");
166 assert_eq!(accounts[1].account_name, "Beta Account");
167 }
168
169 #[tokio::test]
170 async fn test_update_account_with_password() {
171 let pool = common::setup_test_db().await;
172 let user_id = common::create_test_user(&pool).await;
173 let repo = SqliteEmailAccountRepository::new(pool.clone());
174
175 let created = repo
176 .create(
177 user_id,
178 "Original",
179 "old@example.com",
180 "imap.old.com",
181 993,
182 "smtp.old.com",
183 587,
184 "olduser",
185 "oldpass",
186 true,
187 None,
188 )
189 .await
190 .expect("Failed to create account");
191
192 let updated = repo
193 .update(
194 created.id,
195 user_id,
196 "Updated Name",
197 "new@example.com",
198 "imap.new.com",
199 995,
200 "smtp.new.com",
201 465,
202 "newuser",
203 Some("newpass"),
204 false,
205 Some("All Mail"),
206 )
207 .await
208 .expect("Failed to update account");
209
210 assert!(updated.is_some());
211 let updated = updated.unwrap();
212 assert_eq!(updated.account_name, "Updated Name");
213 assert_eq!(updated.email_address, "new@example.com");
214 assert_eq!(updated.imap_server, "imap.new.com");
215 assert_eq!(updated.imap_port, 995);
216 assert_eq!(updated.smtp_server, "smtp.new.com");
217 assert_eq!(updated.smtp_port, 465);
218 assert_eq!(updated.username, "newuser");
219 assert_eq!(updated.password, "newpass");
220 assert!(!updated.use_tls);
221 assert_eq!(updated.archive_folder_name, Some("All Mail".to_string()));
222 }
223
224 #[tokio::test]
225 async fn test_update_account_without_password() {
226 let pool = common::setup_test_db().await;
227 let user_id = common::create_test_user(&pool).await;
228 let repo = SqliteEmailAccountRepository::new(pool.clone());
229
230 let created = repo
231 .create(
232 user_id,
233 "My Account",
234 "me@example.com",
235 "imap.example.com",
236 993,
237 "smtp.example.com",
238 587,
239 "me",
240 "originalpass",
241 true,
242 None,
243 )
244 .await
245 .expect("Failed to create account");
246
247 let updated = repo
248 .update(
249 created.id,
250 user_id,
251 "Renamed Account",
252 "me@example.com",
253 "imap.example.com",
254 993,
255 "smtp.example.com",
256 587,
257 "me",
258 None,
259 true,
260 None,
261 )
262 .await
263 .expect("Failed to update account");
264
265 assert!(updated.is_some());
266 let updated = updated.unwrap();
267 assert_eq!(updated.account_name, "Renamed Account");
268 // Password should remain unchanged
269 assert_eq!(updated.password, "originalpass");
270 }
271
272 #[tokio::test]
273 async fn test_update_oauth_tokens() {
274 let pool = common::setup_test_db().await;
275 let user_id = common::create_test_user(&pool).await;
276 let repo = SqliteEmailAccountRepository::new(pool.clone());
277
278 let expires = Utc::now() + Duration::hours(1);
279 let created = repo
280 .create_oauth(
281 user_id,
282 "Fastmail",
283 "me@fastmail.com",
284 "old-access",
285 "old-refresh",
286 expires,
287 "https://api.fastmail.com/jmap/session",
288 "acct-123",
289 )
290 .await
291 .expect("Failed to create account");
292
293 let new_expires = Utc::now() + Duration::hours(2);
294 let updated = repo
295 .update_oauth_tokens(
296 created.id,
297 user_id,
298 "new-access-token",
299 Some("new-refresh-token"),
300 new_expires,
301 )
302 .await
303 .expect("Failed to update tokens");
304
305 assert!(updated.is_some());
306 let updated = updated.unwrap();
307 assert_eq!(updated.oauth2_access_token, Some("new-access-token".to_string()));
308 assert_eq!(updated.oauth2_refresh_token, Some("new-refresh-token".to_string()));
309 }
310
311 #[tokio::test]
312 async fn test_update_jmap_session() {
313 let pool = common::setup_test_db().await;
314 let user_id = common::create_test_user(&pool).await;
315 let repo = SqliteEmailAccountRepository::new(pool.clone());
316
317 let expires = Utc::now() + Duration::hours(1);
318 let created = repo
319 .create_oauth(
320 user_id,
321 "Fastmail",
322 "me@fastmail.com",
323 "access-tok",
324 "refresh-tok",
325 expires,
326 "https://old.url/jmap/session",
327 "old-acct-id",
328 )
329 .await
330 .expect("Failed to create account");
331
332 let updated = repo
333 .update_jmap_session(
334 created.id,
335 user_id,
336 "https://new.url/jmap/session",
337 "new-acct-id",
338 )
339 .await
340 .expect("Failed to update JMAP session");
341
342 assert!(updated.is_some());
343 let updated = updated.unwrap();
344 assert_eq!(updated.jmap_session_url, Some("https://new.url/jmap/session".to_string()));
345 assert_eq!(updated.jmap_account_id, Some("new-acct-id".to_string()));
346 }
347
348 #[tokio::test]
349 async fn test_delete_account() {
350 let pool = common::setup_test_db().await;
351 let user_id = common::create_test_user(&pool).await;
352 let repo = SqliteEmailAccountRepository::new(pool.clone());
353
354 let created = repo
355 .create(
356 user_id,
357 "Throwaway",
358 "throw@example.com",
359 "imap.example.com",
360 993,
361 "smtp.example.com",
362 587,
363 "throw",
364 "pass",
365 true,
366 None,
367 )
368 .await
369 .expect("Failed to create account");
370
371 let deleted = repo
372 .delete(created.id, user_id)
373 .await
374 .expect("Failed to delete account");
375 assert!(deleted);
376
377 let fetched = repo
378 .get_by_id(created.id, user_id)
379 .await
380 .expect("Failed to get account");
381 assert!(fetched.is_none());
382 }
383
384 #[tokio::test]
385 async fn test_delete_wrong_user_returns_false() {
386 let pool = common::setup_test_db().await;
387 let user1 = common::create_test_user(&pool).await;
388 let user2 = common::create_test_user(&pool).await;
389 let repo = SqliteEmailAccountRepository::new(pool.clone());
390
391 let created = repo
392 .create(
393 user1,
394 "User1 Email",
395 "user1@example.com",
396 "imap.example.com",
397 993,
398 "smtp.example.com",
399 587,
400 "user1",
401 "pass",
402 true,
403 None,
404 )
405 .await
406 .expect("Failed to create account");
407
408 // User 2 cannot delete user 1's account
409 let deleted = repo
410 .delete(created.id, user2)
411 .await
412 .expect("Failed to attempt delete");
413 assert!(!deleted);
414
415 // Account still exists for user 1
416 let fetched = repo
417 .get_by_id(created.id, user1)
418 .await
419 .expect("Failed to get account");
420 assert!(fetched.is_some());
421 }
422
423 #[tokio::test]
424 async fn test_update_last_sync_sets_timestamp() {
425 let pool = common::setup_test_db().await;
426 let user_id = common::create_test_user(&pool).await;
427 let repo = SqliteEmailAccountRepository::new(pool.clone());
428
429 let created = repo
430 .create(
431 user_id,
432 "Sync Test",
433 "sync@example.com",
434 "imap.example.com",
435 993,
436 "smtp.example.com",
437 587,
438 "sync",
439 "pass",
440 true,
441 None,
442 )
443 .await
444 .expect("Failed to create account");
445
446 assert!(created.last_sync_at.is_none());
447
448 let synced = repo
449 .update_last_sync(created.id, user_id)
450 .await
451 .expect("Failed to update last sync");
452 assert!(synced);
453
454 let fetched = repo
455 .get_by_id(created.id, user_id)
456 .await
457 .expect("Failed to get account")
458 .unwrap();
459 assert!(fetched.last_sync_at.is_some());
460 }
461
462 #[tokio::test]
463 async fn test_update_sync_interval() {
464 let pool = common::setup_test_db().await;
465 let user_id = common::create_test_user(&pool).await;
466 let repo = SqliteEmailAccountRepository::new(pool.clone());
467
468 let created = repo
469 .create(
470 user_id,
471 "Interval Test",
472 "interval@example.com",
473 "imap.example.com",
474 993,
475 "smtp.example.com",
476 587,
477 "interval",
478 "pass",
479 true,
480 None,
481 )
482 .await
483 .expect("Failed to create account");
484
485 // Default sync_interval_minutes is 15 (set by migration)
486 assert_eq!(created.sync_interval_minutes, Some(15));
487
488 let updated = repo
489 .update_sync_interval(created.id, user_id, Some(30))
490 .await
491 .expect("Failed to update interval");
492
493 assert!(updated.is_some());
494 let updated = updated.unwrap();
495 assert_eq!(updated.sync_interval_minutes, Some(30));
496
497 // Disable sync interval
498 let disabled = repo
499 .update_sync_interval(created.id, user_id, None)
500 .await
501 .expect("Failed to disable interval");
502
503 assert!(disabled.is_some());
504 assert!(disabled.unwrap().sync_interval_minutes.is_none());
505 }
506
507 #[tokio::test]
508 async fn test_list_accounts_needing_sync_returns_due() {
509 let pool = common::setup_test_db().await;
510 let user_id = common::create_test_user(&pool).await;
511 let repo = SqliteEmailAccountRepository::new(pool.clone());
512
513 let account = repo
514 .create(
515 user_id,
516 "Auto Sync",
517 "autosync@example.com",
518 "imap.example.com",
519 993,
520 "smtp.example.com",
521 587,
522 "autosync",
523 "pass",
524 true,
525 None,
526 )
527 .await
528 .expect("Failed to create account");
529
530 // Enable sync with 1-minute interval
531 repo.update_sync_interval(account.id, user_id, Some(1))
532 .await
533 .expect("Failed to set interval");
534
535 // Set last_sync_at to 2 minutes ago so the account is due
536 sqlx::query("UPDATE email_accounts SET last_sync_at = datetime('now', '-2 minutes') WHERE id = ?")
537 .bind(account.id.to_string())
538 .execute(&pool)
539 .await
540 .unwrap();
541
542 let needing_sync = repo
543 .list_accounts_needing_sync(user_id)
544 .await
545 .expect("Failed to list accounts needing sync");
546 assert_eq!(needing_sync.len(), 1);
547 assert_eq!(needing_sync[0].id, account.id);
548 }
549