graphile_worker 0.13.3

High performance Rust/PostgreSQL job queue (also suitable for getting jobs generated by PostgreSQL triggers/functions out into a different work queue)
Documentation
use super::*;

#[tokio::test]
async fn test_job_fails_with_error() {
    #[derive(Serialize, Deserialize)]
    struct FailingJob {
        message: String,
    }

    impl TaskHandler for FailingJob {
        const IDENTIFIER: &'static str = "failing_job";

        async fn run(self, _ctx: WorkerContext) -> impl IntoTaskHandlerResult {
            Err(format!("Failed with message: {}", self.message))
        }
    }

    helpers::with_test_db(|test_db| async move {
        let worker = test_db
            .create_worker_options()
            .define_job::<FailingJob>()
            .init()
            .await
            .expect("Failed to create worker");

        let utils = worker.create_utils();
        utils
            .add_job(
                FailingJob {
                    message: "test error".to_string(),
                },
                JobSpecBuilder::new().max_attempts(3).build(),
            )
            .await
            .expect("Failed to add job");

        worker.run_once().await.expect("Failed to run worker");

        let jobs = test_db.get_jobs().await;
        assert_eq!(jobs.len(), 1, "Job should still be in the database");
        let job = &jobs[0];
        assert_eq!(job.task_identifier, "failing_job");
        assert_eq!(job.attempts, 1, "Job should have one failed attempt");
        assert!(job.last_error.is_some(), "Job should have an error message");
        assert!(
            job.last_error
                .as_ref()
                .unwrap()
                .contains("Failed with message: test error"),
            "Error message should contain the original error"
        );

        test_db.make_jobs_run_now("failing_job").await;
        worker.run_once().await.expect("Failed to run worker");

        let jobs = test_db.get_jobs().await;
        assert_eq!(jobs.len(), 1, "Job should still be in the database");
        let job = &jobs[0];
        assert_eq!(job.attempts, 2, "Job should have two failed attempts");

        test_db.make_jobs_run_now("failing_job").await;
        worker.run_once().await.expect("Failed to run worker");

        let jobs = test_db.get_jobs().await;
        assert_eq!(jobs.len(), 1, "Job should still be in the database");
        let job = &jobs[0];
        assert_eq!(job.attempts, 3, "Job should have three failed attempts");
        assert_eq!(job.max_attempts, 3, "Max attempts should be 3");
    })
    .await;
}