WP-02: encrypted settings, SMTP mail, job runner and cron scheduler
AES-256-GCM secret storage keyed by MASTER_KEY, SMTP settings with test mail (lettre), persisted job runs with log and status, JobRunner with per-kind concurrency guard, 6-field cron schedules with defaults, scheduler loop. Settings and Jobs pages in the UI. FAKE_HOST mode for dev machines. Tests: 30 application, 8 infrastructure, 23 API, 16 Vitest, 6 Playwright. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@ -1,11 +1,22 @@
|
||||
//! Helpers to build a fully wired app on an in-memory database for integration tests.
|
||||
use crate::{build_app, AppState, Config};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use application::{JobHandler, JobLog, JobRunner};
|
||||
use async_trait::async_trait;
|
||||
use axum::Router;
|
||||
use domain::jobs::JobKind;
|
||||
use domain::ports::Mailer;
|
||||
use domain::settings::SmtpSettings;
|
||||
use domain::DomainError;
|
||||
|
||||
use crate::{build_app, AppState, Config};
|
||||
|
||||
pub fn test_config() -> Config {
|
||||
Config {
|
||||
database_url: "sqlite::memory:".into(),
|
||||
jwt_secret: "test-secret-test-secret-test-secret-1234".into(),
|
||||
master_key: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f".into(),
|
||||
fake_host: true,
|
||||
bind: "127.0.0.1:0".parse().unwrap(),
|
||||
bootstrap_admin: None,
|
||||
cookie_secure: false,
|
||||
@ -13,6 +24,45 @@ pub fn test_config() -> Config {
|
||||
}
|
||||
}
|
||||
|
||||
/// Mails "sent" by all test apps of this process.
|
||||
static SENT: Mutex<Vec<(Vec<String>, String, String)>> = Mutex::new(Vec::new());
|
||||
|
||||
pub struct RecordingMailer;
|
||||
|
||||
#[async_trait]
|
||||
impl Mailer for RecordingMailer {
|
||||
async fn send(
|
||||
&self,
|
||||
_: &SmtpSettings,
|
||||
to: &[String],
|
||||
subject: &str,
|
||||
body: &str,
|
||||
) -> Result<(), DomainError> {
|
||||
SENT.lock()
|
||||
.unwrap()
|
||||
.push((to.to_vec(), subject.into(), body.into()));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sent_mails(_app: &Router) -> Vec<(Vec<String>, String, String)> {
|
||||
SENT.lock().unwrap().clone()
|
||||
}
|
||||
|
||||
pub struct TestJob;
|
||||
|
||||
#[async_trait]
|
||||
impl JobHandler for TestJob {
|
||||
async fn run(&self, _params: Option<String>, log: &dyn JobLog) -> Result<(), String> {
|
||||
log.line("test handler ran").await;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_test_jobs(runner: JobRunner) -> JobRunner {
|
||||
runner.register(JobKind::PackageRefresh, Arc::new(TestJob))
|
||||
}
|
||||
|
||||
pub async fn build_test_app() -> Router {
|
||||
build_test_app_with(test_config()).await
|
||||
}
|
||||
@ -29,7 +79,8 @@ async fn build_test_app_with(cfg: Config) -> Router {
|
||||
let pool = infrastructure::connect(&cfg.database_url)
|
||||
.await
|
||||
.expect("db");
|
||||
let state = AppState::new(cfg, pool);
|
||||
let state = AppState::with_adapters(cfg, pool, Arc::new(RecordingMailer), register_test_jobs)
|
||||
.expect("state");
|
||||
state.bootstrap().await.expect("bootstrap");
|
||||
build_app(state)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user