use crate::depmap::DependencyMap;
use crate::errors::Result;
use crate::operation::{Operation, Operations};
use crate::server::Server;
use crate::storage::{Storage, TaskMap};
use crate::task::{Status, Task};
use crate::taskdb::TaskDb;
use crate::workingset::WorkingSet;
use crate::{Error, TaskData};
use anyhow::Context;
use chrono::{DateTime, Duration, Utc};
use log::trace;
use std::collections::HashMap;
use std::sync::Arc;
use uuid::Uuid;
pub struct Replica {
taskdb: TaskDb,
added_undo_point: bool,
depmap: Option<Arc<DependencyMap>>,
}
impl Replica {
pub fn new(storage: Box<dyn Storage>) -> Replica {
Replica {
taskdb: TaskDb::new(storage),
added_undo_point: false,
depmap: None,
}
}
#[cfg(test)]
pub fn new_inmemory() -> Replica {
use crate::StorageConfig;
Replica::new(StorageConfig::InMemory.into_storage().unwrap())
}
#[deprecated(since = "0.7.0", note = "please use TaskData instead")]
pub fn update_task<S1, S2>(
&mut self,
uuid: Uuid,
property: S1,
value: Option<S2>,
) -> Result<TaskMap>
where
S1: Into<String>,
S2: Into<String>,
{
let value = value.map(|v| v.into());
let property = property.into();
let mut ops = self.make_operations();
let Some(mut task) = self.get_task_data(uuid)? else {
return Err(Error::Database(format!("Task {} does not exist", uuid)));
};
task.update(property, value, &mut ops);
self.commit_operations(ops)?;
Ok(self
.taskdb
.get_task(uuid)?
.expect("task should exist after an update"))
}
pub fn all_tasks(&mut self) -> Result<HashMap<Uuid, Task>> {
let depmap = self.dependency_map(false)?;
let mut res = HashMap::new();
for (uuid, tm) in self.taskdb.all_tasks()?.drain(..) {
res.insert(uuid, Task::new(TaskData::new(uuid, tm), depmap.clone()));
}
Ok(res)
}
pub fn all_task_data(&mut self) -> Result<HashMap<Uuid, TaskData>> {
let mut res = HashMap::new();
for (uuid, tm) in self.taskdb.all_tasks()?.drain(..) {
res.insert(uuid, TaskData::new(uuid, tm));
}
Ok(res)
}
pub fn all_task_uuids(&mut self) -> Result<Vec<Uuid>> {
self.taskdb.all_task_uuids()
}
pub fn pending_tasks(&mut self) -> Result<Vec<Task>> {
let depmap = self.dependency_map(false)?;
let res = self
.pending_task_data()?
.into_iter()
.map(|taskdata| Task::new(taskdata, depmap.clone()))
.collect();
Ok(res)
}
pub fn pending_task_data(&mut self) -> Result<Vec<TaskData>> {
let res = self
.taskdb
.get_pending_tasks()?
.into_iter()
.map(|(uuid, taskmap)| TaskData::new(uuid, taskmap))
.collect::<Vec<_>>();
Ok(res)
}
pub fn working_set(&mut self) -> Result<WorkingSet> {
Ok(WorkingSet::new(self.taskdb.working_set()?))
}
pub fn dependency_map(&mut self, force: bool) -> Result<Arc<DependencyMap>> {
if force || self.depmap.is_none() {
let mut dm = DependencyMap::new();
let mut is_pending_cache: HashMap<Uuid, bool> = HashMap::new();
let ws = self.working_set()?;
for i in 1..=ws.largest_index() {
if let Some(u) = ws.by_index(i) {
if let Some(taskmap) = self.taskdb.get_task(u)? {
for p in taskmap.keys() {
if let Some(dep_str) = p.strip_prefix("dep_") {
if let Ok(dep) = Uuid::parse_str(dep_str) {
let dep_pending = {
if let Some(dep_pending) = is_pending_cache.get(&dep) {
*dep_pending
} else if let Some(dep_taskmap) =
self.taskdb.get_task(dep)?
{
let dep_pending = matches!(
dep_taskmap
.get("status")
.map(|tm| Status::from_taskmap(tm)),
Some(Status::Pending)
);
is_pending_cache.insert(dep, dep_pending);
dep_pending
} else {
false
}
};
if dep_pending {
dm.add_dependency(u, dep);
}
}
}
}
}
}
}
self.depmap = Some(Arc::new(dm));
}
Ok(self.depmap.as_ref().unwrap().clone())
}
pub fn get_task(&mut self, uuid: Uuid) -> Result<Option<Task>> {
let depmap = self.dependency_map(false)?;
Ok(self
.taskdb
.get_task(uuid)?
.map(move |tm| Task::new(TaskData::new(uuid, tm), depmap)))
}
pub fn get_task_data(&mut self, uuid: Uuid) -> Result<Option<TaskData>> {
Ok(self
.taskdb
.get_task(uuid)?
.map(move |tm| TaskData::new(uuid, tm)))
}
pub fn get_task_operations(&mut self, uuid: Uuid) -> Result<Operations> {
self.taskdb.get_task_operations(uuid)
}
#[deprecated(
since = "0.7.0",
note = "please use `create_task` and call `Task` methods `set_status`, `set_description`, and `set_entry`"
)]
pub fn new_task(&mut self, status: Status, description: String) -> Result<Task> {
let uuid = Uuid::new_v4();
let mut ops = self.make_operations();
let now = format!("{}", Utc::now().timestamp());
let mut task = TaskData::create(uuid, &mut ops);
task.update("modified", Some(now.clone()), &mut ops);
task.update("description", Some(description), &mut ops);
task.update("status", Some(status.to_taskmap().to_string()), &mut ops);
task.update("entry", Some(now), &mut ops);
self.commit_operations(ops)?;
trace!("task {} created", uuid);
Ok(self
.get_task(uuid)?
.expect("Task should exist after creation"))
}
pub fn create_task(&mut self, uuid: Uuid, ops: &mut Operations) -> Result<Task> {
if let Some(task) = self.get_task(uuid)? {
return Ok(task);
}
let depmap = self.dependency_map(false)?;
Ok(Task::new(TaskData::create(uuid, ops), depmap))
}
#[deprecated(since = "0.7.0", note = "please use TaskData instead")]
pub fn import_task_with_uuid(&mut self, uuid: Uuid) -> Result<Task> {
let mut ops = self.make_operations();
TaskData::create(uuid, &mut ops);
self.commit_operations(ops)?;
Ok(self
.get_task(uuid)?
.expect("Task should exist after creation"))
}
#[deprecated(since = "0.7.0", note = "please use TaskData::delete")]
pub fn delete_task(&mut self, uuid: Uuid) -> Result<()> {
let Some(mut task) = self.get_task_data(uuid)? else {
return Err(Error::Database(format!("Task {} does not exist", uuid)));
};
let mut ops = self.make_operations();
task.delete(&mut ops);
self.commit_operations(ops)?;
trace!("task {} deleted", uuid);
Ok(())
}
pub fn commit_operations(&mut self, operations: Operations) -> Result<()> {
if operations.is_empty() {
return Ok(());
}
let pending = Status::Pending.to_taskmap();
let recurring = Status::Recurring.to_taskmap();
let is_p_or_r = |val: &Option<String>| {
if let Some(val) = val {
val == pending || val == recurring
} else {
false
}
};
let add_to_working_set = |op: &Operation| match op {
Operation::Update {
property,
value,
old_value,
..
} => property == "status" && !is_p_or_r(old_value) && is_p_or_r(value),
_ => false,
};
self.taskdb
.commit_operations(operations, add_to_working_set)?;
self.depmap = None;
Ok(())
}
pub fn sync(&mut self, server: &mut Box<dyn Server>, avoid_snapshots: bool) -> Result<()> {
self.taskdb
.sync(server, avoid_snapshots)
.context("Failed to synchronize with server")?;
self.rebuild_working_set(false)
.context("Failed to rebuild working set after sync")?;
Ok(())
}
pub fn get_undo_operations(&mut self) -> Result<Operations> {
self.taskdb.get_undo_operations()
}
pub fn commit_reversed_operations(&mut self, operations: Operations) -> Result<bool> {
if !self.taskdb.commit_reversed_operations(operations)? {
return Ok(false);
}
self.depmap = None;
self.rebuild_working_set(false)
.context("Failed to rebuild working set after committing reversed operations")?;
Ok(true)
}
pub fn rebuild_working_set(&mut self, renumber: bool) -> Result<()> {
let pending = String::from(Status::Pending.to_taskmap());
let recurring = String::from(Status::Recurring.to_taskmap());
self.taskdb.rebuild_working_set(
|t| {
if let Some(st) = t.get("status") {
st == &pending || st == &recurring
} else {
false
}
},
renumber,
)?;
Ok(())
}
pub fn expire_tasks(&mut self) -> Result<()> {
let six_mos_ago = Utc::now() - Duration::days(180);
let mut ops = Operations::new();
let deleted = Status::Deleted.to_taskmap();
self.all_task_data()?
.drain()
.filter(|(_, t)| t.get("status") == Some(deleted))
.filter(|(_, t)| {
t.get("modified").map_or(false, |m| {
m.parse().map_or(false, |time_sec| {
DateTime::from_timestamp(time_sec, 0).map_or(false, |dt| dt < six_mos_ago)
})
})
})
.for_each(|(_, mut t)| t.delete(&mut ops));
self.commit_operations(ops)
}
#[deprecated(
since = "0.7.0",
note = "Push an `Operation::UndoPoint` onto your `Operations` instead."
)]
pub fn add_undo_point(&mut self, force: bool) -> Result<()> {
if force || !self.added_undo_point {
let ops = vec![Operation::UndoPoint];
self.commit_operations(ops)?;
self.added_undo_point = true;
}
Ok(())
}
fn make_operations(&mut self) -> Operations {
let mut ops = Operations::new();
if !self.added_undo_point {
ops.push(Operation::UndoPoint);
self.added_undo_point = true;
}
ops
}
pub fn num_local_operations(&mut self) -> Result<usize> {
self.taskdb.num_operations()
}
pub fn num_undo_points(&mut self) -> Result<usize> {
self.taskdb.num_undo_points()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::task::Status;
use chrono::{DateTime, TimeZone};
use pretty_assertions::assert_eq;
use std::collections::HashSet;
use uuid::Uuid;
const JUST_NOW: Option<DateTime<Utc>> = DateTime::from_timestamp(1800000000, 0);
fn clean_op(op: Operation) -> Operation {
if let Operation::Update {
uuid,
property,
mut old_value,
mut value,
..
} = op
{
if property == "modified" || property == "end" || property == "entry" {
if value.is_some() {
value = Some("just-now".into());
}
if old_value.is_some() {
old_value = Some("just-now".into());
}
}
Operation::Update {
uuid,
property,
old_value,
value,
timestamp: JUST_NOW.unwrap(),
}
} else {
op
}
}
#[test]
fn new_task() {
let mut rep = Replica::new_inmemory();
#[allow(deprecated)]
let t = rep.new_task(Status::Pending, "a task".into()).unwrap();
assert_eq!(t.get_description(), String::from("a task"));
assert_eq!(t.get_status(), Status::Pending);
assert!(t.get_modified().is_some());
}
#[test]
fn modify_task() {
let mut rep = Replica::new_inmemory();
#[allow(deprecated)]
let mut t = rep.new_task(Status::Pending, "a task".into()).unwrap();
let mut ops = Operations::new();
t.set_description(String::from("past tense"), &mut ops)
.unwrap();
t.set_status(Status::Completed, &mut ops).unwrap();
assert_eq!(t.get_description(), "past tense");
assert_eq!(t.get_status(), Status::Completed);
let t = rep.get_task(t.get_uuid()).unwrap().unwrap();
assert_eq!(t.get_description(), "a task");
assert_eq!(t.get_status(), Status::Pending);
rep.commit_operations(ops).unwrap();
let t = rep.get_task(t.get_uuid()).unwrap().unwrap();
assert_eq!(t.get_description(), "past tense");
assert_eq!(t.get_status(), Status::Completed);
assert_eq!(
rep.taskdb
.operations()
.drain(..)
.map(clean_op)
.collect::<Vec<_>>(),
vec![
Operation::UndoPoint,
Operation::Create { uuid: t.get_uuid() },
Operation::Update {
uuid: t.get_uuid(),
property: "modified".into(),
old_value: None,
value: Some("just-now".into()),
timestamp: JUST_NOW.unwrap(),
},
Operation::Update {
uuid: t.get_uuid(),
property: "description".into(),
old_value: None,
value: Some("a task".into()),
timestamp: JUST_NOW.unwrap(),
},
Operation::Update {
uuid: t.get_uuid(),
property: "status".into(),
old_value: None,
value: Some("pending".into()),
timestamp: JUST_NOW.unwrap(),
},
Operation::Update {
uuid: t.get_uuid(),
property: "entry".into(),
old_value: None,
value: Some("just-now".into()),
timestamp: JUST_NOW.unwrap(),
},
Operation::Update {
uuid: t.get_uuid(),
property: "modified".into(),
old_value: Some("just-now".into()),
value: Some("just-now".into()),
timestamp: JUST_NOW.unwrap(),
},
Operation::Update {
uuid: t.get_uuid(),
property: "description".into(),
old_value: Some("a task".into()),
value: Some("past tense".into()),
timestamp: JUST_NOW.unwrap(),
},
Operation::Update {
uuid: t.get_uuid(),
property: "end".into(),
old_value: None,
value: Some("just-now".into()),
timestamp: JUST_NOW.unwrap(),
},
Operation::Update {
uuid: t.get_uuid(),
property: "status".into(),
old_value: Some("pending".into()),
value: Some("completed".into()),
timestamp: JUST_NOW.unwrap(),
},
]
);
assert_eq!(rep.num_local_operations().unwrap(), 9);
assert_eq!(rep.num_undo_points().unwrap(), 1);
let ops = vec![Operation::UndoPoint];
rep.commit_operations(ops).unwrap();
assert_eq!(rep.num_undo_points().unwrap(), 2);
}
#[test]
fn delete_task() {
let mut rep = Replica::new_inmemory();
let uuid = Uuid::new_v4();
let mut ops = Operations::new();
rep.create_task(uuid, &mut ops).unwrap();
rep.commit_operations(ops).unwrap();
#[allow(deprecated)]
rep.delete_task(uuid).unwrap();
assert_eq!(rep.get_task(uuid).unwrap(), None);
}
#[test]
fn all_tasks() {
let mut rep = Replica::new_inmemory();
let (uuid1, uuid2) = (Uuid::new_v4(), Uuid::new_v4());
let mut ops = Operations::new();
rep.create_task(uuid1, &mut ops).unwrap();
rep.create_task(uuid2, &mut ops).unwrap();
rep.commit_operations(ops).unwrap();
let all_tasks = rep.all_tasks().unwrap();
assert_eq!(all_tasks.len(), 2);
assert_eq!(all_tasks.get(&uuid1).unwrap().get_uuid(), uuid1);
assert_eq!(all_tasks.get(&uuid2).unwrap().get_uuid(), uuid2);
let all_tasks = rep.all_task_data().unwrap();
assert_eq!(all_tasks.len(), 2);
assert_eq!(all_tasks.get(&uuid1).unwrap().get_uuid(), uuid1);
assert_eq!(all_tasks.get(&uuid2).unwrap().get_uuid(), uuid2);
let mut all_uuids = rep.all_task_uuids().unwrap();
all_uuids.sort();
let mut exp_uuids = vec![uuid1, uuid2];
exp_uuids.sort();
assert_eq!(all_uuids.len(), 2);
assert_eq!(all_uuids, exp_uuids);
}
#[test]
fn pending_tasks() {
let mut rep = Replica::new_inmemory();
let (uuid1, uuid2, uuid3) = (Uuid::new_v4(), Uuid::new_v4(), Uuid::new_v4());
let mut ops = Operations::new();
let mut t1 = rep.create_task(uuid1, &mut ops).unwrap();
t1.set_status(Status::Pending, &mut ops).unwrap();
let mut t2 = rep.create_task(uuid2, &mut ops).unwrap();
t2.set_status(Status::Pending, &mut ops).unwrap();
let mut t3 = rep.create_task(uuid3, &mut ops).unwrap();
t3.set_status(Status::Completed, &mut ops).unwrap();
rep.commit_operations(ops).unwrap();
let pending_tasks = rep.pending_tasks().unwrap();
assert_eq!(pending_tasks.len(), 2);
assert_eq!(pending_tasks.first().unwrap().get_uuid(), uuid1);
assert_eq!(pending_tasks.get(1).unwrap().get_uuid(), uuid2);
}
#[test]
fn commit_operations() -> Result<()> {
let mut rep = Replica::new_inmemory();
rep.dependency_map(true).unwrap();
assert!(rep.depmap.is_some());
let mut ops = Operations::new();
let uuid1 = Uuid::new_v4();
let mut t = rep.create_task(uuid1, &mut ops).unwrap();
t.set_status(Status::Pending, &mut ops).unwrap();
let uuid2 = Uuid::new_v4();
ops.push(Operation::Create { uuid: uuid2 });
ops.push(Operation::Delete {
uuid: uuid2,
old_task: TaskMap::new(),
});
let update_op = |uuid, property: &str, old_value: Option<&str>, value: Option<&str>| {
Operation::Update {
uuid,
property: property.to_string(),
value: value.map(|v| v.to_string()),
timestamp: Utc::now(),
old_value: old_value.map(|v| v.to_string()),
}
};
let uuid3 = Uuid::new_v4();
ops.push(update_op(uuid3, "status", None, Some("deleted")));
let uuid4 = Uuid::new_v4();
ops.push(update_op(uuid4, "status", Some("pending"), Some("pending")));
let uuid5 = Uuid::new_v4();
ops.push(update_op(
uuid5,
"status",
Some("recurring"),
Some("recurring"),
));
let uuid6 = Uuid::new_v4();
ops.push(update_op(
uuid6,
"status",
Some("recurring"),
Some("pending"),
));
let uuid7 = Uuid::new_v4();
ops.push(update_op(
uuid7,
"status",
Some("pending"),
Some("recurring"),
));
let uuid8 = Uuid::new_v4();
ops.push(update_op(uuid8, "status", None, Some("recurring")));
let uuid9 = Uuid::new_v4();
ops.push(update_op(uuid9, "status", None, Some("pending")));
let uuid10 = Uuid::new_v4();
ops.push(update_op(
uuid10,
"status",
Some("deleted"),
Some("pending"),
));
let uuid11 = Uuid::new_v4();
ops.push(update_op(
uuid11,
"status",
Some("pending"),
Some("deleted"),
));
let uuid12 = Uuid::new_v4();
ops.push(update_op(uuid12, "status", Some("pending"), None));
rep.commit_operations(ops)?;
let ws = rep.working_set()?;
assert!(ws.by_uuid(uuid1).is_some());
assert!(ws.by_uuid(uuid2).is_none());
assert!(ws.by_uuid(uuid3).is_none());
assert!(ws.by_uuid(uuid4).is_none());
assert!(ws.by_uuid(uuid5).is_none());
assert!(ws.by_uuid(uuid6).is_none());
assert!(ws.by_uuid(uuid7).is_none());
assert!(ws.by_uuid(uuid8).is_some());
assert!(ws.by_uuid(uuid9).is_some());
assert!(ws.by_uuid(uuid10).is_some());
assert!(ws.by_uuid(uuid11).is_none());
assert!(ws.by_uuid(uuid12).is_none());
assert!(rep.depmap.is_none());
Ok(())
}
#[test]
fn commit_reversed_operations() -> Result<()> {
let uuid1 = Uuid::new_v4();
let uuid2 = Uuid::new_v4();
let uuid3 = Uuid::new_v4();
let mut rep = Replica::new_inmemory();
let mut ops = Operations::new();
ops.push(Operation::UndoPoint);
rep.create_task(uuid1, &mut ops).unwrap();
ops.push(Operation::UndoPoint);
rep.create_task(uuid2, &mut ops).unwrap();
rep.commit_operations(ops)?;
assert_eq!(rep.num_undo_points().unwrap(), 2);
let ops = vec![Operation::Delete {
uuid: uuid3,
old_task: TaskMap::new(),
}];
assert!(!rep.commit_reversed_operations(ops)?);
let ops = rep.get_undo_operations()?;
assert!(rep.commit_reversed_operations(ops)?);
assert_eq!(rep.num_undo_points().unwrap(), 1);
Ok(())
}
#[test]
fn get_and_modify() {
let mut rep = Replica::new_inmemory();
let mut ops = Operations::new();
let uuid = Uuid::new_v4();
let mut t = rep.create_task(uuid, &mut ops).unwrap();
t.set_status(Status::Pending, &mut ops).unwrap();
t.set_description("another task".into(), &mut ops).unwrap();
rep.commit_operations(ops).unwrap();
let mut t = rep.get_task(uuid).unwrap().unwrap();
assert_eq!(t.get_description(), String::from("another task"));
let mut ops = Operations::new();
t.set_status(Status::Deleted, &mut ops).unwrap();
t.set_description("gone".into(), &mut ops).unwrap();
rep.commit_operations(ops).unwrap();
let t = rep.get_task(uuid).unwrap().unwrap();
assert_eq!(t.get_status(), Status::Deleted);
assert_eq!(t.get_description(), "gone");
rep.rebuild_working_set(true).unwrap();
let ws = rep.working_set().unwrap();
assert!(ws.by_uuid(t.get_uuid()).is_none());
}
#[test]
fn get_task_data_and_operations() {
let mut rep = Replica::new_inmemory();
let uuid1 = Uuid::new_v4();
let uuid2 = Uuid::new_v4();
let mut ops = Operations::new();
let mut t = rep.create_task(uuid1, &mut ops).unwrap();
t.set_description("another task".into(), &mut ops).unwrap();
let mut t2 = rep.create_task(uuid2, &mut ops).unwrap();
t2.set_description("a distraction!".into(), &mut ops)
.unwrap();
rep.commit_operations(ops).unwrap();
let t = rep.get_task_data(uuid1).unwrap().unwrap();
assert_eq!(t.get_uuid(), uuid1);
assert_eq!(t.get("description"), Some("another task"));
assert_eq!(
rep.get_task_operations(uuid1)
.unwrap()
.into_iter()
.map(clean_op)
.collect::<Vec<_>>(),
vec![
Operation::Create { uuid: uuid1 },
Operation::Update {
uuid: uuid1,
property: "modified".into(),
old_value: None,
value: Some("just-now".into()),
timestamp: JUST_NOW.unwrap(),
},
Operation::Update {
uuid: uuid1,
property: "description".into(),
old_value: None,
value: Some("another task".into()),
timestamp: JUST_NOW.unwrap(),
},
]
);
assert!(rep.get_task_data(Uuid::new_v4()).unwrap().is_none());
assert_eq!(rep.get_task_operations(Uuid::new_v4()).unwrap(), vec![]);
}
#[test]
fn rebuild_working_set_includes_recurring() {
let mut rep = Replica::new_inmemory();
let uuid = Uuid::new_v4();
let mut ops = Operations::new();
let mut t = rep.create_task(uuid, &mut ops).unwrap();
t.set_status(Status::Completed, &mut ops).unwrap();
rep.commit_operations(ops).unwrap();
let mut t = rep.get_task(uuid).unwrap().unwrap();
let mut ops = Operations::new();
t.set_status(Status::Recurring, &mut ops).unwrap();
rep.commit_operations(ops).unwrap();
rep.rebuild_working_set(true).unwrap();
let ws = rep.working_set().unwrap();
assert!(ws.by_uuid(uuid).is_some());
}
#[test]
fn new_pending_adds_to_working_set() {
let mut rep = Replica::new_inmemory();
let uuid = Uuid::new_v4();
let mut ops = Operations::new();
let mut t = rep.create_task(uuid, &mut ops).unwrap();
t.set_status(Status::Pending, &mut ops).unwrap();
rep.commit_operations(ops).unwrap();
let ws = rep.working_set().unwrap();
assert_eq!(ws.len(), 1); assert!(ws.by_index(0).is_none());
assert_eq!(ws.by_index(1), Some(uuid));
let ws = rep.working_set().unwrap();
assert_eq!(ws.by_uuid(t.get_uuid()), Some(1));
}
#[test]
fn new_recurring_adds_to_working_set() {
let mut rep = Replica::new_inmemory();
let uuid = Uuid::new_v4();
let mut ops = Operations::new();
let mut t = rep.create_task(uuid, &mut ops).unwrap();
t.set_status(Status::Recurring, &mut ops).unwrap();
rep.commit_operations(ops).unwrap();
let ws = rep.working_set().unwrap();
assert_eq!(ws.len(), 1); assert!(ws.by_index(0).is_none());
assert_eq!(ws.by_index(1), Some(uuid));
let ws = rep.working_set().unwrap();
assert_eq!(ws.by_uuid(t.get_uuid()), Some(1));
}
#[test]
fn get_does_not_exist() {
let mut rep = Replica::new_inmemory();
let uuid = Uuid::new_v4();
assert_eq!(rep.get_task(uuid).unwrap(), None);
}
#[test]
fn expire() {
let mut rep = Replica::new_inmemory();
let mut ops = Operations::new();
let keeper_uuid1 = Uuid::new_v4();
let mut t = rep.create_task(keeper_uuid1, &mut ops).unwrap();
t.set_description("keeper 1".into(), &mut ops).unwrap();
t.set_modified(Utc.with_ymd_and_hms(1980, 1, 1, 0, 0, 0).unwrap(), &mut ops)
.unwrap();
t.set_status(Status::Pending, &mut ops).unwrap();
let keeper_uuid2 = Uuid::new_v4();
let mut t = rep.create_task(keeper_uuid2, &mut ops).unwrap();
t.set_description("keeper 2".into(), &mut ops).unwrap();
t.set_modified(Utc.with_ymd_and_hms(1980, 1, 1, 0, 0, 0).unwrap(), &mut ops)
.unwrap();
t.set_status(Status::Completed, &mut ops).unwrap();
let keeper_uuid3 = Uuid::new_v4();
let mut t = rep.create_task(keeper_uuid3, &mut ops).unwrap();
t.set_description("keeper 3".into(), &mut ops).unwrap();
t.set_status(Status::Deleted, &mut ops).unwrap();
t.set_modified(Utc::now(), &mut ops).unwrap();
t.set_entry(Some(Utc::now()), &mut ops).unwrap();
let goner_uuid4 = Uuid::new_v4();
let mut t = rep.create_task(goner_uuid4, &mut ops).unwrap();
t.set_description("goner".into(), &mut ops).unwrap();
t.set_status(Status::Deleted, &mut ops).unwrap();
t.set_modified(Utc.with_ymd_and_hms(1980, 1, 1, 0, 0, 0).unwrap(), &mut ops)
.unwrap();
rep.commit_operations(ops).unwrap();
rep.expire_tasks().unwrap();
assert!(rep.get_task_data(keeper_uuid1).unwrap().is_some());
assert!(rep.get_task_data(keeper_uuid2).unwrap().is_some());
assert!(rep.get_task_data(keeper_uuid3).unwrap().is_some());
assert!(rep.get_task_data(goner_uuid4).unwrap().is_none());
}
#[test]
fn dependency_map() {
let mut rep = Replica::new_inmemory();
let mut tasks = vec![];
let mut ops = Operations::new();
for _ in 0..4 {
let mut t = rep.create_task(Uuid::new_v4(), &mut ops).unwrap();
t.set_status(Status::Pending, &mut ops).unwrap();
tasks.push(t);
}
let uuids: Vec<_> = tasks.iter().map(|t| t.get_uuid()).collect();
let mut t = tasks.pop().unwrap();
t.add_dependency(uuids[2], &mut ops).unwrap();
t.add_dependency(uuids[1], &mut ops).unwrap();
let mut t = tasks.pop().unwrap();
t.add_dependency(uuids[0], &mut ops).unwrap();
let mut t = tasks.pop().unwrap();
t.add_dependency(uuids[0], &mut ops).unwrap();
rep.commit_operations(ops).unwrap();
let dm = rep.dependency_map(false).unwrap();
assert_eq!(
dm.dependencies(uuids[3]).collect::<HashSet<_>>(),
HashSet::from([uuids[1], uuids[2]])
);
assert_eq!(
dm.dependencies(uuids[2]).collect::<HashSet<_>>(),
HashSet::from([uuids[0]])
);
assert_eq!(
dm.dependencies(uuids[1]).collect::<HashSet<_>>(),
HashSet::from([uuids[0]])
);
assert_eq!(
dm.dependencies(uuids[0]).collect::<HashSet<_>>(),
HashSet::from([])
);
assert_eq!(
dm.dependents(uuids[3]).collect::<HashSet<_>>(),
HashSet::from([])
);
assert_eq!(
dm.dependents(uuids[2]).collect::<HashSet<_>>(),
HashSet::from([uuids[3]])
);
assert_eq!(
dm.dependents(uuids[1]).collect::<HashSet<_>>(),
HashSet::from([uuids[3]])
);
assert_eq!(
dm.dependents(uuids[0]).collect::<HashSet<_>>(),
HashSet::from([uuids[1], uuids[2]])
);
let mut ops = Operations::new();
rep.get_task(uuids[0])
.unwrap()
.unwrap()
.done(&mut ops)
.unwrap();
rep.commit_operations(ops).unwrap();
let dm = rep.dependency_map(false).unwrap();
assert_eq!(
dm.dependencies(uuids[3]).collect::<HashSet<_>>(),
HashSet::from([uuids[1], uuids[2]])
);
assert_eq!(
dm.dependencies(uuids[2]).collect::<HashSet<_>>(),
HashSet::from([])
);
assert_eq!(
dm.dependencies(uuids[1]).collect::<HashSet<_>>(),
HashSet::from([])
);
assert_eq!(
dm.dependents(uuids[0]).collect::<HashSet<_>>(),
HashSet::from([])
);
}
}