Merge pull request #34 from SteelDynamite/claude/audit-test-coverage-6gTRR
Add tests for models.rs, error.rs, and repository.rs edge cases
This commit is contained in:
commit
fd1ebce9ce
|
|
@ -59,3 +59,105 @@ impl From<reqwest::Error> for Error {
|
|||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_display_io_error() {
|
||||
let io_err = io::Error::new(io::ErrorKind::NotFound, "file missing");
|
||||
let err = Error::Io(io_err);
|
||||
assert_eq!(err.to_string(), "IO error: file missing");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_display_serialization() {
|
||||
let err = Error::Serialization("bad json".to_string());
|
||||
assert_eq!(err.to_string(), "Serialization error: bad json");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_display_not_found() {
|
||||
let err = Error::NotFound("item".to_string());
|
||||
assert_eq!(err.to_string(), "Not found: item");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_display_invalid_data() {
|
||||
let err = Error::InvalidData("corrupt".to_string());
|
||||
assert_eq!(err.to_string(), "Invalid data: corrupt");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_display_workspace_not_found() {
|
||||
let err = Error::WorkspaceNotFound("myws".to_string());
|
||||
assert_eq!(err.to_string(), "Workspace not found: myws");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_display_list_not_found() {
|
||||
let err = Error::ListNotFound("abc-123".to_string());
|
||||
assert_eq!(err.to_string(), "List not found: abc-123");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_display_task_not_found() {
|
||||
let err = Error::TaskNotFound("task-456".to_string());
|
||||
assert_eq!(err.to_string(), "Task not found: task-456");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_display_webdav() {
|
||||
let err = Error::WebDav("connection refused".to_string());
|
||||
assert_eq!(err.to_string(), "WebDAV error: connection refused");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_display_sync() {
|
||||
let err = Error::Sync("conflict".to_string());
|
||||
assert_eq!(err.to_string(), "Sync error: conflict");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_display_credential() {
|
||||
let err = Error::Credential("keychain locked".to_string());
|
||||
assert_eq!(err.to_string(), "Credential error: keychain locked");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_io_error() {
|
||||
let io_err = io::Error::new(io::ErrorKind::PermissionDenied, "denied");
|
||||
let err: Error = io_err.into();
|
||||
assert!(matches!(err, Error::Io(_)));
|
||||
assert!(err.to_string().contains("denied"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_serde_json_error() {
|
||||
let json_err = serde_json::from_str::<serde_json::Value>("{{bad").unwrap_err();
|
||||
let err: Error = json_err.into();
|
||||
assert!(matches!(err, Error::Serialization(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_serde_yaml_error() {
|
||||
let yaml_err = serde_yaml::from_str::<serde_yaml::Value>(":\n :\n ::: bad").unwrap_err();
|
||||
let err: Error = yaml_err.into();
|
||||
assert!(matches!(err, Error::Serialization(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_error_is_std_error() {
|
||||
let err = Error::NotFound("x".to_string());
|
||||
let _: &dyn std::error::Error = &err;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_error_debug() {
|
||||
let err = Error::NotFound("test".to_string());
|
||||
let debug = format!("{:?}", err);
|
||||
assert!(debug.contains("NotFound"));
|
||||
assert!(debug.contains("test"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -117,3 +117,220 @@ impl TaskList {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// --- TaskStatus tests ---
|
||||
|
||||
#[test]
|
||||
fn test_task_status_serde_roundtrip() {
|
||||
let json = serde_json::to_string(&TaskStatus::Backlog).unwrap();
|
||||
assert_eq!(json, "\"backlog\"");
|
||||
let parsed: TaskStatus = serde_json::from_str(&json).unwrap();
|
||||
assert_eq!(parsed, TaskStatus::Backlog);
|
||||
|
||||
let json = serde_json::to_string(&TaskStatus::Completed).unwrap();
|
||||
assert_eq!(json, "\"completed\"");
|
||||
let parsed: TaskStatus = serde_json::from_str(&json).unwrap();
|
||||
assert_eq!(parsed, TaskStatus::Completed);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_task_status_equality() {
|
||||
assert_eq!(TaskStatus::Backlog, TaskStatus::Backlog);
|
||||
assert_eq!(TaskStatus::Completed, TaskStatus::Completed);
|
||||
assert_ne!(TaskStatus::Backlog, TaskStatus::Completed);
|
||||
}
|
||||
|
||||
// --- Task tests ---
|
||||
|
||||
#[test]
|
||||
fn test_task_new_defaults() {
|
||||
let task = Task::new("My Task".to_string());
|
||||
assert_eq!(task.title, "My Task");
|
||||
assert_eq!(task.description, "");
|
||||
assert_eq!(task.status, TaskStatus::Backlog);
|
||||
assert!(task.due_date.is_none());
|
||||
assert!(!task.has_time);
|
||||
assert_eq!(task.version, 0);
|
||||
assert!(task.parent_id.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_task_with_description() {
|
||||
let task = Task::new("T".to_string())
|
||||
.with_description("Some notes".to_string());
|
||||
assert_eq!(task.description, "Some notes");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_task_with_due_date() {
|
||||
let dt = Utc::now();
|
||||
let task = Task::new("T".to_string()).with_due_date(dt);
|
||||
assert_eq!(task.due_date, Some(dt));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_task_with_parent() {
|
||||
let parent_id = Uuid::new_v4();
|
||||
let task = Task::new("Sub".to_string()).with_parent(parent_id);
|
||||
assert_eq!(task.parent_id, Some(parent_id));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_task_complete_and_uncomplete() {
|
||||
let mut task = Task::new("T".to_string());
|
||||
assert_eq!(task.status, TaskStatus::Backlog);
|
||||
|
||||
task.complete();
|
||||
assert_eq!(task.status, TaskStatus::Completed);
|
||||
|
||||
task.uncomplete();
|
||||
assert_eq!(task.status, TaskStatus::Backlog);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_task_builder_chaining() {
|
||||
let parent_id = Uuid::new_v4();
|
||||
let dt = Utc::now();
|
||||
let task = Task::new("Chained".to_string())
|
||||
.with_description("Desc".to_string())
|
||||
.with_due_date(dt)
|
||||
.with_parent(parent_id);
|
||||
|
||||
assert_eq!(task.title, "Chained");
|
||||
assert_eq!(task.description, "Desc");
|
||||
assert_eq!(task.due_date, Some(dt));
|
||||
assert_eq!(task.parent_id, Some(parent_id));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_task_unique_ids() {
|
||||
let t1 = Task::new("A".to_string());
|
||||
let t2 = Task::new("B".to_string());
|
||||
assert_ne!(t1.id, t2.id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_task_serde_roundtrip() {
|
||||
let parent_id = Uuid::new_v4();
|
||||
let task = Task::new("Serde".to_string())
|
||||
.with_description("Desc".to_string())
|
||||
.with_parent(parent_id);
|
||||
let json = serde_json::to_string(&task).unwrap();
|
||||
let parsed: Task = serde_json::from_str(&json).unwrap();
|
||||
assert_eq!(parsed.id, task.id);
|
||||
assert_eq!(parsed.title, "Serde");
|
||||
assert_eq!(parsed.description, "Desc");
|
||||
assert_eq!(parsed.parent_id, Some(parent_id));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_task_serde_skips_none_fields() {
|
||||
let task = Task::new("Minimal".to_string());
|
||||
let json = serde_json::to_string(&task).unwrap();
|
||||
assert!(!json.contains("due_date"));
|
||||
assert!(!json.contains("parent_id"));
|
||||
}
|
||||
|
||||
// --- TaskList tests ---
|
||||
|
||||
#[test]
|
||||
fn test_task_list_new_defaults() {
|
||||
let list = TaskList::new("My List".to_string());
|
||||
assert_eq!(list.title, "My List");
|
||||
assert!(list.tasks.is_empty());
|
||||
assert!(!list.group_by_due_date);
|
||||
assert!(list.created_at <= Utc::now());
|
||||
assert!(list.updated_at <= Utc::now());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_task_list_add_task() {
|
||||
let mut list = TaskList::new("L".to_string());
|
||||
let before = list.updated_at;
|
||||
std::thread::sleep(std::time::Duration::from_millis(2));
|
||||
|
||||
let task = Task::new("T".to_string());
|
||||
let task_id = task.id;
|
||||
list.add_task(task);
|
||||
|
||||
assert_eq!(list.tasks.len(), 1);
|
||||
assert_eq!(list.tasks[0].id, task_id);
|
||||
assert!(list.updated_at >= before);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_task_list_remove_task() {
|
||||
let mut list = TaskList::new("L".to_string());
|
||||
let task = Task::new("T".to_string());
|
||||
let task_id = task.id;
|
||||
list.add_task(task);
|
||||
|
||||
let removed = list.remove_task(task_id);
|
||||
assert!(removed.is_some());
|
||||
assert_eq!(removed.unwrap().id, task_id);
|
||||
assert!(list.tasks.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_task_list_remove_nonexistent_task() {
|
||||
let mut list = TaskList::new("L".to_string());
|
||||
let removed = list.remove_task(Uuid::new_v4());
|
||||
assert!(removed.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_task_list_get_task() {
|
||||
let mut list = TaskList::new("L".to_string());
|
||||
let task = Task::new("T".to_string());
|
||||
let task_id = task.id;
|
||||
list.add_task(task);
|
||||
|
||||
assert!(list.get_task(task_id).is_some());
|
||||
assert_eq!(list.get_task(task_id).unwrap().title, "T");
|
||||
assert!(list.get_task(Uuid::new_v4()).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_task_list_get_task_mut() {
|
||||
let mut list = TaskList::new("L".to_string());
|
||||
let task = Task::new("T".to_string());
|
||||
let task_id = task.id;
|
||||
list.add_task(task);
|
||||
|
||||
let t = list.get_task_mut(task_id).unwrap();
|
||||
t.title = "Modified".to_string();
|
||||
|
||||
assert_eq!(list.get_task(task_id).unwrap().title, "Modified");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_task_list_update_task() {
|
||||
let mut list = TaskList::new("L".to_string());
|
||||
let task = Task::new("Old".to_string());
|
||||
let task_id = task.id;
|
||||
list.add_task(task);
|
||||
|
||||
let mut updated = Task::new("New".to_string());
|
||||
updated.id = task_id;
|
||||
assert!(list.update_task(updated));
|
||||
assert_eq!(list.get_task(task_id).unwrap().title, "New");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_task_list_update_nonexistent_task() {
|
||||
let mut list = TaskList::new("L".to_string());
|
||||
let task = Task::new("Ghost".to_string());
|
||||
assert!(!list.update_task(task));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_task_list_unique_ids() {
|
||||
let l1 = TaskList::new("A".to_string());
|
||||
let l2 = TaskList::new("B".to_string());
|
||||
assert_ne!(l1.id, l2.id);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -446,6 +446,78 @@ mod tests {
|
|||
assert!(!old_path.exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_task_increments_version() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let mut repo = TaskRepository::init(temp_dir.path().to_path_buf()).unwrap();
|
||||
let list = repo.create_list("Test".to_string()).unwrap();
|
||||
|
||||
let task = Task::new("V".to_string());
|
||||
assert_eq!(task.version, 0);
|
||||
let created = repo.create_task(list.id, task).unwrap();
|
||||
assert_eq!(created.version, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_move_task_nonexistent_task() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let mut repo = TaskRepository::init(temp_dir.path().to_path_buf()).unwrap();
|
||||
let list_a = repo.create_list("A".to_string()).unwrap();
|
||||
let list_b = repo.create_list("B".to_string()).unwrap();
|
||||
|
||||
let result = repo.move_task(list_a.id, list_b.id, Uuid::new_v4());
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_move_task_preserves_task_data() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let mut repo = TaskRepository::init(temp_dir.path().to_path_buf()).unwrap();
|
||||
let list_a = repo.create_list("A".to_string()).unwrap();
|
||||
let list_b = repo.create_list("B".to_string()).unwrap();
|
||||
|
||||
let task = Task::new("Rich Task".to_string())
|
||||
.with_description("Important notes".to_string());
|
||||
let task = repo.create_task(list_a.id, task).unwrap();
|
||||
let task_id = task.id;
|
||||
|
||||
repo.move_task(list_a.id, list_b.id, task_id).unwrap();
|
||||
|
||||
let moved = repo.get_task(list_b.id, task_id).unwrap();
|
||||
assert_eq!(moved.title, "Rich Task");
|
||||
assert_eq!(moved.description, "Important notes");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_subtask_creation_and_retrieval() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let mut repo = TaskRepository::init(temp_dir.path().to_path_buf()).unwrap();
|
||||
let list = repo.create_list("Test".to_string()).unwrap();
|
||||
|
||||
let parent = repo.create_task(list.id, Task::new("Parent".to_string())).unwrap();
|
||||
let child = Task::new("Child".to_string()).with_parent(parent.id);
|
||||
let child = repo.create_task(list.id, child).unwrap();
|
||||
|
||||
let retrieved = repo.get_task(list.id, child.id).unwrap();
|
||||
assert_eq!(retrieved.parent_id, Some(parent.id));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multiple_lists_independent() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
let mut repo = TaskRepository::init(temp_dir.path().to_path_buf()).unwrap();
|
||||
|
||||
let list_a = repo.create_list("A".to_string()).unwrap();
|
||||
let list_b = repo.create_list("B".to_string()).unwrap();
|
||||
|
||||
repo.create_task(list_a.id, Task::new("Task A1".to_string())).unwrap();
|
||||
repo.create_task(list_a.id, Task::new("Task A2".to_string())).unwrap();
|
||||
repo.create_task(list_b.id, Task::new("Task B1".to_string())).unwrap();
|
||||
|
||||
assert_eq!(repo.list_tasks(list_a.id).unwrap().len(), 2);
|
||||
assert_eq!(repo.list_tasks(list_b.id).unwrap().len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_task_order_after_delete() {
|
||||
let temp_dir = TempDir::new().unwrap();
|
||||
|
|
|
|||
Loading…
Reference in a new issue