diff --git a/src/export/json.test.ts b/src/export/json.test.ts index 47fe198..c496d91 100644 --- a/src/export/json.test.ts +++ b/src/export/json.test.ts @@ -163,6 +163,23 @@ describe("import rejects invalid folder graph", () => { data.posts[0]!.order = 0; expect(() => parse(data)).toThrow(ImportValidationError); }); + + test("rejects a blank post name", () => { + const data = populated(); + data.posts[0]!.name = " "; + expect(() => parse(data)).toThrow(ImportValidationError); + }); + + test("rejects a blank recurring-cost name", () => { + const data = populated(); + data.recurring.push({ + id: "rent", name: " ", order: 3, folderId: null, archived: false, + amount: { amount: 100, currency: "DKK" }, startDate: "2026-01", + recurrence: { kind: "everyNMonths", n: 1 }, anchoring: "calendar", + splitMode: "percent", splits: [{ postId: data.posts[0]!.id, value: 100, absorbsRemainder: true }], + }); + expect(() => parse(data)).toThrow(ImportValidationError); + }); }); test("describeDataset counts what an import would replace", () => { diff --git a/src/export/json.ts b/src/export/json.ts index 59ff5dd..ee68998 100644 --- a/src/export/json.ts +++ b/src/export/json.ts @@ -190,9 +190,15 @@ function requireFolderTree(data: Dataset): void { requireLocation(folder.parentId, folder.order, `Folder "${folder.id}"`); } for (const post of data.posts) { + if (typeof post?.name !== "string" || !post.name.trim()) { + throw new ImportValidationError("Post has an empty name"); + } requireLocation(post?.folderId, post?.order, `Post "${post?.name}"`); } for (const cost of data.recurring) { + if (typeof cost?.name !== "string" || !cost.name.trim()) { + throw new ImportValidationError("Recurring cost has an empty name"); + } requireLocation(cost?.folderId, cost?.order, `Recurring cost "${cost?.name}"`); } }