mirror of
https://github.com/CloudNebulaProject/webfingerd.git
synced 2026-04-10 13:10:41 +00:00
feat: add test helpers with in-memory DB and test state
This commit is contained in:
parent
1d4873ba75
commit
4b04cf9b76
2 changed files with 145 additions and 11 deletions
|
|
@ -1,25 +1,93 @@
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
|
||||||
/// Trait for verifying domain ownership challenges (DNS-01 and HTTP-01).
|
use crate::config::ChallengeConfig;
|
||||||
|
|
||||||
|
/// Trait for challenge verification — allows mocking in tests.
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait ChallengeVerifier: Send + Sync + std::fmt::Debug {
|
pub trait ChallengeVerifier: Send + Sync {
|
||||||
async fn verify_dns01(&self, domain: &str, expected_token: &str) -> Result<bool, String>;
|
async fn verify_dns(
|
||||||
async fn verify_http01(&self, domain: &str, expected_token: &str) -> Result<bool, String>;
|
&self,
|
||||||
|
domain: &str,
|
||||||
|
expected_token: &str,
|
||||||
|
config: &ChallengeConfig,
|
||||||
|
) -> Result<bool, String>;
|
||||||
|
|
||||||
|
async fn verify_http(
|
||||||
|
&self,
|
||||||
|
domain: &str,
|
||||||
|
expected_token: &str,
|
||||||
|
config: &ChallengeConfig,
|
||||||
|
) -> Result<bool, String>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Production implementation using real DNS and HTTP lookups.
|
/// Real implementation using DNS lookups and HTTP requests.
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct RealChallengeVerifier;
|
pub struct RealChallengeVerifier;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl ChallengeVerifier for RealChallengeVerifier {
|
impl ChallengeVerifier for RealChallengeVerifier {
|
||||||
async fn verify_dns01(&self, _domain: &str, _expected_token: &str) -> Result<bool, String> {
|
async fn verify_dns(
|
||||||
// TODO: implement with hickory-resolver in a later task
|
&self,
|
||||||
|
domain: &str,
|
||||||
|
expected_token: &str,
|
||||||
|
config: &ChallengeConfig,
|
||||||
|
) -> Result<bool, String> {
|
||||||
|
use hickory_resolver::TokioResolver;
|
||||||
|
|
||||||
|
let resolver = TokioResolver::builder_tokio()
|
||||||
|
.map_err(|e| format!("resolver error: {e}"))?
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let lookup_name = format!("{}.{}", config.dns_txt_prefix, domain);
|
||||||
|
let response = resolver
|
||||||
|
.txt_lookup(&lookup_name)
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("DNS lookup failed: {e}"))?;
|
||||||
|
|
||||||
|
for record in response.iter() {
|
||||||
|
let txt = record.to_string();
|
||||||
|
if txt.trim_matches('"') == expected_token {
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(false)
|
Ok(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn verify_http01(&self, _domain: &str, _expected_token: &str) -> Result<bool, String> {
|
async fn verify_http(
|
||||||
// TODO: implement with reqwest in a later task
|
&self,
|
||||||
Ok(false)
|
domain: &str,
|
||||||
|
expected_token: &str,
|
||||||
|
config: &ChallengeConfig,
|
||||||
|
) -> Result<bool, String> {
|
||||||
|
let url = format!(
|
||||||
|
"https://{}/{}/{}",
|
||||||
|
domain, config.http_well_known_path, expected_token
|
||||||
|
);
|
||||||
|
|
||||||
|
let client = reqwest::Client::builder()
|
||||||
|
.timeout(std::time::Duration::from_secs(10))
|
||||||
|
.build()
|
||||||
|
.map_err(|e| format!("HTTP client error: {e}"))?;
|
||||||
|
|
||||||
|
let response = client
|
||||||
|
.get(&url)
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("HTTP request failed: {e}"))?;
|
||||||
|
|
||||||
|
Ok(response.status().is_success())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mock that always succeeds — for testing.
|
||||||
|
pub struct MockChallengeVerifier;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl ChallengeVerifier for MockChallengeVerifier {
|
||||||
|
async fn verify_dns(&self, _: &str, _: &str, _: &ChallengeConfig) -> Result<bool, String> {
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
async fn verify_http(&self, _: &str, _: &str, _: &ChallengeConfig) -> Result<bool, String> {
|
||||||
|
Ok(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
66
tests/common/mod.rs
Normal file
66
tests/common/mod.rs
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
use sea_orm::{ConnectOptions, ConnectionTrait, Database, DatabaseConnection, Statement};
|
||||||
|
use sea_orm_migration::MigratorTrait;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use webfingerd::cache::Cache;
|
||||||
|
use webfingerd::config::*;
|
||||||
|
use webfingerd::state::AppState;
|
||||||
|
|
||||||
|
pub async fn setup_test_db() -> DatabaseConnection {
|
||||||
|
let opt = ConnectOptions::new("sqlite::memory:");
|
||||||
|
let db = Database::connect(opt).await.unwrap();
|
||||||
|
db.execute(Statement::from_string(
|
||||||
|
sea_orm::DatabaseBackend::Sqlite,
|
||||||
|
"PRAGMA journal_mode=WAL".to_string(),
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
migration::Migrator::up(&db, None).await.unwrap();
|
||||||
|
db
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn test_settings() -> Settings {
|
||||||
|
Settings {
|
||||||
|
server: ServerConfig {
|
||||||
|
listen: "127.0.0.1:0".into(),
|
||||||
|
base_url: "http://localhost:8080".into(),
|
||||||
|
},
|
||||||
|
database: DatabaseConfig {
|
||||||
|
path: ":memory:".into(),
|
||||||
|
wal_mode: true,
|
||||||
|
},
|
||||||
|
cache: CacheConfig {
|
||||||
|
reaper_interval_secs: 1,
|
||||||
|
},
|
||||||
|
rate_limit: RateLimitConfig {
|
||||||
|
public_rpm: 1000,
|
||||||
|
api_rpm: 1000,
|
||||||
|
batch_rpm: 100,
|
||||||
|
batch_max_links: 500,
|
||||||
|
},
|
||||||
|
challenge: ChallengeConfig {
|
||||||
|
dns_txt_prefix: "_webfinger-challenge".into(),
|
||||||
|
http_well_known_path: ".well-known/webfinger-verify".into(),
|
||||||
|
challenge_ttl_secs: 3600,
|
||||||
|
},
|
||||||
|
ui: UiConfig {
|
||||||
|
enabled: false,
|
||||||
|
session_secret: "test-secret-at-least-32-bytes-long-for-signing".into(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn test_state() -> AppState {
|
||||||
|
test_state_with_settings(test_settings()).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn test_state_with_settings(settings: Settings) -> AppState {
|
||||||
|
let db = setup_test_db().await;
|
||||||
|
let cache = Cache::new();
|
||||||
|
cache.hydrate(&db).await.unwrap();
|
||||||
|
AppState {
|
||||||
|
db,
|
||||||
|
cache,
|
||||||
|
settings: Arc::new(settings),
|
||||||
|
challenge_verifier: Arc::new(webfingerd::challenge::MockChallengeVerifier),
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue