feat: add budget folder schema and migration
This commit is contained in:
1 parent
67c77eb247
commit
c34aa846b9
19 files changed
+227
-6
No files matched your search
+54
-2
@@ -114,6 +114,57 @@ describe("validation", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("import rejects invalid folder graph", () => {
|
||||
const folder = (id: string, parentId: string | null, order: number) => ({ id, name: id, parentId, order });
|
||||
const parse = (data: Dataset) => parseDatasetJson(JSON.stringify(data));
|
||||
|
||||
test("valid nested tree parses", () => {
|
||||
const data = populated();
|
||||
data.folders = [folder("home", null, 0), folder("bills", "home", 0)];
|
||||
data.posts[0]!.folderId = "bills";
|
||||
expect(parse(data).posts[0]!.folderId).toBe("bills");
|
||||
});
|
||||
|
||||
test("rejects duplicate folder IDs", () => {
|
||||
const data = populated();
|
||||
data.folders = [folder("same", null, 0), folder("same", null, 1)];
|
||||
expect(() => parse(data)).toThrow(ImportValidationError);
|
||||
});
|
||||
|
||||
test("rejects missing parent", () => {
|
||||
const data = populated();
|
||||
data.folders = [folder("child", "missing", 0)];
|
||||
expect(() => parse(data)).toThrow(ImportValidationError);
|
||||
});
|
||||
|
||||
test("rejects missing item folders", () => {
|
||||
const data = populated();
|
||||
data.posts[0]!.folderId = "missing";
|
||||
expect(() => parse(data)).toThrow(ImportValidationError);
|
||||
data.posts[0]!.folderId = null;
|
||||
data.recurring.push({
|
||||
id: "rent", name: "Rent", order: 3, folderId: "missing", 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("rejects cycles", () => {
|
||||
const data = populated();
|
||||
data.folders = [folder("a", "b", 0), folder("b", "a", 0)];
|
||||
expect(() => parse(data)).toThrow(ImportValidationError);
|
||||
});
|
||||
|
||||
test("rejects duplicate sibling order across item kinds", () => {
|
||||
const data = populated();
|
||||
data.folders = [folder("bills", null, 0)];
|
||||
data.posts[0]!.order = 0;
|
||||
expect(() => parse(data)).toThrow(ImportValidationError);
|
||||
});
|
||||
});
|
||||
|
||||
test("describeDataset counts what an import would replace", () => {
|
||||
expect(describeDataset(populated())).toEqual({
|
||||
posts: 3,
|
||||
@@ -286,7 +337,8 @@ describe("recurring cost validation", () => {
|
||||
const valid = {
|
||||
id: "r1",
|
||||
name: "Rent",
|
||||
order: 0,
|
||||
order: 3,
|
||||
folderId: null,
|
||||
archived: false,
|
||||
amount: { amount: 8000, currency: "DKK" },
|
||||
startDate: "2026-01",
|
||||
@@ -530,7 +582,7 @@ describe("Purchase.source validation", () => {
|
||||
test("a source with a malformed occurrence date is refused", () => {
|
||||
const data: any = JSON.parse(withSource({ recurringId: "r1", occurrenceDate: "nope" }));
|
||||
data.recurring = [{
|
||||
id: "r1", name: "Rent", order: 0, archived: false,
|
||||
id: "r1", name: "Rent", order: 3, folderId: null, archived: false,
|
||||
amount: { amount: 8000, currency: "DKK" },
|
||||
startDate: "2026-01",
|
||||
recurrence: { kind: "everyNMonths", n: 1 },
|
||||
|
||||
Reference in new issue
Block a user