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;
|
||||
|
||||
/// 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]
|
||||
pub trait ChallengeVerifier: Send + Sync + std::fmt::Debug {
|
||||
async fn verify_dns01(&self, domain: &str, expected_token: &str) -> Result<bool, String>;
|
||||
async fn verify_http01(&self, domain: &str, expected_token: &str) -> Result<bool, String>;
|
||||
pub trait ChallengeVerifier: Send + Sync {
|
||||
async fn verify_dns(
|
||||
&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.
|
||||
#[derive(Debug)]
|
||||
/// Real implementation using DNS lookups and HTTP requests.
|
||||
pub struct RealChallengeVerifier;
|
||||
|
||||
#[async_trait]
|
||||
impl ChallengeVerifier for RealChallengeVerifier {
|
||||
async fn verify_dns01(&self, _domain: &str, _expected_token: &str) -> Result<bool, String> {
|
||||
// TODO: implement with hickory-resolver in a later task
|
||||
async fn verify_dns(
|
||||
&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)
|
||||
}
|
||||
|
||||
async fn verify_http01(&self, _domain: &str, _expected_token: &str) -> Result<bool, String> {
|
||||
// TODO: implement with reqwest in a later task
|
||||
Ok(false)
|
||||
async fn verify_http(
|
||||
&self,
|
||||
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