cartulary 0.3.0-alpha.1

The knowledge layer of your project — decisions, issues, docs, all in one place.
Documentation
use crate::domain::model::decision_record::RecordLinks;
use crate::domain::model::record_ref::DecisionRecordRef;
use crate::domain::usecases::decision_record::DecisionRecordRepository;

/// Return the links for an existing decision record.
///
/// Returns `Err` if the record is not found.
pub fn list_links(
    repo: &dyn DecisionRecordRepository,
    id: &DecisionRecordRef,
) -> anyhow::Result<RecordLinks> {
    let record = repo
        .find_by_id(id)?
        .ok_or_else(|| anyhow::anyhow!("record {id} not found"))?;
    Ok(record.links)
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::domain::usecases::decision_record::tests::{
        adr, FakeDecisionRecordRepository, RecordFixture,
    };

    fn scenario() -> Scenario {
        Scenario {
            repo: FakeDecisionRecordRepository::with_records(vec![]),
        }
    }

    struct Scenario {
        repo: FakeDecisionRecordRepository,
    }

    impl Scenario {
        fn given(mut self, fixture: RecordFixture) -> Self {
            let raw = fixture
                .id
                .as_deref()
                .expect("given() requires an explicit id — use .with_id()")
                .to_string();
            let numeric = DecisionRecordRef::new(&raw)
                .unwrap_or_else(|_| panic!("given(): invalid id {raw:?}"));
            self.repo.push_record(fixture.build(numeric));
            self
        }

        fn when_list_links(self, id: &str) -> Outcome {
            let id_ref = DecisionRecordRef::new(id)
                .unwrap_or_else(|_| panic!("when_list_links: invalid id {id:?}"));
            let result = list_links(&self.repo, &id_ref);
            Outcome { result }
        }
    }

    struct Outcome {
        result: anyhow::Result<RecordLinks>,
    }

    impl Outcome {
        fn then_link_count(self, expected: usize) -> Self {
            let links = self.result.as_ref().expect("expected Ok, got Err");
            assert_eq!(
                links.len(),
                expected,
                "expected {expected} link(s), got {}",
                links.len()
            );
            self
        }

        fn then_link_target(self, index: usize, expected: &str) -> Self {
            let links = self.result.as_ref().expect("expected Ok, got Err");
            assert_eq!(
                links[index].target.as_str(),
                expected,
                "expected link[{index}].target = {expected:?}"
            );
            self
        }

        fn then_err_contains(self, substring: &str) {
            let msg = self.result.expect_err("expected Err, got Ok").to_string();
            assert!(
                msg.contains(substring),
                "expected error containing {substring:?}, got {msg:?}"
            );
        }
    }

    #[test]
    fn listing_links_returns_the_links_of_a_record() {
        scenario()
            .given(
                adr("Use Rust")
                    .with_id("ADR-0001")
                    .with_link("ADR-0001", "supersedes"),
            )
            .when_list_links("ADR-0001")
            .then_link_count(1)
            .then_link_target(0, "ADR-0001");
    }

    #[test]
    fn listing_links_of_an_unknown_record_returns_an_error() {
        scenario()
            .when_list_links("ADR-0099")
            .then_err_contains("not found");
    }

    #[test]
    fn listing_links_returns_empty_when_the_record_has_none() {
        scenario()
            .given(adr("Use Rust").with_id("ADR-0001"))
            .when_list_links("ADR-0001")
            .then_link_count(0);
    }

    // Verify that record_link helper is accessible (used in other link tests)
    #[test]
    fn listing_links_returns_the_correct_relationship() {
        scenario()
            .given(
                adr("Use Rust")
                    .with_id("ADR-0001")
                    .with_link("ADR-0002", "amends"),
            )
            .when_list_links("ADR-0001")
            .then_link_count(1)
            .then_link_target(0, "ADR-0002");
    }
}