mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 20:42:43 +00:00
fix(quality): close backup and localization guard gaps
This commit is contained in:
1 parent
477485ba47
commit
a2f2a30ac2
11 files changed
+46
-16
No files matched your search
+2
-1
@@ -2865,7 +2865,8 @@
|
||||
"flights": "Etagen",
|
||||
"falls": "Stürze",
|
||||
"years": "Jahre",
|
||||
"breathingEvents": "Ereignisse"
|
||||
"breathingEvents": "Ereignisse",
|
||||
"respiratoryRate": "Atemzüge/min"
|
||||
},
|
||||
"sleep": {
|
||||
"title": "Schlaf",
|
||||
|
||||
+2
-1
@@ -2865,7 +2865,8 @@
|
||||
"flights": "flights",
|
||||
"falls": "falls",
|
||||
"years": "years",
|
||||
"breathingEvents": "events"
|
||||
"breathingEvents": "events",
|
||||
"respiratoryRate": "breaths/min"
|
||||
},
|
||||
"sleep": {
|
||||
"title": "Sleep",
|
||||
|
||||
+2
-1
@@ -2865,7 +2865,8 @@
|
||||
"flights": "pisos",
|
||||
"falls": "caídas",
|
||||
"years": "años",
|
||||
"breathingEvents": "eventos"
|
||||
"breathingEvents": "eventos",
|
||||
"respiratoryRate": "respiraciones/min"
|
||||
},
|
||||
"sleep": {
|
||||
"title": "Sueño",
|
||||
|
||||
+2
-1
@@ -2865,7 +2865,8 @@
|
||||
"flights": "étages",
|
||||
"falls": "chutes",
|
||||
"years": "ans",
|
||||
"breathingEvents": "événements"
|
||||
"breathingEvents": "événements",
|
||||
"respiratoryRate": "respirations/min"
|
||||
},
|
||||
"sleep": {
|
||||
"title": "Sommeil",
|
||||
|
||||
+2
-1
@@ -2865,7 +2865,8 @@
|
||||
"flights": "piani",
|
||||
"falls": "cadute",
|
||||
"years": "anni",
|
||||
"breathingEvents": "eventi"
|
||||
"breathingEvents": "eventi",
|
||||
"respiratoryRate": "respiri/min"
|
||||
},
|
||||
"sleep": {
|
||||
"title": "Sonno",
|
||||
|
||||
+2
-1
@@ -2865,7 +2865,8 @@
|
||||
"flights": "piętra",
|
||||
"falls": "upadki",
|
||||
"years": "lata",
|
||||
"breathingEvents": "zdarzenia"
|
||||
"breathingEvents": "zdarzenia",
|
||||
"respiratoryRate": "oddechów/min"
|
||||
},
|
||||
"sleep": {
|
||||
"title": "Sen",
|
||||
|
||||
@@ -187,12 +187,15 @@ function relationFieldNames(model: string): string[] {
|
||||
|
||||
const READ_OPS = /(findMany|findUnique|findFirst|findUniqueOrThrow|groupBy)/;
|
||||
const WRITE_OPS = /(create|createMany|upsert|update|updateMany)/;
|
||||
const READ_RELATION_OPS = /[{[t]/;
|
||||
const WRITE_RELATION_OPS = /\{\s*create\b/;
|
||||
|
||||
/** Does any of `files` touch `model` with an operation matching `ops`? */
|
||||
/** Does any of `files` touch `model` through matching delegate and relation operations? */
|
||||
function touches(
|
||||
files: readonly string[],
|
||||
model: string,
|
||||
ops: RegExp,
|
||||
delegateOps: RegExp,
|
||||
relationOps: RegExp,
|
||||
): boolean {
|
||||
const delegate = delegateName(model);
|
||||
const relations = relationFieldNames(model);
|
||||
@@ -202,16 +205,28 @@ function touches(
|
||||
for (const match of source.matchAll(
|
||||
new RegExp(`\\.${delegate}\\s*\\.\\s*(\\w+)`, "g"),
|
||||
)) {
|
||||
if (ops.test(match[1])) return true;
|
||||
if (delegateOps.test(match[1])) return true;
|
||||
}
|
||||
// A nested relation key: `schedules: {`, `dayLogs: [`, `entries: true`.
|
||||
return relations.some((relation) =>
|
||||
new RegExp(`\\b${relation}\\s*:\\s*[{[t]`).test(source),
|
||||
new RegExp(`\\b${relation}\\s*:\\s*(?:${relationOps.source})`).test(
|
||||
source,
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
describe("every backed-up model travels both ways, or is named as debt", () => {
|
||||
it("does not count a nested relation read as a restore write", () => {
|
||||
expect(
|
||||
touches(
|
||||
["src/app/api/admin/backups/[id]/restore/route.ts"],
|
||||
"User",
|
||||
WRITE_OPS,
|
||||
WRITE_RELATION_OPS,
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("declares writer and restore files that exist", () => {
|
||||
for (const file of [...BACKUP_WRITER_FILES, ...BACKUP_RESTORE_FILES]) {
|
||||
expect(() => read(file), `${file} is declared but missing`).not.toThrow();
|
||||
@@ -250,7 +265,8 @@ describe("every backed-up model travels both ways, or is named as debt", () => {
|
||||
|
||||
it("finds a payload reader for every model claimed two-ended", () => {
|
||||
const missing = TWO_ENDED_MODELS.filter(
|
||||
(model) => !touches(BACKUP_WRITER_FILES, model, READ_OPS),
|
||||
(model) =>
|
||||
!touches(BACKUP_WRITER_FILES, model, READ_OPS, READ_RELATION_OPS),
|
||||
);
|
||||
expect(
|
||||
missing,
|
||||
@@ -261,7 +277,8 @@ describe("every backed-up model travels both ways, or is named as debt", () => {
|
||||
|
||||
it("finds a restore branch for every model claimed two-ended", () => {
|
||||
const missing = TWO_ENDED_MODELS.filter(
|
||||
(model) => !touches(BACKUP_RESTORE_FILES, model, WRITE_OPS),
|
||||
(model) =>
|
||||
!touches(BACKUP_RESTORE_FILES, model, WRITE_OPS, WRITE_RELATION_OPS),
|
||||
);
|
||||
expect(
|
||||
missing,
|
||||
|
||||
@@ -34,7 +34,6 @@ const LANGUAGE_NEUTRAL_UNITS = new Set([
|
||||
"%",
|
||||
"/10",
|
||||
"bpm",
|
||||
"breaths/min",
|
||||
"dBA",
|
||||
"kcal",
|
||||
"kg/m²",
|
||||
|
||||
@@ -22,8 +22,7 @@ export default function InsightsAtemfrequenzPage() {
|
||||
i18nPrefix="insights.respiratoryRate"
|
||||
explainerMetric="respiratoryRate"
|
||||
color="var(--info)"
|
||||
unit="breaths/min"
|
||||
yAxisUnit="breaths/min"
|
||||
unitKey="insights.units.respiratoryRate"
|
||||
emptyStateIcon={<Wind className="size-6" />}
|
||||
emptyStateCtaType={null}
|
||||
coachPrefill="I haven't logged any respiratory rate yet — what does this metric tell me about my health, and how do I improve it?"
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
* which is the failure mode the whole backup guard exists to make loud.
|
||||
*/
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
|
||||
// The slot-count module reaches for the global Prisma client at load; this
|
||||
// test only wants the constant off it.
|
||||
@@ -73,6 +75,13 @@ function tx(created: Array<Record<string, unknown>>) {
|
||||
}
|
||||
|
||||
describe("intraday profile backup", () => {
|
||||
it("keeps the backup source reviewable as text", () => {
|
||||
const source = readFileSync(
|
||||
resolve(process.cwd(), "src/lib/export/intraday-profile-backup.ts"),
|
||||
);
|
||||
expect(source.includes(0)).toBe(false);
|
||||
});
|
||||
|
||||
it("carries the same slot count the reader indexes by", () => {
|
||||
// Re-declared rather than imported, so it is pinned here instead: a change
|
||||
// to one without the other would put rows in the table that every read
|
||||
|
||||
Binary file not shown.
Reference in new issue
Block a user