refactor(auth): LoginDeps, RegisterDeps, RefreshDeps, RegisterAndLoginDeps, RefreshSessionCleanupJob

This commit is contained in:
2026-06-11 22:58:42 +02:00
parent 70d1f10e3d
commit 9ca5ada924
18 changed files with 359 additions and 232 deletions

View File

@@ -4,15 +4,24 @@ use domain::models::UserRole;
use domain::testing::InMemoryUserRepository;
use crate::{
auth::commands::RegisterCommand,
auth::queries::LoginQuery,
auth::{login, register},
auth::{
commands::RegisterCommand,
deps::{LoginDeps, RegisterDeps},
login,
queries::LoginQuery,
register,
},
test_helpers::TestContextBuilder,
};
async fn setup_user(ctx: &crate::context::AppContext, email: &str, password: &str) {
async fn setup_user(b: &TestContextBuilder, email: &str, password: &str) {
let deps = RegisterDeps {
user: b.user_repo.clone(),
password_hasher: b.password_hasher.clone(),
config: b.config.clone(),
};
register::execute(
ctx,
&deps,
RegisterCommand {
email: email.to_string(),
username: "testuser".to_string(),
@@ -27,14 +36,18 @@ async fn setup_user(ctx: &crate::context::AppContext, email: &str, password: &st
#[tokio::test]
async fn test_login_valid_credentials_returns_token() {
let users = InMemoryUserRepository::new();
let ctx = TestContextBuilder::new()
.with_users(Arc::clone(&users) as _)
.build();
setup_user(&ctx, "carol@example.com", "secret123").await;
let b = TestContextBuilder::new().with_users(Arc::clone(&users) as _);
setup_user(&b, "carol@example.com", "secret123").await;
let deps = LoginDeps {
user: b.user_repo.clone(),
password_hasher: b.password_hasher.clone(),
auth: b.auth_service.clone(),
refresh_session: b.refresh_session_repo.clone(),
config: b.config.clone(),
};
let result = login::execute(
&ctx,
&deps,
LoginQuery {
email: "carol@example.com".into(),
password: "secret123".into(),
@@ -51,14 +64,18 @@ async fn test_login_valid_credentials_returns_token() {
#[tokio::test]
async fn test_login_wrong_password_fails() {
let users = InMemoryUserRepository::new();
let ctx = TestContextBuilder::new()
.with_users(Arc::clone(&users) as _)
.build();
setup_user(&ctx, "dave@example.com", "correct_password").await;
let b = TestContextBuilder::new().with_users(Arc::clone(&users) as _);
setup_user(&b, "dave@example.com", "correct_password").await;
let deps = LoginDeps {
user: b.user_repo.clone(),
password_hasher: b.password_hasher.clone(),
auth: b.auth_service.clone(),
refresh_session: b.refresh_session_repo.clone(),
config: b.config.clone(),
};
let result = login::execute(
&ctx,
&deps,
LoginQuery {
email: "dave@example.com".into(),
password: "wrong_password".into(),
@@ -71,10 +88,16 @@ async fn test_login_wrong_password_fails() {
#[tokio::test]
async fn test_login_unknown_email_fails() {
let ctx = TestContextBuilder::new().build();
let b = TestContextBuilder::new();
let deps = LoginDeps {
user: b.user_repo.clone(),
password_hasher: b.password_hasher.clone(),
auth: b.auth_service.clone(),
refresh_session: b.refresh_session_repo.clone(),
config: b.config.clone(),
};
let result = login::execute(
&ctx,
&deps,
LoginQuery {
email: "nobody@example.com".into(),
password: "anything".into(),

View File

@@ -4,21 +4,27 @@ use domain::models::UserRole;
use domain::testing::InMemoryUserRepository;
use crate::{
auth::commands::RegisterCommand,
auth::queries::LoginQuery,
auth::{login, logout, refresh, register},
auth::{
commands::RegisterCommand,
deps::{LoginDeps, RefreshDeps, RegisterDeps},
login, logout, refresh, register,
queries::LoginQuery,
},
test_helpers::TestContextBuilder,
};
#[tokio::test]
async fn logout_revokes_refresh_token() {
let users = InMemoryUserRepository::new();
let ctx = TestContextBuilder::new()
.with_users(Arc::clone(&users) as _)
.build();
let b = TestContextBuilder::new().with_users(Arc::clone(&users) as _);
let reg_deps = RegisterDeps {
user: b.user_repo.clone(),
password_hasher: b.password_hasher.clone(),
config: b.config.clone(),
};
register::execute(
&ctx,
&reg_deps,
RegisterCommand {
email: "bob@example.com".to_string(),
username: "bob".to_string(),
@@ -29,8 +35,15 @@ async fn logout_revokes_refresh_token() {
.await
.unwrap();
let login_deps = LoginDeps {
user: b.user_repo.clone(),
password_hasher: b.password_hasher.clone(),
auth: b.auth_service.clone(),
refresh_session: b.refresh_session_repo.clone(),
config: b.config.clone(),
};
let login_result = login::execute(
&ctx,
&login_deps,
LoginQuery {
email: "bob@example.com".into(),
password: "password123".into(),
@@ -39,17 +52,25 @@ async fn logout_revokes_refresh_token() {
.await
.unwrap();
logout::execute(&ctx, &login_result.refresh_token)
.await
.unwrap();
logout::execute(
b.refresh_session_repo.clone(),
&login_result.refresh_token,
)
.await
.unwrap();
let refresh_attempt = refresh::execute(&ctx, &login_result.refresh_token).await;
let refresh_deps = RefreshDeps {
refresh_session: b.refresh_session_repo.clone(),
auth: b.auth_service.clone(),
config: b.config.clone(),
};
let refresh_attempt = refresh::execute(&refresh_deps, &login_result.refresh_token).await;
assert!(refresh_attempt.is_err());
}
#[tokio::test]
async fn logout_with_unknown_token_succeeds() {
let ctx = TestContextBuilder::new().build();
let result = logout::execute(&ctx, "nonexistent-token").await;
let b = TestContextBuilder::new();
let result = logout::execute(b.refresh_session_repo.clone(), "nonexistent-token").await;
assert!(result.is_ok());
}

View File

@@ -4,15 +4,23 @@ use domain::models::UserRole;
use domain::testing::InMemoryUserRepository;
use crate::{
auth::commands::RegisterCommand,
auth::queries::LoginQuery,
auth::{login, refresh, register},
auth::{
commands::RegisterCommand,
deps::{LoginDeps, RefreshDeps, RegisterDeps},
login, refresh, register,
queries::LoginQuery,
},
test_helpers::TestContextBuilder,
};
async fn login_user(ctx: &crate::context::AppContext) -> login::LoginResult {
async fn login_user(b: &TestContextBuilder) -> login::LoginResult {
let reg_deps = RegisterDeps {
user: b.user_repo.clone(),
password_hasher: b.password_hasher.clone(),
config: b.config.clone(),
};
register::execute(
ctx,
&reg_deps,
RegisterCommand {
email: "alice@example.com".to_string(),
username: "alice".to_string(),
@@ -23,8 +31,15 @@ async fn login_user(ctx: &crate::context::AppContext) -> login::LoginResult {
.await
.unwrap();
let login_deps = LoginDeps {
user: b.user_repo.clone(),
password_hasher: b.password_hasher.clone(),
auth: b.auth_service.clone(),
refresh_session: b.refresh_session_repo.clone(),
config: b.config.clone(),
};
login::execute(
ctx,
&login_deps,
LoginQuery {
email: "alice@example.com".into(),
password: "password123".into(),
@@ -37,13 +52,15 @@ async fn login_user(ctx: &crate::context::AppContext) -> login::LoginResult {
#[tokio::test]
async fn refresh_returns_new_tokens() {
let users = InMemoryUserRepository::new();
let ctx = TestContextBuilder::new()
.with_users(Arc::clone(&users) as _)
.build();
let b = TestContextBuilder::new().with_users(Arc::clone(&users) as _);
let login_result = login_user(&b).await;
let login_result = login_user(&ctx).await;
let result = refresh::execute(&ctx, &login_result.refresh_token)
let deps = RefreshDeps {
refresh_session: b.refresh_session_repo.clone(),
auth: b.auth_service.clone(),
config: b.config.clone(),
};
let result = refresh::execute(&deps, &login_result.refresh_token)
.await
.unwrap();
@@ -55,33 +72,37 @@ async fn refresh_returns_new_tokens() {
#[tokio::test]
async fn refresh_rotates_token_old_one_invalid() {
let users = InMemoryUserRepository::new();
let ctx = TestContextBuilder::new()
.with_users(Arc::clone(&users) as _)
.build();
let login_result = login_user(&ctx).await;
let b = TestContextBuilder::new().with_users(Arc::clone(&users) as _);
let login_result = login_user(&b).await;
let old_token = login_result.refresh_token.clone();
refresh::execute(&ctx, &old_token).await.unwrap();
let deps = RefreshDeps {
refresh_session: b.refresh_session_repo.clone(),
auth: b.auth_service.clone(),
config: b.config.clone(),
};
refresh::execute(&deps, &old_token).await.unwrap();
let retry = refresh::execute(&ctx, &old_token).await;
let retry = refresh::execute(&deps, &old_token).await;
assert!(retry.is_err());
}
#[tokio::test]
async fn refresh_with_new_token_works() {
let users = InMemoryUserRepository::new();
let ctx = TestContextBuilder::new()
.with_users(Arc::clone(&users) as _)
.build();
let b = TestContextBuilder::new().with_users(Arc::clone(&users) as _);
let login_result = login_user(&b).await;
let login_result = login_user(&ctx).await;
let first = refresh::execute(&ctx, &login_result.refresh_token)
let deps = RefreshDeps {
refresh_session: b.refresh_session_repo.clone(),
auth: b.auth_service.clone(),
config: b.config.clone(),
};
let first = refresh::execute(&deps, &login_result.refresh_token)
.await
.unwrap();
let second = refresh::execute(&ctx, &first.refresh_token).await.unwrap();
let second = refresh::execute(&deps, &first.refresh_token).await.unwrap();
assert!(!second.token.is_empty());
assert_ne!(second.refresh_token, first.refresh_token);
@@ -89,8 +110,12 @@ async fn refresh_with_new_token_works() {
#[tokio::test]
async fn refresh_with_unknown_token_fails() {
let ctx = TestContextBuilder::new().build();
let result = refresh::execute(&ctx, "nonexistent-token").await;
let b = TestContextBuilder::new();
let deps = RefreshDeps {
refresh_session: b.refresh_session_repo.clone(),
auth: b.auth_service.clone(),
config: b.config.clone(),
};
let result = refresh::execute(&deps, "nonexistent-token").await;
assert!(result.is_err());
}

View File

@@ -5,7 +5,10 @@ use domain::ports::UserRepository;
use domain::testing::InMemoryUserRepository;
use domain::value_objects::Email;
use crate::{auth::commands::RegisterCommand, auth::register, test_helpers::TestContextBuilder};
use crate::{
auth::{commands::RegisterCommand, deps::RegisterDeps, register},
test_helpers::TestContextBuilder,
};
fn cmd(email: &str) -> RegisterCommand {
RegisterCommand {
@@ -19,11 +22,14 @@ fn cmd(email: &str) -> RegisterCommand {
#[tokio::test]
async fn test_register_creates_user() {
let users = InMemoryUserRepository::new();
let ctx = TestContextBuilder::new()
.with_users(Arc::clone(&users) as _)
.build();
let b = TestContextBuilder::new().with_users(Arc::clone(&users) as _);
let deps = RegisterDeps {
user: b.user_repo.clone(),
password_hasher: b.password_hasher.clone(),
config: b.config.clone(),
};
register::execute(&ctx, cmd("alice@example.com"))
register::execute(&deps, cmd("alice@example.com"))
.await
.unwrap();
@@ -36,22 +42,30 @@ async fn test_register_creates_user() {
#[tokio::test]
async fn test_register_duplicate_email_fails() {
let users = InMemoryUserRepository::new();
let ctx = TestContextBuilder::new()
.with_users(Arc::clone(&users) as _)
.build();
let b = TestContextBuilder::new().with_users(Arc::clone(&users) as _);
let deps = RegisterDeps {
user: b.user_repo.clone(),
password_hasher: b.password_hasher.clone(),
config: b.config.clone(),
};
register::execute(&ctx, cmd("bob@example.com"))
register::execute(&deps, cmd("bob@example.com"))
.await
.unwrap();
let result = register::execute(&ctx, cmd("bob@example.com")).await;
let result = register::execute(&deps, cmd("bob@example.com")).await;
assert!(result.is_err(), "duplicate email should fail");
}
#[tokio::test]
async fn test_register_short_password_fails() {
let ctx = TestContextBuilder::new().build();
let b = TestContextBuilder::new();
let deps = RegisterDeps {
user: b.user_repo.clone(),
password_hasher: b.password_hasher.clone(),
config: b.config.clone(),
};
let result = register::execute(
&ctx,
&deps,
RegisterCommand {
email: "x@y.com".to_string(),
username: "testuser".to_string(),

View File

@@ -1,13 +1,21 @@
use crate::auth::commands::RegisterAndLoginCommand;
use crate::auth::deps::RegisterAndLoginDeps;
use crate::auth::register_and_login;
use crate::test_helpers::TestContextBuilder;
#[tokio::test]
async fn registers_and_returns_token() {
let ctx = TestContextBuilder::new().build();
let b = TestContextBuilder::new();
let deps = RegisterAndLoginDeps {
user: b.user_repo.clone(),
password_hasher: b.password_hasher.clone(),
auth: b.auth_service.clone(),
refresh_session: b.refresh_session_repo.clone(),
config: b.config.clone(),
};
let result = register_and_login::execute(
&ctx,
&deps,
RegisterAndLoginCommand {
email: "new@example.com".into(),
username: "newuser".into(),