mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 22:26:13 +00:00
Merge remote-tracking branch 'origin/main' into fix/v1397-derive
This commit is contained in:
commit
da8cc7a6ee
74 files changed
+2304
-292
No files matched your search
@@ -34,7 +34,7 @@ import {
|
||||
GLUCOSE_PANEL_WINDOW_DAYS,
|
||||
type GlucoseClinicalMetrics,
|
||||
} from "@/lib/analytics/glucose-metrics";
|
||||
import { groupByGlucoseContext } from "@/lib/glucose";
|
||||
import { groupByGlucoseContext, resolveGlucoseUnit } from "@/lib/glucose";
|
||||
import {
|
||||
decisionForEvidence,
|
||||
PATTERN_FAMILIES,
|
||||
@@ -619,6 +619,7 @@ async function buildAnalyticsResponse(user: AuthedUser, locale: Locale) {
|
||||
timezone: userTz,
|
||||
sourcePriorityJson: scoreSourcePriority,
|
||||
thresholdsJson: user.thresholdsJson ?? null,
|
||||
glucoseUnit: resolveGlucoseUnit(user.glucoseUnit),
|
||||
},
|
||||
modules: {
|
||||
glucose: glucoseEnabled,
|
||||
|
||||
@@ -23,6 +23,10 @@ vi.mock("@/lib/db", () => ({
|
||||
|
||||
vi.mock("@/lib/auth/session", () => ({ getSession: vi.fn() }));
|
||||
|
||||
vi.mock("@/lib/insights/unit-change-refresh", () => ({
|
||||
refreshTextsAfterUnitChange: vi.fn().mockResolvedValue(undefined),
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/auth/audit", () => ({
|
||||
auditLog: vi.fn().mockResolvedValue(undefined),
|
||||
}));
|
||||
@@ -56,6 +60,7 @@ import { GET, PATCH } from "../route";
|
||||
import { prisma } from "@/lib/db";
|
||||
import { getSession } from "@/lib/auth/session";
|
||||
import { auditLog } from "@/lib/auth/audit";
|
||||
import { refreshTextsAfterUnitChange } from "@/lib/insights/unit-change-refresh";
|
||||
import { checkRateLimit } from "@/lib/rate-limit";
|
||||
|
||||
const SESSION_OK = {
|
||||
@@ -210,3 +215,33 @@ describe("PATCH /api/auth/me/glucose-unit", () => {
|
||||
expect(prisma.user.update).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("PATCH re-warms the texts that quote the unit", () => {
|
||||
it("re-warms the stored notes when the unit actually changes", async () => {
|
||||
vi.mocked(getSession).mockResolvedValue(SESSION_OK as never);
|
||||
vi.mocked(prisma.user.findUnique).mockResolvedValue({
|
||||
glucoseUnit: null,
|
||||
} as never);
|
||||
vi.mocked(prisma.user.update).mockResolvedValue({} as never);
|
||||
|
||||
const res = await (PATCH as (r: Request) => Promise<Response>)(
|
||||
mkPatch({ glucoseUnit: "mmol/L" }),
|
||||
);
|
||||
expect(res.status).toBe(200);
|
||||
expect(refreshTextsAfterUnitChange).toHaveBeenCalledWith("user-1");
|
||||
});
|
||||
|
||||
it("leaves them alone when the same unit is saved again", async () => {
|
||||
vi.mocked(getSession).mockResolvedValue(SESSION_OK as never);
|
||||
vi.mocked(prisma.user.findUnique).mockResolvedValue({
|
||||
glucoseUnit: "mmol/L",
|
||||
} as never);
|
||||
vi.mocked(prisma.user.update).mockResolvedValue({} as never);
|
||||
|
||||
const res = await (PATCH as (r: Request) => Promise<Response>)(
|
||||
mkPatch({ glucoseUnit: "mmol/L" }),
|
||||
);
|
||||
expect(res.status).toBe(200);
|
||||
expect(refreshTextsAfterUnitChange).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -38,6 +38,7 @@ import { prisma } from "@/lib/db";
|
||||
import { checkRateLimit, rateLimitHeaders } from "@/lib/rate-limit";
|
||||
import { resolveGlucoseUnit, type GlucoseUnit } from "@/lib/glucose";
|
||||
import { glucoseUnitPatchSchema } from "@/lib/validations/user-prefs";
|
||||
import { refreshTextsAfterUnitChange } from "@/lib/insights/unit-change-refresh";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
@@ -112,6 +113,12 @@ export const PATCH = apiHandler(async (req: Request) => {
|
||||
},
|
||||
});
|
||||
|
||||
// The status notes and narratives quote glucose in this unit; re-warm
|
||||
// them so today's stored text does not keep the old one.
|
||||
if (resolveGlucoseUnit(previous?.glucoseUnit) !== next) {
|
||||
void refreshTextsAfterUnitChange(user.id);
|
||||
}
|
||||
|
||||
annotate({
|
||||
action: { name: "auth.me.glucose-unit.patch" },
|
||||
meta: { glucoseUnit: next },
|
||||
|
||||
@@ -11,6 +11,10 @@ vi.mock("@/lib/db", () => ({
|
||||
|
||||
vi.mock("@/lib/auth/session", () => ({ getSession: vi.fn() }));
|
||||
|
||||
vi.mock("@/lib/insights/unit-change-refresh", () => ({
|
||||
refreshTextsAfterUnitChange: vi.fn().mockResolvedValue(undefined),
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/auth/audit", () => ({
|
||||
auditLog: vi.fn().mockResolvedValue(undefined),
|
||||
}));
|
||||
@@ -43,6 +47,7 @@ import { GET, PATCH } from "../route";
|
||||
import { prisma } from "@/lib/db";
|
||||
import { getSession } from "@/lib/auth/session";
|
||||
import { auditLog } from "@/lib/auth/audit";
|
||||
import { refreshTextsAfterUnitChange } from "@/lib/insights/unit-change-refresh";
|
||||
|
||||
const SESSION_OK = {
|
||||
session: { id: "sess-1", expiresAt: new Date(Date.now() + 3_600_000) },
|
||||
@@ -197,3 +202,33 @@ describe("PATCH /api/auth/me/unit-preference", () => {
|
||||
expect(prisma.user.update).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("PATCH re-warms the texts that quote the unit", () => {
|
||||
it("re-warms the stored notes when the unit actually changes", async () => {
|
||||
vi.mocked(getSession).mockResolvedValue(SESSION_OK as never);
|
||||
vi.mocked(prisma.user.findUnique).mockResolvedValue({
|
||||
unitPreference: null,
|
||||
} as never);
|
||||
vi.mocked(prisma.user.update).mockResolvedValue({} as never);
|
||||
|
||||
const res = await (PATCH as (r: Request) => Promise<Response>)(
|
||||
mkPatch({ unitPreference: "imperial" }),
|
||||
);
|
||||
expect(res.status).toBe(200);
|
||||
expect(refreshTextsAfterUnitChange).toHaveBeenCalledWith("user-1");
|
||||
});
|
||||
|
||||
it("leaves them alone when the same unit is saved again", async () => {
|
||||
vi.mocked(getSession).mockResolvedValue(SESSION_OK as never);
|
||||
vi.mocked(prisma.user.findUnique).mockResolvedValue({
|
||||
unitPreference: "imperial",
|
||||
} as never);
|
||||
vi.mocked(prisma.user.update).mockResolvedValue({} as never);
|
||||
|
||||
const res = await (PATCH as (r: Request) => Promise<Response>)(
|
||||
mkPatch({ unitPreference: "imperial" }),
|
||||
);
|
||||
expect(res.status).toBe(200);
|
||||
expect(refreshTextsAfterUnitChange).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -38,6 +38,7 @@ import { prisma } from "@/lib/db";
|
||||
import { checkRateLimit, rateLimitHeaders } from "@/lib/rate-limit";
|
||||
import { DEFAULT_UNIT_PREFERENCE } from "@/lib/measurements/display-transform";
|
||||
import { unitPreferencePatchSchema } from "@/lib/validations/user-prefs";
|
||||
import { refreshTextsAfterUnitChange } from "@/lib/insights/unit-change-refresh";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
@@ -118,6 +119,12 @@ export const PATCH = apiHandler(async (req: Request) => {
|
||||
},
|
||||
});
|
||||
|
||||
// The status notes and narratives quote mass, length and temperature in
|
||||
// this branch; re-warm them so today's stored text follows the switch.
|
||||
if (resolve(previous?.unitPreference) !== next) {
|
||||
void refreshTextsAfterUnitChange(user.id);
|
||||
}
|
||||
|
||||
annotate({
|
||||
action: { name: "auth.me.unit-preference.patch" },
|
||||
meta: { unitPreference: next },
|
||||
|
||||
@@ -41,6 +41,7 @@ import {
|
||||
parseNotificationPrefs,
|
||||
resolveNotificationPrefs,
|
||||
} from "@/lib/validations/notification-prefs";
|
||||
import { refreshTextsAfterUnitChange } from "@/lib/insights/unit-change-refresh";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
@@ -230,6 +231,9 @@ export const PATCH = apiHandler(
|
||||
},
|
||||
});
|
||||
invalidateUserProfile(access.recordId);
|
||||
if (patch.unitPreference !== undefined) {
|
||||
void refreshTextsAfterUnitChange(access.recordId);
|
||||
}
|
||||
if (patch.timezone !== undefined) {
|
||||
invalidateUserMedications(access.recordId, { evict: true });
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user