use cfait::client::RustyClient;
use cfait::context::TestContext;
use cfait::journal::{Action, Journal};
use cfait::model::Task;
use mockito::{Matcher, Server};
use std::collections::HashMap;
use std::sync::Arc;
#[tokio::test]
async fn test_sync_delete_404_is_success() {
let ctx = Arc::new(TestContext::new());
let mut server = Server::new_async().await;
let url = server.url();
let mock = server
.mock("DELETE", "/cal/task.ics")
.with_status(404)
.create_async()
.await;
let client = RustyClient::new(ctx.clone(), &url, "u", "p", true, None).unwrap();
let mut task = Task::new("T", &HashMap::new(), None);
task.href = format!("{}/cal/task.ics", url);
task.etag = "\"123\"".to_string();
Journal::push(ctx.as_ref(), Action::Delete(task)).unwrap();
let res = client.sync_journal().await;
assert!(res.is_ok(), "Sync failed: {:?}", res.err());
mock.assert();
let j = Journal::load(ctx.as_ref());
assert!(j.is_empty(), "Journal should be empty after 404 delete");
}
#[tokio::test]
async fn test_sync_500_keeps_item_in_queue() {
let ctx = Arc::new(TestContext::new());
let mut server = Server::new_async().await;
let url = server.url();
let mock = server
.mock("PUT", "/cal/task.ics")
.with_status(500)
.create_async()
.await;
let client = RustyClient::new(ctx.clone(), &url, "u", "p", true, None).unwrap();
let mut task = Task::new("T", &HashMap::new(), None);
task.uid = "task".to_string();
task.calendar_href = "/cal/".to_string();
Journal::push(ctx.as_ref(), Action::Create(task)).unwrap();
let res = client.sync_journal().await;
assert!(res.is_err(), "Sync should have failed due to 500");
mock.assert();
let j = Journal::load(ctx.as_ref());
assert!(
!j.is_empty(),
"Journal should still contain the failed item"
);
assert_eq!(j.queue.len(), 1);
}
#[tokio::test]
async fn test_sync_ignores_companion_events_to_prevent_multiget_spam() {
let ctx = Arc::new(TestContext::new());
let mut server = Server::new_async().await;
let url = server.url();
let cal_path = "/cal/";
let mock_list = server
.mock("REPORT", cal_path)
.match_header("depth", "1")
.match_body(Matcher::Regex("name=\"VTODO\"".to_string()))
.with_status(207)
.with_body(r#"
<d:multistatus xmlns:d="DAV:">
<d:response>
<d:href>/cal/valid-task.ics</d:href>
<d:propstat><d:prop><d:getetag>"1"</d:getetag></d:prop><d:status>HTTP/1.1 200 OK</d:status></d:propstat>
</d:response>
</d:multistatus>
"#)
.create_async()
.await;
let mock_journal = server
.mock("REPORT", cal_path)
.match_header("depth", "1")
.match_body(Matcher::Regex("name=\"VJOURNAL\"".to_string()))
.with_status(207)
.with_body(r#"<d:multistatus xmlns:d="DAV:"></d:multistatus>"#)
.create_async()
.await;
let valid_ics = "BEGIN:VCALENDAR\nVERSION:2.0\nBEGIN:VTODO\nUID:valid-task\nSUMMARY:Test\nSTATUS:NEEDS-ACTION\nEND:VTODO\nEND:VCALENDAR";
let mock_get = server
.mock("REPORT", cal_path)
.with_status(207)
.with_body(format!(
r#"
<d:multistatus xmlns:d="DAV:" xmlns:cal="urn:ietf:params:xml:ns:caldav">
<d:response>
<d:href>/cal/valid-task.ics</d:href>
<d:propstat>
<d:prop>
<cal:calendar-data>{}</cal:calendar-data>
<d:getetag>"1"</d:getetag>
</d:prop>
<d:status>HTTP/1.1 200 OK</d:status>
</d:propstat>
</d:response>
</d:multistatus>
"#,
valid_ics
))
.create_async()
.await;
let client = RustyClient::new(ctx.clone(), &url, "user", "pass", true, None).unwrap();
let tasks = client
.get_tasks(&format!("{}{}", url, cal_path))
.await
.unwrap();
mock_list.assert();
mock_journal.assert();
mock_get.assert();
assert_eq!(
tasks.len(),
1,
"Client should have completely ignored the evt- file and only parsed the 1 valid task."
);
assert_eq!(tasks[0].uid, "valid-task");
}