//! Integration tests for SqliteUserRepository. #[allow(dead_code)] mod common; use goingson_core::UserRepository; use goingson_db_sqlite::SqliteUserRepository; #[tokio::test] async fn test_create_hashes_password_and_lowercases_email() { let pool = common::setup_test_db().await; let repo = SqliteUserRepository::new(pool); let user = repo .create("Alice@Example.COM", "correct horse battery staple", "Alice") .await .expect("Failed to create user"); assert_eq!( user.email, "alice@example.com", "email must be stored lowercased" ); assert_eq!(user.display_name, "Alice"); assert_ne!( user.password_hash, "correct horse battery staple", "password must not be stored in plaintext" ); assert!( user.password_hash.starts_with("$argon2"), "stored hash should be a PHC Argon2 string, got: {}", user.password_hash ); assert!( user.last_login_at.is_none(), "a freshly created user has never logged in" ); } #[tokio::test] async fn test_find_by_email_found_and_not_found() { let pool = common::setup_test_db().await; let repo = SqliteUserRepository::new(pool); repo.create("bob@example.com", "hunter2hunter2", "Bob") .await .expect("create"); let found = repo.find_by_email("bob@example.com").await.expect("find"); assert!(found.is_some()); assert_eq!(found.unwrap().display_name, "Bob"); let missing = repo .find_by_email("nobody@example.com") .await .expect("find"); assert!(missing.is_none(), "unknown email must return None"); } #[tokio::test] async fn test_find_by_email_is_case_insensitive() { let pool = common::setup_test_db().await; let repo = SqliteUserRepository::new(pool); let created = repo .create("carol@example.com", "passwordpassword", "Carol") .await .expect("create"); let found = repo .find_by_email("CAROL@EXAMPLE.COM") .await .expect("find") .expect("should match despite case"); assert_eq!( found.id, created.id, "lookup must fold case to match the stored lowercase email" ); } #[tokio::test] async fn test_authenticate_correct_password_succeeds() { let pool = common::setup_test_db().await; let repo = SqliteUserRepository::new(pool); let created = repo .create("dave@example.com", "s3cr3t-passphrase", "Dave") .await .expect("create"); assert!(created.last_login_at.is_none()); let authed = repo .authenticate("dave@example.com", "s3cr3t-passphrase") .await .expect("authenticate"); assert!(authed.is_some(), "correct password must authenticate"); assert_eq!(authed.unwrap().id, created.id); // authenticate() records a successful login timestamp. let after = repo .find_by_email("dave@example.com") .await .expect("find") .unwrap(); assert!( after.last_login_at.is_some(), "successful authentication must stamp last_login_at" ); } #[tokio::test] async fn test_authenticate_wrong_password_fails() { let pool = common::setup_test_db().await; let repo = SqliteUserRepository::new(pool); repo.create("erin@example.com", "the-right-password", "Erin") .await .expect("create"); let result = repo .authenticate("erin@example.com", "the-wrong-password") .await .expect("authenticate"); assert!(result.is_none(), "wrong password must not authenticate"); // A failed login must not stamp last_login_at. let after = repo .find_by_email("erin@example.com") .await .expect("find") .unwrap(); assert!( after.last_login_at.is_none(), "failed authentication must leave last_login_at untouched" ); } #[tokio::test] async fn test_authenticate_unknown_email_returns_none() { let pool = common::setup_test_db().await; let repo = SqliteUserRepository::new(pool); let result = repo .authenticate("ghost@example.com", "whatever") .await .expect("authenticate"); assert!( result.is_none(), "authenticating an unknown email must return None, not error" ); } #[tokio::test] async fn test_update_last_login_sets_timestamp() { let pool = common::setup_test_db().await; let repo = SqliteUserRepository::new(pool); let user = repo .create("frank@example.com", "frankspassword", "Frank") .await .expect("create"); assert!(user.last_login_at.is_none()); repo.update_last_login(user.id) .await .expect("update_last_login"); let refreshed = repo .find_by_email("frank@example.com") .await .expect("find") .unwrap(); assert!( refreshed.last_login_at.is_some(), "update_last_login must persist a timestamp" ); }