mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 04:50:59 +00:00
Derive score-version fixtures from the SCORE_VERSION constant
Eight test and fixture files hardcoded scoreVersion: 2, a value the constant has not held since v1.35.0. Now they import SCORE_VERSION and use it, so a future bump cannot leave a fixture pinned to a stale number. The one genuinely historical case (record.test.ts, proving the input fingerprint moves when the algorithm version moves) now derives both its "previous" and "current" markers from SCORE_VERSION instead of two unrelated literals, so the pair keeps its meaning after the next bump.
This commit is contained in:
1 parent
7df25a6d92
commit
af35346687
8 files changed
+35
-19
No files matched your search
@@ -2,6 +2,8 @@ import { expect, test } from "./setup/test";
|
||||
|
||||
import { STORAGE_STATE_PATH } from "./setup/global-setup";
|
||||
import { settleBeforeMeasure } from "./utils/settle";
|
||||
import { SCORE_VERSION } from "@/lib/analytics/score/types";
|
||||
import { healthScoreAlgorithmItemKey } from "@/lib/daily/priority-item-key";
|
||||
|
||||
/**
|
||||
* Settings → Health Score: the surface that decides which pillars count.
|
||||
@@ -65,11 +67,14 @@ function scoreReport() {
|
||||
],
|
||||
delta: null,
|
||||
deltaReason: "no_current_score",
|
||||
scoreVersion: 2,
|
||||
scoreVersion: SCORE_VERSION,
|
||||
weightGoal: insufficient("no_goal"),
|
||||
// Raised and never dismissed, which is the state every account is in:
|
||||
// nothing has ever rendered this notice.
|
||||
algorithmNotice: { itemKey: "health_score_algorithm:2", dismissed: false },
|
||||
algorithmNotice: {
|
||||
itemKey: healthScoreAlgorithmItemKey(SCORE_VERSION),
|
||||
dismissed: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -223,7 +228,7 @@ test.describe("Settings → Health Score", () => {
|
||||
await notice.locator('[data-slot="score-change-notice-dismiss"]').click();
|
||||
|
||||
await expect(notice).toHaveCount(0);
|
||||
expect(dismissed).toBe("health_score_algorithm:2");
|
||||
expect(dismissed).toBe(healthScoreAlgorithmItemKey(SCORE_VERSION));
|
||||
});
|
||||
|
||||
test("refuses a selection too narrow to produce a score, in plain language", async ({
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
SECTION_MOBILITY,
|
||||
SECTION_VITALS,
|
||||
} from "@/components/insights/derived/use-dashboard-derived";
|
||||
import { SCORE_VERSION } from "@/lib/analytics/score/types";
|
||||
|
||||
/**
|
||||
* A POPULATED Insights overview fixture.
|
||||
@@ -170,7 +171,7 @@ const HEALTH_SCORE = {
|
||||
composition: ["BLOOD_PRESSURE", "ACTIVITY", "SLEEP", "ADIPOSITY"],
|
||||
configured: false,
|
||||
noiseFloor: 3,
|
||||
scoreVersion: 2,
|
||||
scoreVersion: SCORE_VERSION,
|
||||
},
|
||||
coverage: coverage(),
|
||||
confidence: confidence(),
|
||||
@@ -187,7 +188,7 @@ const HEALTH_SCORE = {
|
||||
],
|
||||
delta: 2,
|
||||
deltaReason: null,
|
||||
scoreVersion: 2,
|
||||
scoreVersion: SCORE_VERSION,
|
||||
weightGoal: {
|
||||
status: "ok" as const,
|
||||
value: {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { mkdir } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import type { Page, TestInfo } from "@playwright/test";
|
||||
import type { DailyDigest } from "@/lib/daily/digest";
|
||||
import { SCORE_VERSION } from "@/lib/analytics/score/types";
|
||||
|
||||
import { expect, test } from "./setup/test";
|
||||
import { STORAGE_STATE_PATH } from "./setup/global-setup";
|
||||
@@ -23,7 +24,7 @@ const NARRATIVE_DIGEST: DailyDigest = {
|
||||
band: "green",
|
||||
delta: -2,
|
||||
deltaReason: null,
|
||||
scoreVersion: 2,
|
||||
scoreVersion: SCORE_VERSION,
|
||||
composition: ["BLOOD_PRESSURE", "ACTIVITY", "SLEEP"],
|
||||
},
|
||||
topSignal: LONG_HEADLINE_BRIEFING.signalsOfDay?.[0] ?? null,
|
||||
|
||||
@@ -22,6 +22,8 @@ import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { NextRequest } from "next/server";
|
||||
import { Buffer } from "node:buffer";
|
||||
|
||||
import { SCORE_VERSION } from "@/lib/analytics/score/types";
|
||||
|
||||
process.env.ENCRYPTION_KEY =
|
||||
"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
|
||||
|
||||
@@ -269,7 +271,7 @@ function sourceClient() {
|
||||
timezone: "Europe/Berlin",
|
||||
composite: 76,
|
||||
band: "yellow",
|
||||
scoreVersion: 2,
|
||||
scoreVersion: SCORE_VERSION,
|
||||
composition: ["BLOOD_PRESSURE", "SLEEP", "ADIPOSITY"],
|
||||
pillarScores: { BLOOD_PRESSURE: 86, SLEEP: 100, ADIPOSITY: 42 },
|
||||
inputFingerprint: "a".repeat(64),
|
||||
@@ -629,7 +631,7 @@ describe("backup round trip — export, wire schema, restore", () => {
|
||||
timezone: "Europe/Berlin",
|
||||
composite: 76,
|
||||
band: "yellow",
|
||||
scoreVersion: 2,
|
||||
scoreVersion: SCORE_VERSION,
|
||||
inputFingerprint: "a".repeat(64),
|
||||
});
|
||||
// The composition and the per-pillar scores are what keep the stored day
|
||||
|
||||
@@ -3,6 +3,7 @@ import { NextRequest } from "next/server";
|
||||
|
||||
import { apiError } from "@/lib/api-response";
|
||||
import type { DailyDigest } from "@/lib/daily/digest";
|
||||
import { SCORE_VERSION } from "@/lib/analytics/score/types";
|
||||
|
||||
/**
|
||||
* `GET /api/daily/digest` — the P3 read seam.
|
||||
@@ -63,7 +64,7 @@ const DIGEST: DailyDigest = {
|
||||
band: "good",
|
||||
delta: 3,
|
||||
deltaReason: null,
|
||||
scoreVersion: 2,
|
||||
scoreVersion: SCORE_VERSION,
|
||||
composition: ["BLOOD_PRESSURE", "ACTIVITY", "SLEEP"],
|
||||
},
|
||||
topSignal: null,
|
||||
|
||||
@@ -6,6 +6,7 @@ import { I18nProvider } from "@/lib/i18n/context";
|
||||
import { TodayHero } from "../today-hero";
|
||||
import type { DailyDigest } from "@/lib/daily/digest";
|
||||
import type { PriorityItem } from "@/lib/daily/priority-item";
|
||||
import { SCORE_VERSION } from "@/lib/analytics/score/types";
|
||||
|
||||
// The hero now wires the coach check-in card's keep / let-go taps through
|
||||
// `useCoachCheckinAction`, so it needs a QueryClient in the tree.
|
||||
@@ -57,7 +58,7 @@ function digest(over: Partial<DailyDigest> = {}): DailyDigest {
|
||||
band: "green",
|
||||
delta: 3,
|
||||
deltaReason: null,
|
||||
scoreVersion: 2,
|
||||
scoreVersion: SCORE_VERSION,
|
||||
composition: ["BLOOD_PRESSURE", "ACTIVITY", "SLEEP"],
|
||||
},
|
||||
topSignal: {
|
||||
@@ -114,7 +115,7 @@ describe("<TodayHero>", () => {
|
||||
band: "green",
|
||||
delta: 20,
|
||||
deltaReason: "algorithm_changed",
|
||||
scoreVersion: 2,
|
||||
scoreVersion: SCORE_VERSION,
|
||||
composition: ["BLOOD_PRESSURE", "ACTIVITY", "SLEEP"],
|
||||
},
|
||||
})}
|
||||
|
||||
@@ -144,8 +144,12 @@ describe("stored bands", () => {
|
||||
});
|
||||
|
||||
describe("input fingerprint", () => {
|
||||
// The fixture only needs two DIFFERENT version markers to prove the
|
||||
// fingerprint is sensitive to a version move — pinned relative to
|
||||
// SCORE_VERSION so the pair stays "previous vs current" rather than
|
||||
// going stale (or colliding) the next time the constant bumps.
|
||||
const base = {
|
||||
scoreVersion: 1,
|
||||
scoreVersion: SCORE_VERSION - 1,
|
||||
composition: ["BLOOD_PRESSURE", "SLEEP", "ADIPOSITY"] as ScorePillarId[],
|
||||
pillars: HEALTHY,
|
||||
};
|
||||
@@ -157,9 +161,9 @@ describe("input fingerprint", () => {
|
||||
});
|
||||
|
||||
it("moves when the algorithm version moves", () => {
|
||||
expect(healthScoreInputFingerprint({ ...base, scoreVersion: 2 })).not.toBe(
|
||||
healthScoreInputFingerprint(base),
|
||||
);
|
||||
expect(
|
||||
healthScoreInputFingerprint({ ...base, scoreVersion: SCORE_VERSION }),
|
||||
).not.toBe(healthScoreInputFingerprint(base));
|
||||
});
|
||||
|
||||
it("moves when the composition narrows", () => {
|
||||
|
||||
@@ -77,7 +77,7 @@ vi.mock("@/lib/analytics/score/reader", () => ({
|
||||
bandSetter: "BLOOD_PRESSURE" as const,
|
||||
composition: ["BLOOD_PRESSURE", "ACTIVITY", "SLEEP"] as const,
|
||||
noiseFloor: 2,
|
||||
scoreVersion: 2 as const,
|
||||
scoreVersion: SCORE_VERSION,
|
||||
},
|
||||
coverage,
|
||||
confidence: { score: 100, band: "high" as const },
|
||||
@@ -94,7 +94,7 @@ vi.mock("@/lib/analytics/score/reader", () => ({
|
||||
pillars: [],
|
||||
delta: legacy?.delta ?? null,
|
||||
deltaReason: null,
|
||||
scoreVersion: 2 as const,
|
||||
scoreVersion: SCORE_VERSION,
|
||||
weightGoal: {
|
||||
status: "insufficient" as const,
|
||||
coverage,
|
||||
@@ -154,6 +154,7 @@ import {
|
||||
} from "../snapshot";
|
||||
import type { ModuleKey } from "@/lib/modules/gate";
|
||||
import { MODULE_KEYS } from "@/lib/modules/registry";
|
||||
import { SCORE_VERSION } from "@/lib/analytics/score/types";
|
||||
|
||||
const emptySummary = {
|
||||
count: 0,
|
||||
@@ -370,7 +371,7 @@ describe("buildDashboardSnapshot — per-type thick-phase gate", () => {
|
||||
confidence: { score: 100, band: "high" },
|
||||
composition: ["BLOOD_PRESSURE", "ACTIVITY", "SLEEP"],
|
||||
deltaReason: null,
|
||||
scoreVersion: 2,
|
||||
scoreVersion: SCORE_VERSION,
|
||||
bandSetter: "BLOOD_PRESSURE",
|
||||
// v1.21.2 (A5 / A6) — additive narrative fields, null absent a
|
||||
// disagreeing readiness set / a returned metric (the fake derived
|
||||
@@ -516,7 +517,7 @@ describe("buildDashboardSnapshot — healthScore (warm phase only)", () => {
|
||||
confidence: { score: 100, band: "high" },
|
||||
composition: ["BLOOD_PRESSURE", "ACTIVITY", "SLEEP"],
|
||||
deltaReason: null,
|
||||
scoreVersion: 2,
|
||||
scoreVersion: SCORE_VERSION,
|
||||
bandSetter: "BLOOD_PRESSURE",
|
||||
// v1.21.2 (A5 / A6) — additive narrative fields, null here.
|
||||
tension: null,
|
||||
|
||||
Reference in new issue
Block a user