use std::fmt;
use chrono::Local;
use serde::{Deserialize, Serialize};
use strum::{AsRefStr, EnumIter, EnumMessage, EnumString};
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Serialize,
Deserialize,
Default,
EnumString,
EnumMessage,
EnumIter,
AsRefStr,
)]
pub enum Priority {
#[strum(serialize = "l", serialize = "0", serialize = "low")]
Low,
#[default]
#[strum(serialize = "n", serialize = "1", serialize = "normal")]
Normal,
#[strum(serialize = "h", serialize = "2", serialize = "high")]
High,
#[strum(serialize = "u", serialize = "3", serialize = "urgent")]
Urgent,
}
impl fmt::Display for Priority {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_ref())
}
}
#[derive(
Debug,
Clone,
Copy,
PartialEq,
Eq,
PartialOrd,
Ord,
Serialize,
Deserialize,
Default,
EnumString,
EnumMessage,
EnumIter,
AsRefStr,
)]
pub enum TaskStatus {
#[strum(serialize = "a", serialize = "aborted")]
Aborted,
#[strum(serialize = "d", serialize = "done")]
Done,
#[default]
#[strum(serialize = "t", serialize = "todo")]
Todo,
}
impl fmt::Display for TaskStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_ref())
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Task {
pub id: String,
pub description: String,
pub priority: Priority,
pub scope: Option<String>,
pub task_type: Option<String>,
pub status: TaskStatus,
pub created_at: String,
pub updated_at: Option<String>,
pub completed_at: Option<String>,
pub time_spent: Option<u64>,
}
impl Task {
pub fn new(
id: String,
description: String,
priority: Priority,
scope: Option<String>,
task_type: Option<String>,
) -> Self {
Self {
id,
description,
priority,
scope,
task_type,
status: TaskStatus::Todo,
created_at: Local::now().to_rfc3339(),
updated_at: None,
completed_at: None,
time_spent: None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_task_creation() {
let id = "test-id".to_string();
let description = "Test task".to_string();
let priority = Priority::Normal;
let scope = Some("test-scope".to_string());
let task_type = Some("test-type".to_string());
let task = Task::new(
id.clone(),
description.clone(),
priority,
scope.clone(),
task_type.clone(),
);
assert_eq!(task.id, id);
assert_eq!(task.description, description);
assert_eq!(task.priority, priority);
assert_eq!(task.scope, scope);
assert_eq!(task.task_type, task_type);
assert_eq!(task.status, TaskStatus::Todo);
assert!(task.updated_at.is_none());
assert!(task.completed_at.is_none());
assert!(task.time_spent.is_none());
assert!(chrono::DateTime::parse_from_rfc3339(&task.created_at).is_ok());
}
#[test]
fn test_priority_display() {
assert_eq!(Priority::Urgent.to_string(), "urgent");
assert_eq!(Priority::High.to_string(), "high");
assert_eq!(Priority::Normal.to_string(), "normal");
assert_eq!(Priority::Low.to_string(), "low");
}
#[test]
fn test_status_display() {
assert_eq!(TaskStatus::Todo.to_string(), "todo");
assert_eq!(TaskStatus::Done.to_string(), "done");
assert_eq!(TaskStatus::Aborted.to_string(), "aborted");
}
#[test]
fn test_priority_equality() {
assert_eq!(Priority::Urgent, Priority::Urgent);
assert_ne!(Priority::Urgent, Priority::High);
assert_ne!(Priority::High, Priority::Normal);
assert_ne!(Priority::Normal, Priority::Low);
}
#[test]
fn test_status_equality() {
assert_eq!(TaskStatus::Todo, TaskStatus::Todo);
assert_ne!(TaskStatus::Todo, TaskStatus::Done);
assert_ne!(TaskStatus::Done, TaskStatus::Aborted);
assert_ne!(TaskStatus::Aborted, TaskStatus::Todo);
}
#[test]
fn test_priority_default() {
let default_priority = Priority::default();
assert_eq!(default_priority, Priority::Normal);
}
#[test]
fn test_status_default() {
let default_status = TaskStatus::default();
assert_eq!(default_status, TaskStatus::Todo);
}
#[test]
fn test_task_serialization_deserialization() {
let original_task = Task {
id: "test-id".to_string(),
description: "Test description".to_string(),
priority: Priority::High,
scope: Some("test-scope".to_string()),
task_type: Some("test-type".to_string()),
status: TaskStatus::Todo,
created_at: "2023-01-01T12:00:00+00:00".to_string(),
updated_at: Some("2023-01-02T12:00:00+00:00".to_string()),
completed_at: None,
time_spent: Some(3600),
};
let toml_string = toml::to_string(&original_task).unwrap();
assert!(toml_string.contains("id = \"test-id\""));
assert!(toml_string.contains("description = \"Test description\""));
assert!(toml_string.contains("priority = \"High\""));
assert!(toml_string.contains("scope = \"test-scope\""));
assert!(toml_string.contains("task_type = \"test-type\""));
assert!(toml_string.contains("status = \"Todo\""));
assert!(toml_string.contains("created_at = \"2023-01-01T12:00:00+00:00\""));
assert!(toml_string.contains("updated_at = \"2023-01-02T12:00:00+00:00\""));
assert!(!toml_string.contains("completed_at")); assert!(toml_string.contains("time_spent = 3600"));
let deserialized_task: Task = toml::from_str(&toml_string).unwrap();
assert_eq!(deserialized_task.id, original_task.id);
assert_eq!(deserialized_task.description, original_task.description);
assert_eq!(deserialized_task.priority, original_task.priority);
assert_eq!(deserialized_task.scope, original_task.scope);
assert_eq!(deserialized_task.task_type, original_task.task_type);
assert_eq!(deserialized_task.status, original_task.status);
assert_eq!(deserialized_task.created_at, original_task.created_at);
assert_eq!(deserialized_task.updated_at, original_task.updated_at);
assert_eq!(deserialized_task.completed_at, original_task.completed_at);
assert_eq!(deserialized_task.time_spent, original_task.time_spent);
}
#[test]
fn test_priority_clone_and_copy() {
let p1 = Priority::Urgent;
let p2 = p1; let p3 = p1;
assert_eq!(p1, p2);
assert_eq!(p1, p3);
let p4 = Priority::Low;
assert_ne!(p1, p4);
assert_ne!(p2, p4);
assert_ne!(p3, p4);
}
#[test]
fn test_status_clone_and_copy() {
let s1 = TaskStatus::Done;
let s2 = s1; let s3 = s1;
assert_eq!(s1, s2);
assert_eq!(s1, s3);
let s4 = TaskStatus::Aborted;
assert_ne!(s1, s4);
assert_ne!(s2, s4);
assert_ne!(s3, s4);
}
#[test]
fn test_debug_representation() {
let debug_str = format!("{:?}", Priority::Urgent);
assert!(debug_str.contains("Urgent"));
let debug_str = format!("{:?}", TaskStatus::Done);
assert!(debug_str.contains("Done"));
let task = Task::new(
"debug-test".to_string(),
"Test description".to_string(),
Priority::Normal,
None,
None,
);
let debug_str = format!("{task:?}");
assert!(debug_str.contains("debug-test"));
assert!(debug_str.contains("Test description"));
assert!(debug_str.contains("Normal"));
assert!(debug_str.contains("Todo"));
}
}