ito-core 0.1.33

Core functionality and business logic for Ito
Documentation
use super::*;
use chrono::Utc;
use ito_domain::tasks::TasksParseResult;

/// In-memory backend reader for testing.
struct FakeReader {
    changes: Vec<ChangeSummary>,
    full: Vec<Change>,
}

impl FakeReader {
    fn new(changes: Vec<ChangeSummary>, full: Vec<Change>) -> Self {
        Self { changes, full }
    }
}

impl BackendChangeReader for FakeReader {
    fn list_changes(
        &self,
        _filter: ito_domain::changes::ChangeLifecycleFilter,
    ) -> DomainResult<Vec<ChangeSummary>> {
        Ok(self.changes.clone())
    }

    fn get_change(
        &self,
        change_id: &str,
        _filter: ito_domain::changes::ChangeLifecycleFilter,
    ) -> DomainResult<Change> {
        for c in &self.full {
            if c.id == change_id {
                return Ok(c.clone());
            }
        }
        Err(ito_domain::errors::DomainError::not_found(
            "change", change_id,
        ))
    }
}

fn make_summary(id: &str, completed: u32, total: u32) -> ChangeSummary {
    ChangeSummary {
        id: id.to_string(),
        module_id: None,
        sub_module_id: None,
        completed_tasks: completed,
        shelved_tasks: 0,
        in_progress_tasks: 0,
        pending_tasks: total - completed,
        total_tasks: total,
        last_modified: Utc::now(),
        has_proposal: true,
        has_design: false,
        has_specs: true,
        has_tasks: true,
        orchestrate: ito_domain::changes::ChangeOrchestrateMetadata::default(),
    }
}

fn make_change(id: &str) -> Change {
    Change {
        id: id.to_string(),
        module_id: None,
        sub_module_id: None,
        path: std::path::PathBuf::from("/fake"),
        proposal: Some("# Proposal".to_string()),
        design: None,
        specs: vec![],
        tasks: TasksParseResult::empty(),
        orchestrate: ito_domain::changes::ChangeOrchestrateMetadata::default(),
        last_modified: Utc::now(),
    }
}

#[test]
fn list_returns_all_changes() {
    let summaries = vec![
        make_summary("001-01_a", 0, 2),
        make_summary("001-02_b", 1, 2),
    ];
    let reader = FakeReader::new(summaries.clone(), vec![]);
    let repo = BackendChangeRepository::new(reader);

    let result = repo.list().unwrap();
    assert_eq!(result.len(), 2);
}

#[test]
fn resolve_target_exact_match() {
    let summaries = vec![make_summary("001-01_a", 0, 2)];
    let reader = FakeReader::new(summaries, vec![]);
    let repo = BackendChangeRepository::new(reader);

    let resolution = DomainChangeRepository::resolve_target(&repo, "001-01_a");
    assert!(matches!(resolution, ChangeTargetResolution::Unique(id) if id == "001-01_a"));
}

#[test]
fn resolve_target_prefix_match() {
    let summaries = vec![make_summary("001-01_something", 0, 2)];
    let reader = FakeReader::new(summaries, vec![]);
    let repo = BackendChangeRepository::new(reader);

    let resolution = DomainChangeRepository::resolve_target(&repo, "001-01");
    assert!(matches!(resolution, ChangeTargetResolution::Unique(id) if id == "001-01_something"));
}

#[test]
fn resolve_target_ambiguous() {
    let summaries = vec![
        make_summary("001-01_a", 0, 2),
        make_summary("001-01_b", 0, 2),
    ];
    let reader = FakeReader::new(summaries, vec![]);
    let repo = BackendChangeRepository::new(reader);

    let resolution = DomainChangeRepository::resolve_target(&repo, "001-01");
    assert!(matches!(resolution, ChangeTargetResolution::Ambiguous(_)));
}

#[test]
fn resolve_target_not_found() {
    let summaries = vec![make_summary("001-01_a", 0, 2)];
    let reader = FakeReader::new(summaries, vec![]);
    let repo = BackendChangeRepository::new(reader);

    let resolution = DomainChangeRepository::resolve_target(&repo, "999");
    assert!(matches!(resolution, ChangeTargetResolution::NotFound));
}

#[test]
fn get_delegates_to_reader() {
    let change = make_change("001-01_a");
    let reader = FakeReader::new(vec![], vec![change]);
    let repo = BackendChangeRepository::new(reader);

    let result = DomainChangeRepository::get(&repo, "001-01_a").unwrap();
    assert_eq!(result.id, "001-01_a");
}

#[test]
fn list_incomplete_filters_correctly() {
    let summaries = vec![
        make_summary("001-01_a", 2, 2),
        make_summary("001-02_b", 1, 2),
    ];
    let reader = FakeReader::new(summaries, vec![]);
    let repo = BackendChangeRepository::new(reader);

    let incomplete = DomainChangeRepository::list_incomplete(&repo).unwrap();
    assert_eq!(incomplete.len(), 1);
    assert_eq!(incomplete[0].id, "001-02_b");
}

#[test]
fn list_complete_filters_correctly() {
    let summaries = vec![
        make_summary("001-01_a", 2, 2),
        make_summary("001-02_b", 1, 2),
    ];
    let reader = FakeReader::new(summaries, vec![]);
    let repo = BackendChangeRepository::new(reader);

    let complete = DomainChangeRepository::list_complete(&repo).unwrap();
    assert_eq!(complete.len(), 1);
    assert_eq!(complete[0].id, "001-01_a");
}