use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use crate::email::Email;
use crate::jobs::{Job, JobContext, JobError, JobResult};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SendEmailJob {
pub email: Email,
}
impl SendEmailJob {
#[must_use]
pub const fn new(email: Email) -> Self {
Self { email }
}
}
#[async_trait]
impl Job for SendEmailJob {
type Result = ();
async fn execute(&self, ctx: &JobContext) -> JobResult<Self::Result> {
self.email.validate()
.map_err(|e| JobError::ExecutionFailed(format!("Email validation failed: {e}")))?;
let Some(email_sender) = ctx.email_sender() else {
return Err(JobError::ExecutionFailed(
"Email sender not available in JobContext".to_string()
));
};
email_sender.send(self.email.clone()).await
.map_err(|e| JobError::ExecutionFailed(format!("Email send failed: {e}")))?;
Ok(())
}
fn max_retries(&self) -> u32 {
3
}
fn timeout(&self) -> std::time::Duration {
std::time::Duration::from_secs(30)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::email::sender::MockEmailSender; use std::sync::Arc;
#[test]
fn test_send_email_job_creation() {
let email = Email::new()
.to("user@example.com")
.from("noreply@myapp.com")
.subject("Test")
.text("Hello");
let job = SendEmailJob::new(email.clone());
assert_eq!(job.email.to, email.to);
assert_eq!(job.email.from, email.from);
assert_eq!(job.email.subject, email.subject);
}
#[test]
fn test_send_email_job_serialization() {
let email = Email::new()
.to("user@example.com")
.from("noreply@myapp.com")
.subject("Test")
.text("Hello");
let job = SendEmailJob::new(email);
let serialized = serde_json::to_string(&job).unwrap();
let deserialized: SendEmailJob = serde_json::from_str(&serialized).unwrap();
assert_eq!(job.email.to, deserialized.email.to);
assert_eq!(job.email.from, deserialized.email.from);
}
#[tokio::test]
async fn test_send_email_job_execute_with_sender() {
let mut mock_sender = MockEmailSender::new();
mock_sender.expect_send().times(1).returning(|_| Ok(()));
let ctx = JobContext::new().with_email_sender(Arc::new(mock_sender));
let email = Email::new()
.to("user@example.com")
.from("noreply@myapp.com")
.subject("Test")
.text("Hello");
let job = SendEmailJob::new(email);
let result = job.execute(&ctx).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_send_email_job_no_sender() {
let ctx = JobContext::new();
let email = Email::new()
.to("user@example.com")
.from("noreply@myapp.com")
.subject("Test")
.text("Hello");
let job = SendEmailJob::new(email);
let result = job.execute(&ctx).await;
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("not available"));
}
#[tokio::test]
async fn test_send_email_job_invalid_email() {
let mut mock_sender = MockEmailSender::new();
mock_sender.expect_send().times(0);
let ctx = JobContext::new().with_email_sender(Arc::new(mock_sender));
let email = Email::new()
.from("noreply@myapp.com")
.subject("Test")
.text("Hello");
let job = SendEmailJob::new(email);
let result = job.execute(&ctx).await;
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("validation failed"));
}
}