test: add scheduler unit test and find_due_schedules integration tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
master
oabrivard 3 months ago
parent 3dea7bf286
commit fa793de8bf

@ -90,3 +90,14 @@ pub async fn run_scheduled_jobs(state: &AppState) {
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn current_day_code_returns_valid_code() {
let code = current_day_code();
assert!(["mon", "tue", "wed", "thu", "fri", "sat", "sun"].contains(&code));
}
}

@ -12,6 +12,7 @@
mod common;
use axum::http::StatusCode;
use ai_synth_backend::db;
fn require_test_db() -> bool {
std::env::var("TEST_DATABASE_URL").is_ok()
@ -400,3 +401,217 @@ async fn schedule_requires_theme_ownership() {
"User B should get 404 when attempting to PUT schedule on User A's theme"
);
}
// ═══════════════════════════════════════════════════════════════════════════
// find_due_schedules — DB query logic
// ═══════════════════════════════════════════════════════════════════════════
/// Return (day_code, time_utc) for the current UTC moment.
fn today_day_and_time() -> (String, String) {
use chrono::Datelike;
let now = chrono::Utc::now();
let day_code = match now.weekday() {
chrono::Weekday::Mon => "mon",
chrono::Weekday::Tue => "tue",
chrono::Weekday::Wed => "wed",
chrono::Weekday::Thu => "thu",
chrono::Weekday::Fri => "fri",
chrono::Weekday::Sat => "sat",
chrono::Weekday::Sun => "sun",
};
let time = now.format("%H:%M").to_string();
(day_code.to_string(), time)
}
/// Return the day code for the day *after* the current UTC day (wraps around).
fn tomorrow_day_code() -> String {
use chrono::Datelike;
let tomorrow = chrono::Utc::now() + chrono::Duration::days(1);
match tomorrow.weekday() {
chrono::Weekday::Mon => "mon".to_string(),
chrono::Weekday::Tue => "tue".to_string(),
chrono::Weekday::Wed => "wed".to_string(),
chrono::Weekday::Thu => "thu".to_string(),
chrono::Weekday::Fri => "fri".to_string(),
chrono::Weekday::Sat => "sat".to_string(),
chrono::Weekday::Sun => "sun".to_string(),
}
}
#[tokio::test]
async fn find_due_schedules_returns_matching_schedule() {
if !require_test_db() {
eprintln!("SKIPPED: TEST_DATABASE_URL not set");
return;
}
let app = common::TestApp::new().await;
let (_user_id, session) = app
.create_authenticated_user("find-due-match@example.com")
.await;
let theme_id = create_theme(&app, &session).await;
// Use today's day code and "00:00" so the schedule is always already due.
let (day_code, _) = today_day_and_time();
let body = serde_json::json!({
"enabled": true,
"days": [day_code],
"time_utc": "00:00",
"emails": []
});
let (status, _) = app
.put_with_session(
&format!("/api/v1/themes/{}/schedule", theme_id),
&body,
&session,
)
.await;
assert_eq!(status, StatusCode::OK);
let (day_code, current_time) = today_day_and_time();
let due = db::schedules::find_due_schedules(&app.pool, &day_code, &current_time)
.await
.expect("find_due_schedules should not fail");
let theme_uuid: uuid::Uuid = theme_id.parse().unwrap();
assert!(
due.iter().any(|s| s.theme_id == theme_uuid),
"Expected the schedule for theme {} to be returned as due, got: {:?}",
theme_id,
due.iter().map(|s| s.theme_id).collect::<Vec<_>>()
);
}
#[tokio::test]
async fn find_due_schedules_skips_disabled_schedule() {
if !require_test_db() {
eprintln!("SKIPPED: TEST_DATABASE_URL not set");
return;
}
let app = common::TestApp::new().await;
let (_user_id, session) = app
.create_authenticated_user("find-due-disabled@example.com")
.await;
let theme_id = create_theme(&app, &session).await;
let (day_code, _) = today_day_and_time();
let body = serde_json::json!({
"enabled": false,
"days": [day_code],
"time_utc": "00:00",
"emails": []
});
let (status, _) = app
.put_with_session(
&format!("/api/v1/themes/{}/schedule", theme_id),
&body,
&session,
)
.await;
assert_eq!(status, StatusCode::OK);
let (day_code, current_time) = today_day_and_time();
let due = db::schedules::find_due_schedules(&app.pool, &day_code, &current_time)
.await
.expect("find_due_schedules should not fail");
let theme_uuid: uuid::Uuid = theme_id.parse().unwrap();
assert!(
!due.iter().any(|s| s.theme_id == theme_uuid),
"Disabled schedule should not appear in due list"
);
}
#[tokio::test]
async fn find_due_schedules_skips_already_run_today() {
if !require_test_db() {
eprintln!("SKIPPED: TEST_DATABASE_URL not set");
return;
}
let app = common::TestApp::new().await;
let (_user_id, session) = app
.create_authenticated_user("find-due-already-run@example.com")
.await;
let theme_id = create_theme(&app, &session).await;
let (day_code, _) = today_day_and_time();
let body = serde_json::json!({
"enabled": true,
"days": [day_code],
"time_utc": "00:00",
"emails": []
});
let (status, resp) = app
.put_with_session(
&format!("/api/v1/themes/{}/schedule", theme_id),
&body,
&session,
)
.await;
assert_eq!(status, StatusCode::OK);
// Mark the schedule as already run
let schedule_id: uuid::Uuid = resp["id"].as_str().unwrap().parse().unwrap();
db::schedules::mark_run(&app.pool, schedule_id)
.await
.expect("mark_run should not fail");
let (day_code, current_time) = today_day_and_time();
let due = db::schedules::find_due_schedules(&app.pool, &day_code, &current_time)
.await
.expect("find_due_schedules should not fail");
let theme_uuid: uuid::Uuid = theme_id.parse().unwrap();
assert!(
!due.iter().any(|s| s.theme_id == theme_uuid),
"Already-run schedule should not appear in due list"
);
}
#[tokio::test]
async fn find_due_schedules_skips_wrong_day() {
if !require_test_db() {
eprintln!("SKIPPED: TEST_DATABASE_URL not set");
return;
}
let app = common::TestApp::new().await;
let (_user_id, session) = app
.create_authenticated_user("find-due-wrong-day@example.com")
.await;
let theme_id = create_theme(&app, &session).await;
// Schedule for tomorrow only, so it must not be due today.
let wrong_day = tomorrow_day_code();
let body = serde_json::json!({
"enabled": true,
"days": [wrong_day],
"time_utc": "00:00",
"emails": []
});
let (status, _) = app
.put_with_session(
&format!("/api/v1/themes/{}/schedule", theme_id),
&body,
&session,
)
.await;
assert_eq!(status, StatusCode::OK);
let (day_code, current_time) = today_day_and_time();
let due = db::schedules::find_due_schedules(&app.pool, &day_code, &current_time)
.await
.expect("find_due_schedules should not fail");
let theme_uuid: uuid::Uuid = theme_id.parse().unwrap();
assert!(
!due.iter().any(|s| s.theme_id == theme_uuid),
"Schedule for a different day should not appear in due list"
);
}

Loading…
Cancel
Save