refactor: extract inline test modules to separate files
This commit is contained in:
@@ -1,89 +0,0 @@
|
||||
use async_trait::async_trait;
|
||||
use domain::{
|
||||
errors::DomainError,
|
||||
ports::{ApiKeyRepository, ApiKeyService},
|
||||
value_objects::UserId,
|
||||
};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct ApiKeyServiceImpl {
|
||||
repo: Arc<dyn ApiKeyRepository>,
|
||||
}
|
||||
|
||||
impl ApiKeyServiceImpl {
|
||||
pub fn new(repo: Arc<dyn ApiKeyRepository>) -> Self {
|
||||
Self { repo }
|
||||
}
|
||||
|
||||
fn hash(raw: &str) -> String {
|
||||
hex::encode(Sha256::digest(raw.as_bytes()))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ApiKeyService for ApiKeyServiceImpl {
|
||||
async fn validate_key(&self, raw_key: &str) -> Result<Option<UserId>, DomainError> {
|
||||
let hash = Self::hash(raw_key);
|
||||
Ok(self.repo.find_by_hash(&hash).await?.map(|k| k.user_id))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use async_trait::async_trait;
|
||||
use chrono::Utc;
|
||||
use domain::{
|
||||
errors::DomainError,
|
||||
models::api_key::ApiKey,
|
||||
ports::ApiKeyRepository,
|
||||
value_objects::{ApiKeyId, UserId},
|
||||
};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
struct FakeApiKeyRepo(Mutex<Vec<ApiKey>>);
|
||||
|
||||
#[async_trait]
|
||||
impl ApiKeyRepository for FakeApiKeyRepo {
|
||||
async fn save(&self, key: &ApiKey) -> Result<(), DomainError> {
|
||||
self.0.lock().unwrap().push(key.clone());
|
||||
Ok(())
|
||||
}
|
||||
async fn find_by_hash(&self, hash: &str) -> Result<Option<ApiKey>, DomainError> {
|
||||
Ok(self.0.lock().unwrap().iter().find(|k| k.key_hash == hash).cloned())
|
||||
}
|
||||
async fn list_for_user(&self, _uid: &UserId) -> Result<Vec<ApiKey>, DomainError> {
|
||||
Ok(vec![])
|
||||
}
|
||||
async fn delete(&self, _id: &ApiKeyId, _uid: &UserId) -> Result<(), DomainError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn validate_known_key_returns_user_id() {
|
||||
let uid = UserId::new();
|
||||
let raw = "super-secret-key";
|
||||
let hash = ApiKeyServiceImpl::hash(raw);
|
||||
let key = ApiKey {
|
||||
id: ApiKeyId::new(),
|
||||
user_id: uid.clone(),
|
||||
key_hash: hash,
|
||||
name: "test".into(),
|
||||
created_at: Utc::now(),
|
||||
};
|
||||
let repo = Arc::new(FakeApiKeyRepo(Mutex::new(vec![key])));
|
||||
let svc = ApiKeyServiceImpl::new(repo);
|
||||
let result = svc.validate_key(raw).await.unwrap();
|
||||
assert_eq!(result.unwrap().as_uuid(), uid.as_uuid());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn validate_unknown_key_returns_none() {
|
||||
let repo = Arc::new(FakeApiKeyRepo(Mutex::new(vec![])));
|
||||
let svc = ApiKeyServiceImpl::new(repo);
|
||||
let result = svc.validate_key("unknown-key").await.unwrap();
|
||||
assert!(result.is_none());
|
||||
}
|
||||
}
|
||||
33
crates/adapters/auth/src/api_key_service/mod.rs
Normal file
33
crates/adapters/auth/src/api_key_service/mod.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
use async_trait::async_trait;
|
||||
use domain::{
|
||||
errors::DomainError,
|
||||
ports::{ApiKeyRepository, ApiKeyService},
|
||||
value_objects::UserId,
|
||||
};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct ApiKeyServiceImpl {
|
||||
repo: Arc<dyn ApiKeyRepository>,
|
||||
}
|
||||
|
||||
impl ApiKeyServiceImpl {
|
||||
pub fn new(repo: Arc<dyn ApiKeyRepository>) -> Self {
|
||||
Self { repo }
|
||||
}
|
||||
|
||||
fn hash(raw: &str) -> String {
|
||||
hex::encode(Sha256::digest(raw.as_bytes()))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ApiKeyService for ApiKeyServiceImpl {
|
||||
async fn validate_key(&self, raw_key: &str) -> Result<Option<UserId>, DomainError> {
|
||||
let hash = Self::hash(raw_key);
|
||||
Ok(self.repo.find_by_hash(&hash).await?.map(|k| k.user_id))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
55
crates/adapters/auth/src/api_key_service/tests.rs
Normal file
55
crates/adapters/auth/src/api_key_service/tests.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
use super::*;
|
||||
use async_trait::async_trait;
|
||||
use chrono::Utc;
|
||||
use domain::{
|
||||
errors::DomainError,
|
||||
models::api_key::ApiKey,
|
||||
ports::ApiKeyRepository,
|
||||
value_objects::{ApiKeyId, UserId},
|
||||
};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
struct FakeApiKeyRepo(Mutex<Vec<ApiKey>>);
|
||||
|
||||
#[async_trait]
|
||||
impl ApiKeyRepository for FakeApiKeyRepo {
|
||||
async fn save(&self, key: &ApiKey) -> Result<(), DomainError> {
|
||||
self.0.lock().unwrap().push(key.clone());
|
||||
Ok(())
|
||||
}
|
||||
async fn find_by_hash(&self, hash: &str) -> Result<Option<ApiKey>, DomainError> {
|
||||
Ok(self.0.lock().unwrap().iter().find(|k| k.key_hash == hash).cloned())
|
||||
}
|
||||
async fn list_for_user(&self, _uid: &UserId) -> Result<Vec<ApiKey>, DomainError> {
|
||||
Ok(vec![])
|
||||
}
|
||||
async fn delete(&self, _id: &ApiKeyId, _uid: &UserId) -> Result<(), DomainError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn validate_known_key_returns_user_id() {
|
||||
let uid = UserId::new();
|
||||
let raw = "super-secret-key";
|
||||
let hash = ApiKeyServiceImpl::hash(raw);
|
||||
let key = ApiKey {
|
||||
id: ApiKeyId::new(),
|
||||
user_id: uid.clone(),
|
||||
key_hash: hash,
|
||||
name: "test".into(),
|
||||
created_at: Utc::now(),
|
||||
};
|
||||
let repo = Arc::new(FakeApiKeyRepo(Mutex::new(vec![key])));
|
||||
let svc = ApiKeyServiceImpl::new(repo);
|
||||
let result = svc.validate_key(raw).await.unwrap();
|
||||
assert_eq!(result.unwrap().as_uuid(), uid.as_uuid());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn validate_unknown_key_returns_none() {
|
||||
let repo = Arc::new(FakeApiKeyRepo(Mutex::new(vec![])));
|
||||
let svc = ApiKeyServiceImpl::new(repo);
|
||||
let result = svc.validate_key("unknown-key").await.unwrap();
|
||||
assert!(result.is_none());
|
||||
}
|
||||
@@ -93,31 +93,4 @@ impl PasswordHasher for Argon2PasswordHasher {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use domain::ports::AuthService;
|
||||
|
||||
#[test]
|
||||
fn generate_and_validate_token() {
|
||||
let svc = JwtAuthService::new("a-secret-that-is-at-least-32-bytes!!".into(), 3600);
|
||||
let id = UserId::new();
|
||||
let tok = svc.generate_token(&id).unwrap();
|
||||
let parsed = svc.validate_token(&tok.token).unwrap();
|
||||
assert_eq!(parsed.as_uuid(), id.as_uuid());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_token_returns_unauthorized() {
|
||||
let svc = JwtAuthService::new("a-secret-that-is-at-least-32-bytes!!".into(), 3600);
|
||||
let err = svc.validate_token("not.a.token").unwrap_err();
|
||||
assert!(matches!(err, DomainError::Unauthorized));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn hash_and_verify() {
|
||||
let hasher = Argon2PasswordHasher;
|
||||
let hash = hasher.hash("mypassword").await.unwrap();
|
||||
assert!(hasher.verify("mypassword", &hash).await.unwrap());
|
||||
assert!(!hasher.verify("wrongpassword", &hash).await.unwrap());
|
||||
}
|
||||
}
|
||||
mod tests;
|
||||
|
||||
26
crates/adapters/auth/src/tests.rs
Normal file
26
crates/adapters/auth/src/tests.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
use super::*;
|
||||
use domain::ports::AuthService;
|
||||
|
||||
#[test]
|
||||
fn generate_and_validate_token() {
|
||||
let svc = JwtAuthService::new("a-secret-that-is-at-least-32-bytes!!".into(), 3600);
|
||||
let id = UserId::new();
|
||||
let tok = svc.generate_token(&id).unwrap();
|
||||
let parsed = svc.validate_token(&tok.token).unwrap();
|
||||
assert_eq!(parsed.as_uuid(), id.as_uuid());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_token_returns_unauthorized() {
|
||||
let svc = JwtAuthService::new("a-secret-that-is-at-least-32-bytes!!".into(), 3600);
|
||||
let err = svc.validate_token("not.a.token").unwrap_err();
|
||||
assert!(matches!(err, DomainError::Unauthorized));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn hash_and_verify() {
|
||||
let hasher = Argon2PasswordHasher;
|
||||
let hash = hasher.hash("mypassword").await.unwrap();
|
||||
assert!(hasher.verify("mypassword", &hash).await.unwrap());
|
||||
assert!(!hasher.verify("wrongpassword", &hash).await.unwrap());
|
||||
}
|
||||
Reference in New Issue
Block a user