WP-02: contracts and failing tests for settings, mail, jobs and scheduler
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@ -185,3 +185,123 @@ pub fn fixture() -> Fixture {
|
||||
svc,
|
||||
}
|
||||
}
|
||||
|
||||
use domain::jobs::{JobKind, JobRun, JobStatus};
|
||||
use domain::ports::{Cipher, JobRunRepository, Mailer, SettingsRepository};
|
||||
use domain::settings::SmtpSettings;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct MemSettings(pub Mutex<HashMap<String, String>>);
|
||||
|
||||
#[async_trait]
|
||||
impl SettingsRepository for MemSettings {
|
||||
async fn get(&self, key: &str) -> Result<Option<String>, DomainError> {
|
||||
Ok(self.0.lock().unwrap().get(key).cloned())
|
||||
}
|
||||
async fn set(&self, key: &str, value: &str) -> Result<(), DomainError> {
|
||||
self.0.lock().unwrap().insert(key.into(), value.into());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Reversible "encryption" so tests can assert the stored value is not plain text.
|
||||
pub struct FakeCipher;
|
||||
impl Cipher for FakeCipher {
|
||||
fn encrypt(&self, plain: &str) -> Result<String, DomainError> {
|
||||
Ok(format!("enc:{}", plain.chars().rev().collect::<String>()))
|
||||
}
|
||||
fn decrypt(&self, c: &str) -> Result<String, DomainError> {
|
||||
c.strip_prefix("enc:")
|
||||
.map(|s| s.chars().rev().collect())
|
||||
.ok_or(DomainError::Storage("bad cipher text".into()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct MemMailer(pub Mutex<Vec<(Vec<String>, String, String)>>);
|
||||
|
||||
#[async_trait]
|
||||
impl Mailer for MemMailer {
|
||||
async fn send(
|
||||
&self,
|
||||
_smtp: &SmtpSettings,
|
||||
to: &[String],
|
||||
subject: &str,
|
||||
body: &str,
|
||||
) -> Result<(), DomainError> {
|
||||
self.0
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push((to.to_vec(), subject.into(), body.into()));
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct MemJobRuns(pub Mutex<Vec<JobRun>>);
|
||||
|
||||
#[async_trait]
|
||||
impl JobRunRepository for MemJobRuns {
|
||||
async fn insert(&self, run: &JobRun) -> Result<(), DomainError> {
|
||||
self.0.lock().unwrap().push(run.clone());
|
||||
Ok(())
|
||||
}
|
||||
async fn append_log(&self, id: Uuid, line: &str) -> Result<(), DomainError> {
|
||||
let mut v = self.0.lock().unwrap();
|
||||
let r = v
|
||||
.iter_mut()
|
||||
.find(|r| r.id == id)
|
||||
.ok_or(DomainError::NotFound)?;
|
||||
r.log.push_str(line);
|
||||
r.log.push('\n');
|
||||
Ok(())
|
||||
}
|
||||
async fn finish(&self, id: Uuid, status: JobStatus) -> Result<(), DomainError> {
|
||||
let mut v = self.0.lock().unwrap();
|
||||
let r = v
|
||||
.iter_mut()
|
||||
.find(|r| r.id == id)
|
||||
.ok_or(DomainError::NotFound)?;
|
||||
r.status = status;
|
||||
r.finished_at = Some(Utc::now());
|
||||
Ok(())
|
||||
}
|
||||
async fn get(&self, id: Uuid) -> Result<Option<JobRun>, DomainError> {
|
||||
Ok(self.0.lock().unwrap().iter().find(|r| r.id == id).cloned())
|
||||
}
|
||||
async fn list(&self, limit: u32) -> Result<Vec<JobRun>, DomainError> {
|
||||
let v = self.0.lock().unwrap();
|
||||
Ok(v.iter().rev().take(limit as usize).cloned().collect())
|
||||
}
|
||||
async fn find_running(&self, kind: JobKind) -> Result<Option<JobRun>, DomainError> {
|
||||
Ok(self
|
||||
.0
|
||||
.lock()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.find(|r| r.kind == kind && r.status == JobStatus::Running)
|
||||
.cloned())
|
||||
}
|
||||
async fn last_finished(&self, kind: JobKind) -> Result<Option<JobRun>, DomainError> {
|
||||
Ok(self
|
||||
.0
|
||||
.lock()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.rev()
|
||||
.find(|r| r.kind == kind && r.status != JobStatus::Running)
|
||||
.cloned())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn smtp() -> SmtpSettings {
|
||||
SmtpSettings {
|
||||
host: "mail.example.com".into(),
|
||||
port: 587,
|
||||
security: domain::settings::SmtpSecurity::StartTls,
|
||||
username: "bot".into(),
|
||||
password: "s3cret".into(),
|
||||
from: "monitoring@example.com".into(),
|
||||
notify_to: vec!["ops@example.com".into()],
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user