mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 06:06:10 +00:00
Merge branch 'feat/module-surfaces' into release/v1.39.0
This commit is contained in:
commit
ab0d1b4b71
67 files changed
+1975
-703
No files matched your search
@@ -83,7 +83,7 @@ vi.mock("next/headers", () => ({
|
||||
import { GET } from "../route";
|
||||
import { getSession } from "@/lib/auth/session";
|
||||
import { prisma } from "@/lib/db";
|
||||
import { requireModuleEnabled } from "@/lib/modules/gate";
|
||||
import { requireModuleEnabled, resolveModuleMap } from "@/lib/modules/gate";
|
||||
import { apiError } from "@/lib/api-response";
|
||||
import { checkAnalyticsReadRateLimit } from "@/lib/rate-limit";
|
||||
import { __resetAllCachesForTests } from "@/lib/cache/server-cache";
|
||||
@@ -100,6 +100,10 @@ beforeEach(() => {
|
||||
// prior test would serve a hit and skip the builder every assertion spies on.
|
||||
__resetAllCachesForTests();
|
||||
vi.mocked(requireModuleEnabled).mockResolvedValue({ enabled: true });
|
||||
// Every module on unless a test says otherwise.
|
||||
vi.mocked(resolveModuleMap).mockResolvedValue(
|
||||
{} as Awaited<ReturnType<typeof resolveModuleMap>>,
|
||||
);
|
||||
// v1.15.20 — default to an allowing analytics-read budget.
|
||||
vi.mocked(checkAnalyticsReadRateLimit).mockResolvedValue({
|
||||
allowed: true,
|
||||
@@ -249,6 +253,29 @@ describe("GET /api/insights/correlations", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("skips the lab draws with the labs module off", async () => {
|
||||
vi.mocked(getSession).mockResolvedValue(SESSION_OK as never);
|
||||
vi.mocked(resolveModuleMap).mockResolvedValue({
|
||||
labs: false,
|
||||
} as Awaited<ReturnType<typeof resolveModuleMap>>);
|
||||
const res = await callGet(makeReq());
|
||||
expect(res.status).toBe(200);
|
||||
expect(prisma.labResult.findMany).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("recomputes rather than serve a cached body after a module was switched off", async () => {
|
||||
// The switched-off set is part of the cache key. Watched red with the
|
||||
// suffix removed from the key: the second call is a cache hit and the
|
||||
// measurement window is read once.
|
||||
vi.mocked(getSession).mockResolvedValue(SESSION_OK as never);
|
||||
await callGet(makeReq());
|
||||
vi.mocked(resolveModuleMap).mockResolvedValue({
|
||||
mood: false,
|
||||
} as Awaited<ReturnType<typeof resolveModuleMap>>);
|
||||
await callGet(makeReq());
|
||||
expect(prisma.measurement.findMany).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
// v1.18.0 (B2) — the route now also requires the `insights` module.
|
||||
it("returns 403 + module.disabled when the insights module is off", async () => {
|
||||
vi.mocked(getSession).mockResolvedValue(SESSION_OK as never);
|
||||
|
||||
@@ -23,7 +23,12 @@ import { apiError, apiSuccess } from "@/lib/api-response";
|
||||
import { cachedSwr, caches, type ServerCache } from "@/lib/cache/server-cache";
|
||||
import { annotate } from "@/lib/logging/context";
|
||||
import { checkAnalyticsReadRateLimit } from "@/lib/rate-limit";
|
||||
import { requireModuleEnabled } from "@/lib/modules/gate";
|
||||
import {
|
||||
MODULE_KEYS,
|
||||
requireModuleEnabled,
|
||||
resolveModuleMap,
|
||||
} from "@/lib/modules/gate";
|
||||
import { isSurfaceVisible, type SurfaceModuleMap } from "@/lib/modules/surface";
|
||||
import { resolveServerLocale } from "@/lib/i18n/server-locale";
|
||||
import { prisma } from "@/lib/db";
|
||||
import { wallClockInTz } from "@/lib/tz/wall-clock";
|
||||
@@ -89,12 +94,18 @@ export const GET = apiHandler(async () => {
|
||||
// `invalidateUserCorrelationPatterns`, which sweeps it too. The
|
||||
// `syncAcceptedPatterns` calls are DB writes and live INSIDE the builder,
|
||||
// so a cache hit performs no write on the read path.
|
||||
// Statistics serve only with their owning modules: a channel whose module
|
||||
// is off never enters the scan. The switched-off set is part of the cache
|
||||
// key, so toggling a module never serves a body computed under the other
|
||||
// setting.
|
||||
const modules = await resolveModuleMap(user.id);
|
||||
const modulesOff = MODULE_KEYS.filter((key) => modules[key] === false);
|
||||
const body = await cachedSwr(
|
||||
caches.analytics as ServerCache<
|
||||
Awaited<ReturnType<typeof buildCorrelationsResponse>>
|
||||
>,
|
||||
`${user.id}|correlations|${locale}`,
|
||||
() => buildCorrelationsResponse(user.id, locale),
|
||||
`${user.id}|correlations|${locale}|off:${modulesOff.join(",")}`,
|
||||
() => buildCorrelationsResponse(user.id, locale, modules),
|
||||
annotate,
|
||||
);
|
||||
|
||||
@@ -104,6 +115,7 @@ export const GET = apiHandler(async () => {
|
||||
async function buildCorrelationsResponse(
|
||||
userId: string,
|
||||
locale: Awaited<ReturnType<typeof resolveServerLocale>>,
|
||||
modules: SurfaceModuleMap,
|
||||
) {
|
||||
const profile = await prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
@@ -125,8 +137,15 @@ async function buildCorrelationsResponse(
|
||||
// v1.22 — lab draws (for the labs ↔ outcome pass) fetch alongside; they feed
|
||||
// a different pass over a different grain, so they are not matrix channels.
|
||||
const [matrix, labDraws] = await Promise.all([
|
||||
assembleDiscoveryMatrix(userId, { tz, since, fetchMode: "tiered" }),
|
||||
fetchLabDraws(userId, tz, since),
|
||||
assembleDiscoveryMatrix(userId, {
|
||||
tz,
|
||||
since,
|
||||
fetchMode: "tiered",
|
||||
modules,
|
||||
}),
|
||||
isSurfaceVisible("correlation:LAB_DRAWS", modules)
|
||||
? fetchLabDraws(userId, tz, since)
|
||||
: Promise.resolve([]),
|
||||
]);
|
||||
const { series, diagnostics } = matrix;
|
||||
|
||||
|
||||
Reference in new issue
Block a user