godchat-core 0.1.0

Core types and traits for godchat
Documentation
use std::collections::HashMap;
use std::sync::Mutex;

use crate::ports::TaskRepo;
use crate::types::*;

pub struct InMemoryTaskRepo {
    tasks: Mutex<HashMap<TaskId, TaskSummary>>,
    next_id: Mutex<i64>,
}

impl InMemoryTaskRepo {
    pub fn new() -> Self {
        Self {
            tasks: Mutex::new(HashMap::new()),
            next_id: Mutex::new(1),
        }
    }
}

impl TaskRepo for InMemoryTaskRepo {
    fn create(&self, task: &Task<Conceptualizing>) -> TaskId {
        let mut next = self.next_id.lock().unwrap();
        let id = TaskId::parse(*next).unwrap();
        *next += 1;

        let mut summary = task.to_summary();
        summary.id = id.clone();
        self.tasks.lock().unwrap().insert(id.clone(), summary);
        id
    }

    fn get(&self, id: &TaskId) -> Option<TaskSummary> {
        self.tasks.lock().unwrap().get(id).cloned()
    }

    fn list_by_project(&self, project_id: &ProjectId) -> Vec<TaskSummary> {
        self.tasks
            .lock()
            .unwrap()
            .values()
            .filter(|t| t.project_id == *project_id)
            .cloned()
            .collect()
    }

    fn save(&self, task: &TaskSummary) {
        self.tasks
            .lock()
            .unwrap()
            .insert(task.id.clone(), task.clone());
    }

    fn children(&self, parent_id: &TaskId) -> Vec<TaskSummary> {
        self.tasks
            .lock()
            .unwrap()
            .values()
            .filter(|t| t.parent_id.as_ref() == Some(parent_id))
            .cloned()
            .collect()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::mocks::fixtures::{pid, tname};

    const ALPHA: &str = "alpha";

    fn test_task() -> Task<Conceptualizing> {
        Task::new(TaskData {
            id: TaskId::parse(1).unwrap(),
            project_id: pid(ALPHA),
            parent_id: None,
            name: tname("fix-auth"),
            title: "Fix authentication".into(),
            context_brief: None,
            summary: None,
            claude_session: None,
            created_at: Utc::now(),
            updated_at: Utc::now(),
        })
    }

    #[test]
    fn create_assigns_id() {
        let repo = InMemoryTaskRepo::new();
        let id = repo.create(&test_task());
        assert_eq!(id.as_i64(), 1);
    }

    #[test]
    fn create_increments_id() {
        let repo = InMemoryTaskRepo::new();
        let id1 = repo.create(&test_task());
        let id2 = repo.create(&test_task());
        assert_eq!(id1.as_i64(), 1);
        assert_eq!(id2.as_i64(), 2);
    }

    #[test]
    fn get_after_create() {
        let repo = InMemoryTaskRepo::new();
        let id = repo.create(&test_task());
        let found = repo.get(&id).unwrap();
        assert_eq!(found.name, tname("fix-auth"));
        assert_eq!(found.status, TaskStatus::Conceptualizing);
    }

    #[test]
    fn save_persists_state_change() {
        let repo = InMemoryTaskRepo::new();
        let id = repo.create(&test_task());

        let summary = repo.get(&id).unwrap();
        let typed = summary.into_typed();
        match typed {
            AnyTask::Conceptualizing(task) => {
                let executing = task.execute();
                repo.save(&executing.to_summary());
            }
            _ => panic!("expected Conceptualizing"),
        }

        let found = repo.get(&id).unwrap();
        assert_eq!(found.status, TaskStatus::Executing);
    }

    #[test]
    fn list_by_project_filters() {
        let repo = InMemoryTaskRepo::new();
        repo.create(&test_task());
        repo.create(&Task::new(TaskData {
            project_id: pid("other"),
            ..test_task().data
        }));

        let alpha_tasks = repo.list_by_project(&pid(ALPHA));
        assert_eq!(alpha_tasks.len(), 1);
    }

    #[test]
    fn children_finds_subtasks() {
        let repo = InMemoryTaskRepo::new();
        let parent_id = repo.create(&test_task());
        repo.create(&Task::new(TaskData {
            parent_id: Some(parent_id.clone()),
            name: tname("subtask"),
            ..test_task().data
        }));

        let kids = repo.children(&parent_id);
        assert_eq!(kids.len(), 1);
    }
}