mod common;
use common::{LedgerDir, accepted, check, check_in, refused};
use indoc::indoc;
use rstest::rstest;
const WHOLE: &str = indoc! {r#"
schema_version: 4
prefix: QCTL
active: QCTL-001
queue:
- id: QCTL-001
title: "Quoted: a title with a colon"
scope: qctl
outcome: >-
A folded outcome that wraps
across two lines.
blocked_by: []
acceptance:
- It holds.
patch: a-changeset-name
plan: docs/plan.md
links:
- https://github.com/victor-software-house/qctl
notes:
- Context the other fields cannot carry.
- id: QCTL-002
title: Second
scope: release
outcome: Second outcome.
blocked_by: [QCTL-001]
acceptance: [It also holds.]
archive:
- id: QCTL-003
title: Shipped earlier
scope: qctl
completed: 2026-08-01T09:12:00
outcome: It shipped.
evidence: [The tag exists.]
disposition: completed
horizon:
- id: QCTL-004
title: Someday
scope: qctl
outcome: Something would be true.
kind: research
open: Nobody knows the answer yet.
"#};
#[test]
fn every_field_of_every_row_kind_is_accepted() {
let dir = LedgerDir::empty();
dir.plant("docs/plan.md", "# The plan\n");
let (ok, complaint) = check_in(&dir, WHOLE);
assert!(ok, "refused:\n{complaint}");
}
#[test]
fn refuses_a_plan_that_was_never_written() {
let (ok, complaint) = check(WHOLE);
assert!(!ok, "accepted a plan that does not exist");
assert!(
complaint.contains("missing plan docs/plan.md"),
"{complaint}"
);
}
#[rstest]
#[case::a_paused_ledger(indoc! {"
schema_version: 4
prefix: QCTL
active: null
queue: []
archive: []
"})]
#[case::a_ledger_with_no_horizon_section(indoc! {"
schema_version: 4
prefix: QCTL
active: QCTL-001
queue:
- id: QCTL-001
title: The only row
scope: qctl
outcome: Something is true.
blocked_by: []
acceptance: [It holds.]
archive: []
"})]
#[case::an_archived_row_without_a_disposition(indoc! {"
schema_version: 4
prefix: QCTL
active: null
queue: []
archive:
- id: QCTL-001
title: Shipped
scope: qctl
completed: 2026-08-01T09:12:00
outcome: It shipped.
evidence: [The tag exists.]
"})]
fn accepts_a_ledger(#[case] ledger: &str) {
accepted(ledger);
}
#[rstest]
#[case::a_key_the_root_does_not_have(
indoc! {"
schema_version: 4
prefix: QCTL
active: null
owner: me
queue: []
archive: []
"},
"owner"
)]
#[case::no_schema_version(
indoc! {"
prefix: QCTL
active: null
queue: []
archive: []
"},
"schema_version"
)]
#[case::the_schema_version_this_one_replaced(
indoc! {"
schema_version: 2
prefix: QCTL
active: null
queue: []
archive: []
"},
"schema_version"
)]
#[case::a_lowercase_prefix(
indoc! {"
schema_version: 4
prefix: qctl
active: null
queue: []
archive: []
"},
"prefix"
)]
#[case::a_prefix_longer_than_the_ids_allow(
indoc! {"
schema_version: 4
prefix: QCTLQCTLQ
active: null
queue: []
archive: []
"},
"prefix"
)]
#[case::no_active_line(
indoc! {"
schema_version: 4
prefix: QCTL
queue: []
archive: []
"},
"active"
)]
#[case::an_active_that_is_not_an_id(
indoc! {"
schema_version: 4
prefix: QCTL
active: whatever
queue: []
archive: []
"},
"active"
)]
#[case::no_queue(
indoc! {"
schema_version: 4
prefix: QCTL
active: null
archive: []
"},
"queue"
)]
#[case::no_archive(
indoc! {"
schema_version: 4
prefix: QCTL
active: null
queue: []
"},
"archive"
)]
#[case::a_plan_with_a_backslash_parent(
indoc! {r#"
schema_version: 4
prefix: QCTL
active: null
queue:
- id: QCTL-001
title: t
scope: qctl
outcome: o
blocked_by: []
acceptance: [a]
plan: "..\\secret.md"
archive: []
"#},
"does not match"
)]
#[case::a_file_that_does_not_parse(
indoc! {"
queue:
- id: QCTL-001
title: crooked
"},
"tasks.yaml:3:4:"
)]
fn refuses_a_ledger(#[case] ledger: &str, #[case] field: &str) {
refused(ledger, field);
}
#[test]
fn reports_every_defect_in_one_pass() {
let ledger = indoc! {r#"
schema_version: 4
prefix: QCTL
active: null
queue:
- id: QCTL-001
title: ""
scope: qctl
outcome: Something is true.
blocked_by: [QCTL-000, QCTL-000]
acceptance: [It holds.]
- id: QCTL-001
title: The same id again
scope: qctl
outcome: Something else is true.
blocked_by: []
acceptance: [It also holds.]
archive: []
"#};
let (ok, complaint) = check(ledger);
assert!(!ok);
for expected in [
"title",
"blocked_by",
"duplicate id QCTL-001 appears 2 times in queue",
] {
assert!(
complaint.contains(expected),
"no {expected} in:\n{complaint}"
);
}
}