//! WP-02: /api/settings (SMTP, schedules). mod common; use axum::http::StatusCode; use common::{get, post, send_json, test_app_with_admin}; use serde_json::json; const ADMIN: &str = "admin@example.com"; const PW: &str = "admin-password-123"; fn smtp() -> serde_json::Value { json!({"host": "mail.example.com", "port": 587, "security": "starttls", "username": "bot", "password": "s3cret", "from": "monitoring@example.com", "notify_to": ["ops@example.com"]}) } #[tokio::test] async fn smtp_settings_roundtrip_without_exposing_password() { let app = test_app_with_admin().await; let token = common::login(&app, ADMIN, PW).await.access; let res = get(&app, "/api/settings/smtp", Some(&token)).await; assert_eq!(res.status, StatusCode::OK); assert_eq!(res.json["configured"], false); let res = send_json(&app, "PUT", "/api/settings/smtp", smtp(), Some(&token)).await; assert_eq!(res.status, StatusCode::NO_CONTENT, "{}", res.json); let res = get(&app, "/api/settings/smtp", Some(&token)).await; assert_eq!(res.json["configured"], true); assert_eq!(res.json["smtp"]["host"], "mail.example.com"); assert_eq!(res.json["smtp"]["password_set"], true); assert!(res.json["smtp"].get("password").is_none()); // empty password keeps the stored one let mut upd = smtp(); upd["password"] = json!(""); upd["port"] = json!(2525); assert_eq!( send_json(&app, "PUT", "/api/settings/smtp", upd, Some(&token)) .await .status, StatusCode::NO_CONTENT ); let res = get(&app, "/api/settings/smtp", Some(&token)).await; assert_eq!(res.json["smtp"]["port"], 2525); assert_eq!(res.json["smtp"]["password_set"], true); } #[tokio::test] async fn smtp_validation_and_test_mail() { let app = test_app_with_admin().await; let token = common::login(&app, ADMIN, PW).await.access; let mut bad = smtp(); bad["from"] = json!("nope"); assert_eq!( send_json(&app, "PUT", "/api/settings/smtp", bad, Some(&token)) .await .status, StatusCode::UNPROCESSABLE_ENTITY ); // not configured yet -> 422 let res = post( &app, "/api/settings/smtp/test", json!({"to": "me@example.com"}), Some(&token), ) .await; assert_eq!(res.status, StatusCode::UNPROCESSABLE_ENTITY); send_json(&app, "PUT", "/api/settings/smtp", smtp(), Some(&token)).await; let res = post( &app, "/api/settings/smtp/test", json!({"to": "me@example.com"}), Some(&token), ) .await; assert_eq!(res.status, StatusCode::NO_CONTENT, "{}", res.json); let sent = api::test_support::sent_mails(&app); assert_eq!(sent.len(), 1); assert_eq!(sent[0].0, vec!["me@example.com".to_string()]); } #[tokio::test] async fn schedules_list_and_update() { let app = test_app_with_admin().await; let token = common::login(&app, ADMIN, PW).await.access; let res = get(&app, "/api/settings/schedules", Some(&token)).await; assert_eq!(res.status, StatusCode::OK); let list = res.json.as_array().unwrap(); let pr = list .iter() .find(|s| s["kind"] == "package_refresh") .unwrap(); assert_eq!(pr["cron"], "0 0 * * * *"); let res = send_json( &app, "PUT", "/api/settings/schedules/package_refresh", json!({"cron": "0 */30 * * * *"}), Some(&token), ) .await; assert_eq!(res.status, StatusCode::NO_CONTENT, "{}", res.json); let res = send_json( &app, "PUT", "/api/settings/schedules/package_refresh", json!({"cron": "bad"}), Some(&token), ) .await; assert_eq!(res.status, StatusCode::UNPROCESSABLE_ENTITY); let res = send_json( &app, "PUT", "/api/settings/schedules/package_refresh", json!({"cron": null}), Some(&token), ) .await; assert_eq!(res.status, StatusCode::NO_CONTENT); let res = get(&app, "/api/settings/schedules", Some(&token)).await; let pr = res .json .as_array() .unwrap() .iter() .find(|s| s["kind"] == "package_refresh") .unwrap() .clone(); assert!(pr["cron"].is_null()); assert_eq!( send_json( &app, "PUT", "/api/settings/schedules/nope", json!({"cron": null}), Some(&token) ) .await .status, StatusCode::NOT_FOUND ); } #[tokio::test] async fn settings_writes_are_admin_only_reads_for_all() { let app = test_app_with_admin().await; let admin = common::login(&app, ADMIN, PW).await.access; post(&app, "/api/users", json!({"email": "u@x.de", "display_name": "U", "password": "user-password-123", "role": "user"}), Some(&admin)).await; let user = common::login(&app, "u@x.de", "user-password-123") .await .access; assert_eq!( get(&app, "/api/settings/smtp", Some(&user)).await.status, StatusCode::OK ); assert_eq!( send_json(&app, "PUT", "/api/settings/smtp", smtp(), Some(&user)) .await .status, StatusCode::FORBIDDEN ); assert_eq!( get(&app, "/api/settings/smtp", None).await.status, StatusCode::UNAUTHORIZED ); }