code refactor
Some checks failed
CI / Check / Test (push) Has been cancelled

Reviewed-on: #26
This commit was merged in pull request #26.
This commit is contained in:
2026-10-26 09:36:23 +00:00
parent 2ce7db12c2
commit 017b58d6fa
804 changed files with 33423 additions and 28955 deletions

View File

@@ -0,0 +1,83 @@
use crate::support::TestContextBuilder;
use crate::support::{login_command, register_command};
use std::sync::Arc;
use domain::testing::InMemoryStore;
use application::auth::{
commands::RegisterCommand,
deps::{LoginDeps, RegisterDeps},
login, register,
};
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(
&deps,
RegisterCommand {
password: password.to_string(),
..register_command(email, "testuser")
},
)
.await
.unwrap();
}
#[tokio::test]
async fn login_with_valid_credentials_returns_a_token() {
let users = InMemoryStore::new();
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(&deps, login_command("carol@example.com", "secret123"))
.await
.unwrap();
assert!(!result.token.is_empty());
assert!(!result.refresh_token.is_empty());
assert_eq!(result.email, "carol@example.com");
}
#[tokio::test]
async fn login_with_the_wrong_password_fails() {
let users = InMemoryStore::new();
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(&deps, login_command("dave@example.com", "wrong_password")).await;
assert!(result.is_err());
}
#[tokio::test]
async fn login_with_an_unknown_email_fails() {
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(&deps, login_command("nobody@example.com", "anything")).await;
assert!(result.is_err());
}

View File

@@ -0,0 +1,61 @@
use crate::support::TestContextBuilder;
use crate::support::{login_command, register_command};
use std::sync::Arc;
use domain::testing::InMemoryStore;
use application::auth::{
deps::{LoginDeps, LogoutDeps, RefreshDeps, RegisterDeps},
login, logout, refresh, register,
};
#[tokio::test]
async fn logout_revokes_refresh_token() {
let users = InMemoryStore::new();
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(&reg_deps, register_command("bob@example.com", "bob"))
.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(&login_deps, login_command("bob@example.com", "password123"))
.await
.unwrap();
let logout_deps = LogoutDeps {
refresh_session: b.refresh_session_repo.clone(),
};
logout::execute(&logout_deps, &login_result.refresh_token)
.await
.unwrap();
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 b = TestContextBuilder::new();
let logout_deps = LogoutDeps {
refresh_session: b.refresh_session_repo.clone(),
};
let result = logout::execute(&logout_deps, "nonexistent-token").await;
assert!(result.is_ok());
}

View File

@@ -0,0 +1,106 @@
use crate::support::TestContextBuilder;
use crate::support::{login_command, register_command};
use std::sync::Arc;
use domain::testing::InMemoryStore;
use application::auth::{
deps::{LoginDeps, RefreshDeps, RegisterDeps},
login, refresh, register,
};
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(&reg_deps, register_command("alice@example.com", "alice"))
.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(
&login_deps,
login_command("alice@example.com", "password123"),
)
.await
.unwrap()
}
#[tokio::test]
async fn refresh_returns_new_tokens() {
let users = InMemoryStore::new();
let b = TestContextBuilder::new().with_users(Arc::clone(&users) as _);
let login_result = login_user(&b).await;
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();
assert!(!result.token.is_empty());
assert!(!result.refresh_token.is_empty());
assert_ne!(result.refresh_token, login_result.refresh_token);
}
#[tokio::test]
async fn refresh_rotates_token_old_one_invalid() {
let users = InMemoryStore::new();
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();
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(&deps, &old_token).await;
assert!(retry.is_err());
}
#[tokio::test]
async fn refresh_with_new_token_works() {
let users = InMemoryStore::new();
let b = TestContextBuilder::new().with_users(Arc::clone(&users) as _);
let login_result = login_user(&b).await;
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(&deps, &first.refresh_token).await.unwrap();
assert!(!second.token.is_empty());
assert_ne!(second.refresh_token, first.refresh_token);
}
#[tokio::test]
async fn refresh_with_unknown_token_fails() {
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

@@ -0,0 +1,30 @@
use crate::support::TestContextBuilder;
use application::auth::commands::RegisterAndLoginCommand;
use application::auth::deps::RegisterAndLoginDeps;
use application::auth::register_and_login;
#[tokio::test]
async fn registers_and_returns_token() {
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(
&deps,
RegisterAndLoginCommand {
email: "new@example.com".into(),
username: "newuser".into(),
password: "password123".into(),
},
)
.await
.unwrap();
assert!(!result.token.is_empty());
assert_eq!(result.email, "new@example.com");
}

View File

@@ -0,0 +1,103 @@
use crate::support::TestContextBuilder;
use crate::support::register_command;
use std::sync::Arc;
use domain::ports::UserRepository;
use domain::testing::InMemoryStore;
use domain::value_objects::Email;
use application::auth::{commands::RegisterCommand, deps::RegisterDeps, register};
fn cmd(email: &str) -> RegisterCommand {
register_command(email, "alice")
}
#[tokio::test]
async fn registering_creates_the_user() {
let users = InMemoryStore::new();
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(&deps, cmd("alice@example.com"))
.await
.unwrap();
let email = Email::new("alice@example.com".into()).unwrap();
let user = users.find_by_email(&email).await.unwrap().unwrap();
assert_eq!(user.email().value(), "alice@example.com");
assert!(user.password_hash().value().starts_with("hashed:"));
}
#[tokio::test]
async fn registering_with_a_duplicate_email_fails() {
let users = InMemoryStore::new();
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(&deps, cmd("bob@example.com"))
.await
.unwrap();
let result = register::execute(&deps, cmd("bob@example.com")).await;
assert!(result.is_err(), "duplicate email should fail");
}
#[tokio::test]
async fn registering_with_a_short_password_fails() {
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(
&deps,
RegisterCommand {
password: "short".to_string(),
..register_command("x@y.com", "testuser")
},
)
.await;
let err = result.unwrap_err().to_string();
assert!(
err.contains("8 characters"),
"expected password length error, got: {err}"
);
}
#[tokio::test]
async fn registration_refuses_a_retired_username() {
let users = InMemoryStore::new();
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(&deps, register_command("old@example.com", "ghost"))
.await
.unwrap();
let old = users
.find_by_email(&Email::new("old@example.com".into()).unwrap())
.await
.unwrap()
.unwrap();
users.mark_deleted(old.id(), &[]).await.unwrap();
users.delete_user(old.id());
let err = register::execute(&deps, register_command("new@example.com", "Ghost"))
.await
.unwrap_err();
assert!(
matches!(&err, domain::errors::DomainError::ValidationError(m) if m == "Username already taken"),
"got {err:?}"
);
}