use cfait::context::TestContext;
use cfait::model::{Task, TaskStatus};
use cfait::storage::{LOCAL_CALENDAR_HREF, LocalStorage};
use cfait::store::{FilterOptions, TaskStore};
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
#[test]
fn test_reproduce_android_local_revert_bug() {
let ctx = Arc::new(TestContext::new());
let uid = "corrupted-uid-123";
let mut t1 = Task::new("Original Summary", &HashMap::new(), None);
t1.uid = uid.to_string();
t1.calendar_href = LOCAL_CALENDAR_HREF.to_string();
let mut t2 = t1.clone();
t2.summary = "Stale Duplicate".to_string();
let corrupted_list = vec![t1.clone(), t2.clone()];
LocalStorage::save_for_href(ctx.as_ref(), LOCAL_CALENDAR_HREF, &corrupted_list).unwrap();
let mut local_list = LocalStorage::load_for_href(ctx.as_ref(), LOCAL_CALENDAR_HREF).unwrap();
if let Some(idx) = local_list.iter().position(|t| t.uid == uid) {
local_list[idx].summary = "Updated via Bridge".to_string();
local_list[idx].status = TaskStatus::Completed;
LocalStorage::save_for_href(ctx.as_ref(), LOCAL_CALENDAR_HREF, &local_list).unwrap();
}
let mut store = TaskStore::new(ctx.clone());
let reloaded_list = LocalStorage::load_for_href(ctx.as_ref(), LOCAL_CALENDAR_HREF).unwrap();
store.insert(LOCAL_CALENDAR_HREF.to_string(), reloaded_list);
let filter_res = store.filter(FilterOptions {
active_cal_href: None,
hidden_calendars: &HashSet::new(),
selected_categories: &HashSet::new(),
selected_locations: &HashSet::new(),
match_all_categories: false,
search_term: "",
hide_completed_global: false, hide_fully_completed_tags: false,
hide_aliases_in_sidebar: false,
cutoff_date: None,
min_duration: None,
max_duration: None,
include_unset_duration: true,
urgent_days: 1,
urgent_prio: 1,
default_priority: 5,
start_grace_period_days: 1,
sort_standard_by_priority: false,
expanded_done_groups: &HashSet::new(),
expanded_tags: &HashSet::new(),
expanded_locations: &HashSet::new(),
max_done_roots: usize::MAX,
max_done_subtasks: usize::MAX,
tag_aliases: &HashMap::new(),
});
let visible_task = filter_res
.items
.iter()
.find(|t| {
if let cfait::store::TaskListItem::Task(task) = t {
task.uid == uid
} else {
false
}
})
.expect("Task should exist");
if let cfait::store::TaskListItem::Task(task) = visible_task {
assert_eq!(
task.summary, "Updated via Bridge",
"BUG: Task summary was reverted to the stale duplicate!"
);
} else {
panic!("Expected Task variant");
}
if let cfait::store::TaskListItem::Task(task) = visible_task {
assert!(
task.status == TaskStatus::Completed,
"BUG: Task status was reverted to the stale duplicate!"
);
} else {
panic!("Expected Task variant");
}
}
#[test]
fn test_cross_calendar_deduplication() {
let ctx = Arc::new(TestContext::new());
let mut store = TaskStore::new(ctx.clone());
let uid = "split-brain-uid";
let mut t_old = Task::new("Old Task", &HashMap::new(), None);
t_old.uid = uid.to_string();
t_old.calendar_href = "cal1".to_string();
t_old.sequence = 1;
let mut t_new = Task::new("New Task", &HashMap::new(), None);
t_new.uid = uid.to_string();
t_new.calendar_href = "cal2".to_string();
t_new.sequence = 2;
store.insert("cal1".to_string(), vec![t_old.clone()]);
store.insert("cal2".to_string(), vec![t_new.clone()]);
assert!(
store.calendars.get("cal1").unwrap().is_empty(),
"Ghost task should have been evicted from the incorrect collection"
);
let retained_task = store.calendars.get("cal2").unwrap().get(uid).unwrap();
assert_eq!(retained_task.summary, "New Task");
assert_eq!(retained_task.calendar_href, "cal2");
assert_eq!(
store.index.get(uid).unwrap(),
"cal2",
"Index should point to the correct active collection"
);
}