mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-11 22:02:16 +00:00
test(backup): bind MoodEntry to a named backup select
The mood read was an inline `include`, so nothing could compare it to the model the way `measurement-backup-completeness.test.ts` compares the measurement select. Name it — `MOOD_ENTRY_BACKUP_SELECT` and `MOOD_ENTRY_TAG_LINK_SELECT` — and add the sibling guard that binds both to `prisma/schema.prisma`. The mapper's parameter is spelled `moodEntry` rather than `e` because the guard proves a column reaches a payload by matching `: moodEntry.`, and `e` is also the intake-event mapper's parameter one section above: a matcher on `: e.` would match across both and could never fail. The guard is red as it stands, and names the defect: `note`, `noteEncrypted`, `tz`, `syncedAt`, `syncVersion`, `createdAt` and `updatedAt` are selected and written to no payload. The fix follows.
This commit is contained in:
1 parent
8a1585a391
commit
1bdc5ea4ee
3 files changed
+331
-19
No files matched your search
@@ -409,7 +409,13 @@ describe("buildFullBackupPayload disaster-recovery mode", () => {
|
||||
expect(prisma.moodEntry.findMany).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
where: { userId: "user-1" },
|
||||
include: expect.any(Object),
|
||||
select: expect.objectContaining({
|
||||
id: true,
|
||||
noteEncrypted: true,
|
||||
tz: true,
|
||||
syncVersion: true,
|
||||
deletedAt: true,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
expect(mocks.buildRecordsBackupSection).toHaveBeenCalledWith(
|
||||
|
||||
@@ -103,6 +103,46 @@ const MEASUREMENT_BACKUP_SELECT = {
|
||||
updatedAt: true,
|
||||
} as const satisfies Prisma.MeasurementSelect;
|
||||
|
||||
/**
|
||||
* Every restorable `MoodEntry` scalar. `userId` is absent for the same reason
|
||||
* it is absent above: the backup is user-scoped and the owner is re-derived
|
||||
* from the restoring account.
|
||||
*
|
||||
* Named rather than inlined so `mood-backup-completeness.test.ts` can compare
|
||||
* it field-by-field against the model in `prisma/schema.prisma`, exactly as the
|
||||
* measurement guard does for the select above.
|
||||
*/
|
||||
const MOOD_ENTRY_BACKUP_SELECT = {
|
||||
id: true,
|
||||
date: true,
|
||||
mood: true,
|
||||
score: true,
|
||||
tags: true,
|
||||
note: true,
|
||||
noteEncrypted: true,
|
||||
source: true,
|
||||
externalId: true,
|
||||
moodLoggedAt: true,
|
||||
tz: true,
|
||||
syncedAt: true,
|
||||
syncVersion: true,
|
||||
deletedAt: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
} as const satisfies Prisma.MoodEntrySelect;
|
||||
|
||||
/**
|
||||
* A structured-tag link, as it rides the wire. The link is carried by the
|
||||
* tag's KEY, not by its id: a restore resolves the key against the target
|
||||
* instance's catalogue, where the source instance's row id may not exist at
|
||||
* all. `kind` comes along so the mapper can split RATED factors from BINARY
|
||||
* tags without a second query.
|
||||
*/
|
||||
const MOOD_ENTRY_TAG_LINK_SELECT = {
|
||||
rating: true,
|
||||
moodTag: { select: { key: true, kind: true } },
|
||||
} as const satisfies Prisma.MoodEntryTagLinkSelect;
|
||||
|
||||
/**
|
||||
* Build the canonical full-backup payload for `userId`. Portable exports omit
|
||||
* tombstones and document ciphertext; disaster-recovery payloads preserve
|
||||
@@ -207,13 +247,11 @@ export async function buildFullBackupPayload(
|
||||
prisma.moodEntry.findMany({
|
||||
where: disasterRecovery ? { userId } : { userId, deletedAt: null },
|
||||
orderBy: { moodLoggedAt: "desc" },
|
||||
include: {
|
||||
select: {
|
||||
...MOOD_ENTRY_BACKUP_SELECT,
|
||||
tagLinks: {
|
||||
where: { rating: { not: null } },
|
||||
select: {
|
||||
rating: true,
|
||||
moodTag: { select: { key: true } },
|
||||
},
|
||||
select: MOOD_ENTRY_TAG_LINK_SELECT,
|
||||
},
|
||||
},
|
||||
}),
|
||||
@@ -394,19 +432,24 @@ export async function buildFullBackupPayload(
|
||||
skipped: e.skipped,
|
||||
source: e.source,
|
||||
})),
|
||||
moodEntries: moodEntries.map((e) => ({
|
||||
id: e.id,
|
||||
date: e.date,
|
||||
mood: e.mood,
|
||||
score: e.score,
|
||||
tags: e.tags,
|
||||
source: e.source,
|
||||
loggedAt: e.moodLoggedAt.toISOString(),
|
||||
externalId: e.externalId,
|
||||
deletedAt: e.deletedAt?.toISOString() ?? null,
|
||||
factors: e.tagLinks.map((link) => ({
|
||||
key: link.moodTag.key,
|
||||
rating: link.rating!,
|
||||
// The parameter is spelled out rather than the `e` every other mapper here
|
||||
// uses, and deliberately: `mood-backup-completeness.test.ts` proves a
|
||||
// selected column reaches a payload by matching `: moodEntry.`. A matcher
|
||||
// on `: e.` would also match the intake-event mapper above it and could
|
||||
// therefore never fail.
|
||||
moodEntries: moodEntries.map((moodEntry) => ({
|
||||
id: moodEntry.id,
|
||||
date: moodEntry.date,
|
||||
mood: moodEntry.mood,
|
||||
score: moodEntry.score,
|
||||
tags: moodEntry.tags,
|
||||
source: moodEntry.source,
|
||||
loggedAt: moodEntry.moodLoggedAt.toISOString(),
|
||||
externalId: moodEntry.externalId,
|
||||
deletedAt: moodEntry.deletedAt?.toISOString() ?? null,
|
||||
factors: moodEntry.tagLinks.map((tagLink) => ({
|
||||
key: tagLink.moodTag.key,
|
||||
rating: tagLink.rating!,
|
||||
})),
|
||||
})),
|
||||
customMoodTags: customMoodTags.map((tag) => ({
|
||||
|
||||
Reference in new issue
Block a user