test(core): every surviving mutant killed or argued equivalent
Max-testing pass, step 8. The final mutation run left 112 survivors (89% caught). Three agents triaged them by file, by the playbook's table: spec the boundary then test it, a law rather than one more pinned value, assert the real value, never weaken a test. Every expected value is hand-computed. - reports.rs (35): 28 killed, chiefly cash flow (inclusive start date, each line's sign and label, a loss on disposal moving to investing, financing designation), general ledger and members' equity date boundaries, and the balanced() checks (a real report nudged by a cent must stop balancing). 7 equivalent: four >/>= after an early return on zero; two in dead code (removed in this commit); one sign of a zero cleaned on output. - ledger.rs, kernel.rs, journal.rs, money.rs (52, plus one new): 49 killed, including every replay_* mutant at the core level (ids, accumulation, status, vendor tags, out-of-order refusal), FX rates, 0%/100% deductible boundaries, a dividend sweep with no income, the legacy member rounding at close, a bill paid on its own date, a liability-account reconciliation, mixed-currency entries (via serde), ISO 4217 minor units, Display/Debug. 3 equivalent: zero refused before a </<= check; a period whose end is in a closed period refused earlier; columns(0). - schedules, tax, analysis, payouts, assets, form1099 (25): 23 killed (schedule length 1-600, recurring end on its start, frequency parsing and stepping, cost of goods in tax net, a zero payout, disposal on the purchase date, quarter starts, labels); 2 equivalent (zero adds zero; days_held is provably >= 1). Two small production cleanups the triage surfaced: kernel::columns(0) returned a negative-zero credit that would print "-0" (regression a_zero_movement_has_no_negative_zero_column, shown to fail before); and cash_flow computed a book value it never used. Spec lines added where a boundary wasn't specified (cash flow's inclusive start and line signs, members' equity start date, payout with no deposit, disposal on the purchase date, bill paid on its own date). `just mutants` now runs core and store tests (the store's replay tests), not the whole workspace, which rebuilt the viewer for every mutant. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
This commit is contained in:
1 parent
b2b49267db
commit
bf3f5b4cdb
15 files changed
+989
-5
No files matched your search
@@ -224,3 +224,100 @@ fn recurring_entries_generate_drafts_never_posts() {
|
||||
assert!(matches!(bad, Err(LedgerError::InvalidSchedule(_))));
|
||||
let _ = b.revenue;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn schedule_length_is_one_to_six_hundred_months() {
|
||||
let b = books();
|
||||
let sched = |months| NewSchedule {
|
||||
description: "Insurance".into(),
|
||||
kind: ScheduleKind::PrepaidExpense,
|
||||
total: usd("600"),
|
||||
start: d(1, 1),
|
||||
months,
|
||||
balance_account: b.prepaid,
|
||||
pl_account: b.insurance,
|
||||
};
|
||||
let chart = b.l.chart();
|
||||
for ok in [1, 600] {
|
||||
assert!(
|
||||
ledger_core::schedules::validate_schedule(&sched(ok), chart).is_ok(),
|
||||
"{ok} months"
|
||||
);
|
||||
}
|
||||
for bad in [0, 601] {
|
||||
assert!(
|
||||
matches!(
|
||||
ledger_core::schedules::validate_schedule(&sched(bad), chart),
|
||||
Err(LedgerError::InvalidSchedule(_))
|
||||
),
|
||||
"{bad} months"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recurring_templates_take_any_number_of_lines_and_may_end_on_their_start() {
|
||||
let mut b = books();
|
||||
// Three balanced lines are a valid template.
|
||||
b.l.register_recurring(NewRecurring {
|
||||
description: "Rent and insurance".into(),
|
||||
lines: vec![
|
||||
(b.rent, Side::Debit, usd("2000")),
|
||||
(b.insurance, Side::Debit, usd("100")),
|
||||
(b.cash, Side::Credit, usd("2100")),
|
||||
],
|
||||
frequency: Frequency::Monthly,
|
||||
start: d(1, 1),
|
||||
end: None,
|
||||
})
|
||||
.unwrap();
|
||||
// An end on the start date is one occurrence, not an error.
|
||||
let once =
|
||||
b.l.register_recurring(NewRecurring {
|
||||
description: "One-off".into(),
|
||||
lines: vec![
|
||||
(b.rent, Side::Debit, usd("2000")),
|
||||
(b.cash, Side::Credit, usd("2000")),
|
||||
],
|
||||
frequency: Frequency::Monthly,
|
||||
start: d(5, 1),
|
||||
end: Some(d(5, 1)),
|
||||
})
|
||||
.unwrap();
|
||||
let r = b.l.recurring_entries().find(|r| r.id == once).unwrap();
|
||||
assert_eq!(r.due(d(12, 31)), [d(5, 1)]);
|
||||
// An end before the start is.
|
||||
let bad = b.l.register_recurring(NewRecurring {
|
||||
description: "Backwards".into(),
|
||||
lines: vec![
|
||||
(b.rent, Side::Debit, usd("2000")),
|
||||
(b.cash, Side::Credit, usd("2000")),
|
||||
],
|
||||
frequency: Frequency::Monthly,
|
||||
start: d(5, 2),
|
||||
end: Some(d(5, 1)),
|
||||
});
|
||||
assert!(matches!(bad, Err(LedgerError::InvalidSchedule(_))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn frequencies_parse_and_step_from_the_start() {
|
||||
assert_eq!(Frequency::parse("weekly"), Some(Frequency::Weekly));
|
||||
assert_eq!(Frequency::parse(" Monthly "), Some(Frequency::Monthly));
|
||||
assert_eq!(Frequency::parse("QUARTERLY"), Some(Frequency::Quarterly));
|
||||
assert_eq!(Frequency::parse("annually"), Some(Frequency::Yearly));
|
||||
assert_eq!(Frequency::parse("fortnightly"), None);
|
||||
// Weekly: the kth occurrence is 7k days on.
|
||||
let start = d(1, 1);
|
||||
for (k, want) in [(0, d(1, 1)), (1, d(1, 8)), (2, d(1, 15)), (3, d(1, 22))] {
|
||||
assert_eq!(Frequency::Weekly.nth(start, k), Some(want), "week {k}");
|
||||
}
|
||||
assert_eq!(Frequency::Quarterly.nth(start, 2), Some(d(7, 1)));
|
||||
for (k, year) in [(1, 2027), (2, 2028), (3, 2029)] {
|
||||
assert_eq!(
|
||||
Frequency::Yearly.nth(start, k),
|
||||
NaiveDate::from_ymd_opt(year, 1, 1),
|
||||
"year {k}"
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user