fix(validation): review round 1 — close the store's source hole, fix ordering and messages
I1: a calendar charge's MISSING occurrenceDate no longer reports the literal word "undefined" (String(undefined) printed it, and that text appears nowhere in the file someone is searching). Missing and malformed are split into two messages; the missing one names the cost, matching the anchored side's message. I2: addPurchase/updatePurchase accepted an arbitrary `source` unchecked — only confirmOccurrence built a coherent one. A later task hand-builds a source and passes it straight to addPurchase, so this closes it at the store layer now rather than leaving that task to carry the validation. Both enforce the same pairing the importer does: a calendar cost's source requires occurrenceDate, an anchored cost's source forbids it, an unknown cost throws. updatePurchase checks the resolved pair (incoming source if given, else the stored one), the same style already used for anchoring. M1: the "no recurring costs" fold test added last round was a duplicate of an existing one and couldn't fail independently. Replaced with a test that actually discriminates which charge an anchored cost's walk steps from — a 10-day cycle matching the gap between three charges, where anchoring off the earliest instead of the latest changes whether anything is still expected in the same month. Verified by making that swap in occurrences.ts, watching it fail, and reverting. M2: updateRecurringCost now refuses a charged cost's anchoring change before checking startDate granularity, so a doomed request fails on the real reason first rather than making the owner fix an unrelated date problem and resubmit into the same wall. Also closed two pre-existing asymmetries with json.ts: requireRecurrence now rejects an unknown recurrence kind, and both addRecurringCost and updateRecurringCost reject an unknown anchoring — both previously relied on TypeScript's unions alone, which a hand-edited object or an `as` cast does not go through.
This commit is contained in:
1 parent
77bd049fb9
commit
7a755f6406
5 files changed
+322
-26
No files matched your search
@@ -606,6 +606,30 @@ describe("the occurrenceDate / anchoring pairing", () => {
|
||||
test("an anchored charge without occurrenceDate is accepted", () => {
|
||||
expect(() => parseDatasetJson(withCostAndCharge("lastCharge", { recurringId: "r1" }))).not.toThrow();
|
||||
});
|
||||
|
||||
// REVIEW ROUND 1 / I1: a calendar charge with a genuinely MISSING
|
||||
// occurrenceDate used to be reported with `String(undefined)`, which prints
|
||||
// the literal word "undefined" — so someone restoring a backup would
|
||||
// search their file for that text and find nothing, since the file has no
|
||||
// such string anywhere. Missing and malformed are different problems, and
|
||||
// the missing case now names the cost instead.
|
||||
test("a calendar charge missing occurrenceDate names the cost, not the word 'undefined'", () => {
|
||||
let message = "";
|
||||
try {
|
||||
parseDatasetJson(withCostAndCharge("calendar", { recurringId: "r1" }));
|
||||
} catch (error) {
|
||||
message = (error as Error).message;
|
||||
}
|
||||
expect(message).toMatch(/Bill/); // the cost's name, from withCostAndCharge's fixture
|
||||
expect(message).not.toMatch(/undefined/);
|
||||
});
|
||||
|
||||
// The malformed case (present but not a real date) keeps its old shape.
|
||||
test("a calendar charge with a malformed occurrenceDate still names the bad value", () => {
|
||||
expect(() =>
|
||||
parseDatasetJson(withCostAndCharge("calendar", { recurringId: "r1", occurrenceDate: "nope" })),
|
||||
).toThrow(/invalid occurrence date "nope"/);
|
||||
});
|
||||
});
|
||||
|
||||
describe("startDate granularity under the anchored model", () => {
|
||||
|
||||
Reference in new issue
Block a user