mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 06:06:10 +00:00
fix(backup): carry each medication's category through backup and restore
The clinical category a person picks for a medication lives in a side table created at runtime, outside the schema. The backup builder and the restore only walk schema models, so no backup ever carried it: after a restore every medication read as "Other". Both backup formats now carry the category inside its medication, and the restore writes it back in the same transaction as the medication. A file from before this change restores as before; a category this server does not know reads as Other instead of refusing the file. The backup plan gains a list for tables created with raw SQL, and a check that every such table in the source either maps to a schema model or has a verdict there, so the next one cannot drop out of backups unnoticed.
This commit is contained in:
1 parent
e94e9b94a8
commit
6f37cbdb23
10 files changed
+215
-5
No files matched your search
@@ -1,5 +1,5 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import { readdirSync, readFileSync } from "node:fs";
|
||||
import { join, resolve } from "node:path";
|
||||
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
BACKED_UP_MODELS,
|
||||
DERIVED_MODELS,
|
||||
NOT_IN_BACKUP_MODELS,
|
||||
RAW_SQL_TABLES,
|
||||
backupVerdict,
|
||||
} from "@/lib/export/backup-plan";
|
||||
|
||||
@@ -491,3 +492,62 @@ describe("every backed-up model travels both ways, or is named as debt", () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("tables outside the schema have a backup verdict too", () => {
|
||||
const SRC = resolve(__dirname, "..");
|
||||
|
||||
function sourceFiles(dir: string): string[] {
|
||||
return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
|
||||
const full = join(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
return entry.name === "generated" || entry.name === "__tests__"
|
||||
? []
|
||||
: sourceFiles(full);
|
||||
}
|
||||
return /\.tsx?$/.test(entry.name) ? [full] : [];
|
||||
});
|
||||
}
|
||||
|
||||
function rawTables(): string[] {
|
||||
const names = new Set<string>();
|
||||
for (const file of sourceFiles(SRC)) {
|
||||
const text = readFileSync(file, "utf8");
|
||||
for (const m of text.matchAll(
|
||||
/CREATE\s+TABLE\s+IF\s+NOT\s+EXISTS\s+"?(\w+)"?/gi,
|
||||
)) {
|
||||
names.add(m[1]);
|
||||
}
|
||||
}
|
||||
return [...names];
|
||||
}
|
||||
|
||||
const mappedTables = new Set(
|
||||
[...readFileSync(SCHEMA_PATH, "utf8").matchAll(/@@map\("(\w+)"\)/g)].map(
|
||||
(m) => m[1],
|
||||
),
|
||||
);
|
||||
|
||||
it("finds the raw tables it is meant to judge (no vacuous pass)", () => {
|
||||
expect(rawTables()).toContain("medication_categories");
|
||||
});
|
||||
|
||||
it("gives every raw table that is not a schema model a verdict", () => {
|
||||
const unjudged = rawTables().filter(
|
||||
(name) => !mappedTables.has(name) && !(name in RAW_SQL_TABLES),
|
||||
);
|
||||
expect(unjudged).toEqual([]);
|
||||
});
|
||||
|
||||
it("carries the medication category through both ends", () => {
|
||||
const writer = readFileSync(
|
||||
join(SRC, "lib/export/full-backup-payload.ts"),
|
||||
"utf8",
|
||||
);
|
||||
const restore = readFileSync(
|
||||
join(SRC, "lib/export/restore-backup.ts"),
|
||||
"utf8",
|
||||
);
|
||||
expect(writer).toMatch(/getMedicationCategories\(/);
|
||||
expect(restore).toMatch(/setMedicationCategory\(/);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user