style: apply prettier across the tree

One-shot prettier pass so the new format:check gate starts from a clean
baseline. Formatting only — no behaviour change. Generator-owned and
vendored artifacts stay untouched via .prettierignore.
This commit is contained in:
Marc-André Bombeck committed 2026-06-19 22:20:31 +02:00
1 parent b3fea2ef59
commit 1f4ba5939d
1000 files changed
+12130 -8385

No files matched your search

@@ -57,14 +57,17 @@ import { apiError } from "@/lib/api-response";
const SESSION_OK = {
session: { id: "sess-1", expiresAt: new Date(Date.now() + 3_600_000) },
user: { id: "user-1", username: "testuser", role: "USER" as const, locale: "en" },
user: {
id: "user-1",
username: "testuser",
role: "USER" as const,
locale: "en",
},
};
const callGet = GET as unknown as (req: NextRequest) => Promise<Response>;
function makeReq(): NextRequest {
return new NextRequest(
"http://localhost/api/insights/blood-pressure-status",
);
return new NextRequest("http://localhost/api/insights/blood-pressure-status");
}
beforeEach(() => {
@@ -70,7 +70,10 @@ vi.mock("@/lib/ai/coach/persistence", () => ({
appendMessage: vi.fn(),
createConversation: vi.fn(),
fetchConversationWithMessages: vi.fn(),
listConversations: vi.fn(async () => ({ conversations: [], nextCursor: null })),
listConversations: vi.fn(async () => ({
conversations: [],
nextCursor: null,
})),
}));
vi.mock("@/lib/ai/coach/coach-memory-shared", () => ({
enqueueCoachMemoryRefresh: vi.fn(),
@@ -82,12 +85,16 @@ vi.mock("@/lib/ai/coach/budget", () => ({
recordSpend: vi.fn(),
}));
vi.mock("@/lib/ai/coach/refusal", () => ({ detectRefusal: vi.fn() }));
vi.mock("@/lib/ai/coach/system-prompt", () => ({ getCoachSystemPrompt: vi.fn() }));
vi.mock("@/lib/ai/coach/system-prompt", () => ({
getCoachSystemPrompt: vi.fn(),
}));
vi.mock("@/lib/ai/coach/about-me", () => ({
getSelfContextTextForUser: vi.fn(),
}));
vi.mock("@/lib/ai/coach/snapshot", () => ({ buildCoachSnapshot: vi.fn() }));
vi.mock("@/lib/ai/coach/keyvalues", () => ({ parseKeyValuesSentinel: vi.fn() }));
vi.mock("@/lib/ai/coach/keyvalues", () => ({
parseKeyValuesSentinel: vi.fn(),
}));
vi.mock("@/lib/validations/coach-prefs", () => ({ parseCoachPrefs: vi.fn() }));
vi.mock("@/lib/sse/create-stream", () => ({ createSseStream: vi.fn() }));
+3 -4
View File
@@ -571,7 +571,8 @@ Reply now as the assistant, in ${locale === "de" ? "German" : "English"}.`;
prisma,
userId,
cadence: suggestParse.cadence,
prefs: coachPrefs.reminderSuggestions ?? DEFAULT_REMINDER_SUGGESTION_PREFS,
prefs:
coachPrefs.reminderSuggestions ?? DEFAULT_REMINDER_SUGGESTION_PREFS,
});
if (decision.surface) {
const cadence = suggestParse.cadence;
@@ -613,9 +614,7 @@ Reply now as the assistant, in ${locale === "de" ? "German" : "English"}.`;
}
const enrichedProvenance: typeof snapshot.provenance = {
...snapshot.provenance,
...(sentinel.keyValues.length > 0
? { keyValues: sentinel.keyValues }
: {}),
...(sentinel.keyValues.length > 0 ? { keyValues: sentinel.keyValues } : {}),
...(surfacedSuggestion ? { suggestion: surfacedSuggestion } : {}),
};
if (sentinel.malformed) {
@@ -58,8 +58,7 @@ vi.mock("@/lib/db-compat", () => ({
}));
vi.mock("@/lib/logging/context", async (importOriginal) => {
const actual =
await importOriginal<typeof import("@/lib/logging/context")>();
const actual = await importOriginal<typeof import("@/lib/logging/context")>();
return {
...actual,
annotate: vi.fn(),
@@ -167,10 +166,7 @@ describe("GET /api/insights/coach/facts", () => {
await callGet();
const orderBy = vi.mocked(prisma.coachFact.findMany).mock.calls[0]?.[0]
?.orderBy;
expect(orderBy).toEqual([
{ confidence: "desc" },
{ createdAt: "desc" },
]);
expect(orderBy).toEqual([{ confidence: "desc" }, { createdAt: "desc" }]);
});
it("skips an undecryptable row rather than 500ing the whole list", async () => {
@@ -35,12 +35,14 @@ vi.mock("@/lib/db", () => ({
// real recompute against the mocked Prisma. The route fires the
// warm-up as fire-and-forget; the return value is irrelevant.
vi.mock("@/lib/rollups/mood-rollups", async () => {
const actual = await vi.importActual<typeof import("@/lib/rollups/mood-rollups")>(
"@/lib/rollups/mood-rollups",
);
const actual = await vi.importActual<
typeof import("@/lib/rollups/mood-rollups")
>("@/lib/rollups/mood-rollups");
return {
...actual,
ensureUserMoodRollupsFresh: vi.fn().mockResolvedValue({ recomputed: false }),
ensureUserMoodRollupsFresh: vi
.fn()
.mockResolvedValue({ recomputed: false }),
};
});
@@ -118,9 +120,9 @@ beforeEach(() => {
resetAt: Date.now() + 60_000,
});
// Default to assistant-on so the gate doesn't 403 every test.
(
prisma.appSettings.findUnique as ReturnType<typeof vi.fn>
).mockResolvedValue(null);
(prisma.appSettings.findUnique as ReturnType<typeof vi.fn>).mockResolvedValue(
null,
);
(prisma.user.findUnique as ReturnType<typeof vi.fn>).mockResolvedValue({
heightCm: 180,
dateOfBirth: new Date("1985-01-01"),
@@ -146,15 +148,15 @@ describe("GET /api/insights/comprehensive — envelope shape", () => {
it("emits every legacy envelope key for an empty user", async () => {
vi.mocked(getSession).mockResolvedValue(SESSION_OK as never);
(
buildComprehensiveAggregate as ReturnType<typeof vi.fn>
).mockResolvedValue({
summaries: {},
bpRawRows: { sys: [], dia: [] },
dailyByType: {},
firstMeasurementAt: null,
totalMeasurements: 0,
});
(buildComprehensiveAggregate as ReturnType<typeof vi.fn>).mockResolvedValue(
{
summaries: {},
bpRawRows: { sys: [], dia: [] },
dailyByType: {},
firstMeasurementAt: null,
totalMeasurements: 0,
},
);
const res = await callGet(makeReq());
expect(res.status).toBe(200);
@@ -202,31 +204,31 @@ describe("GET /api/insights/comprehensive — envelope shape", () => {
it("computes BMI from aggregate WEIGHT.latest and user heightCm", async () => {
vi.mocked(getSession).mockResolvedValue(SESSION_OK as never);
(
buildComprehensiveAggregate as ReturnType<typeof vi.fn>
).mockResolvedValue({
summaries: {
WEIGHT: {
count: 1,
latest: 81.0,
min: 81.0,
max: 81.0,
mean: 81.0,
avg7: 81.0,
avg30: 81.0,
slope7: null,
slope30: null,
slope90: null,
anomalyCount: 0,
avg30LastMonth: null,
avg30LastYear: null,
(buildComprehensiveAggregate as ReturnType<typeof vi.fn>).mockResolvedValue(
{
summaries: {
WEIGHT: {
count: 1,
latest: 81.0,
min: 81.0,
max: 81.0,
mean: 81.0,
avg7: 81.0,
avg30: 81.0,
slope7: null,
slope30: null,
slope90: null,
anomalyCount: 0,
avg30LastMonth: null,
avg30LastYear: null,
},
},
bpRawRows: { sys: [], dia: [] },
dailyByType: {},
firstMeasurementAt: new Date(),
totalMeasurements: 1,
},
bpRawRows: { sys: [], dia: [] },
dailyByType: {},
firstMeasurementAt: new Date(),
totalMeasurements: 1,
});
);
const res = await callGet(makeReq());
const body = (await res.json()) as {
@@ -240,49 +242,49 @@ describe("GET /api/insights/comprehensive — envelope shape", () => {
it("pairs sys + dia raw rows with 5-min tolerance for bpPctInTarget", async () => {
vi.mocked(getSession).mockResolvedValue(SESSION_OK as never);
const at = new Date("2026-05-10T08:00:00Z");
(
buildComprehensiveAggregate as ReturnType<typeof vi.fn>
).mockResolvedValue({
summaries: {
BLOOD_PRESSURE_SYS: {
count: 1,
latest: 125,
min: 125,
max: 125,
mean: 125,
avg7: 125,
avg30: 125,
slope7: null,
slope30: null,
slope90: null,
anomalyCount: 0,
avg30LastMonth: null,
avg30LastYear: null,
(buildComprehensiveAggregate as ReturnType<typeof vi.fn>).mockResolvedValue(
{
summaries: {
BLOOD_PRESSURE_SYS: {
count: 1,
latest: 125,
min: 125,
max: 125,
mean: 125,
avg7: 125,
avg30: 125,
slope7: null,
slope30: null,
slope90: null,
anomalyCount: 0,
avg30LastMonth: null,
avg30LastYear: null,
},
BLOOD_PRESSURE_DIA: {
count: 1,
latest: 75,
min: 75,
max: 75,
mean: 75,
avg7: 75,
avg30: 75,
slope7: null,
slope30: null,
slope90: null,
anomalyCount: 0,
avg30LastMonth: null,
avg30LastYear: null,
},
},
BLOOD_PRESSURE_DIA: {
count: 1,
latest: 75,
min: 75,
max: 75,
mean: 75,
avg7: 75,
avg30: 75,
slope7: null,
slope30: null,
slope90: null,
anomalyCount: 0,
avg30LastMonth: null,
avg30LastYear: null,
bpRawRows: {
sys: [{ measuredAt: at, value: 125 }],
dia: [{ measuredAt: at, value: 75 }],
},
dailyByType: {},
firstMeasurementAt: at,
totalMeasurements: 2,
},
bpRawRows: {
sys: [{ measuredAt: at, value: 125 }],
dia: [{ measuredAt: at, value: 75 }],
},
dailyByType: {},
firstMeasurementAt: at,
totalMeasurements: 2,
});
);
const res = await callGet(makeReq());
const body = (await res.json()) as {
@@ -305,15 +307,15 @@ describe("GET /api/insights/comprehensive — envelope shape", () => {
// mean DataPoints, matching the rollup-tier convention shipped by
// `/api/mood/analytics` post-v1.4.39.
vi.mocked(getSession).mockResolvedValue(SESSION_OK as never);
(
buildComprehensiveAggregate as ReturnType<typeof vi.fn>
).mockResolvedValue({
summaries: {},
bpRawRows: { sys: [], dia: [] },
dailyByType: {},
firstMeasurementAt: new Date(),
totalMeasurements: 0,
});
(buildComprehensiveAggregate as ReturnType<typeof vi.fn>).mockResolvedValue(
{
summaries: {},
bpRawRows: { sys: [], dia: [] },
dailyByType: {},
firstMeasurementAt: new Date(),
totalMeasurements: 0,
},
);
(
prisma.moodEntryRollup.findMany as ReturnType<typeof vi.fn>
).mockResolvedValue([
@@ -364,15 +366,15 @@ describe("GET /api/insights/comprehensive — envelope shape", () => {
// supposed to fire fire-and-forget so the next request lands on
// the rollup tier.
vi.mocked(getSession).mockResolvedValue(SESSION_OK as never);
(
buildComprehensiveAggregate as ReturnType<typeof vi.fn>
).mockResolvedValue({
summaries: {},
bpRawRows: { sys: [], dia: [] },
dailyByType: {},
firstMeasurementAt: new Date(),
totalMeasurements: 0,
});
(buildComprehensiveAggregate as ReturnType<typeof vi.fn>).mockResolvedValue(
{
summaries: {},
bpRawRows: { sys: [], dia: [] },
dailyByType: {},
firstMeasurementAt: new Date(),
totalMeasurements: 0,
},
);
(
prisma.moodEntryRollup.findMany as ReturnType<typeof vi.fn>
).mockResolvedValue([]);
@@ -411,18 +413,21 @@ describe("GET /api/insights/comprehensive — envelope shape", () => {
"2026-05-04",
"2026-05-05",
];
(
buildComprehensiveAggregate as ReturnType<typeof vi.fn>
).mockResolvedValue({
summaries: {},
bpRawRows: { sys: [], dia: [] },
dailyByType: {
WEIGHT: days.map((d, i) => ({ day: d, value: 80 + i })),
BLOOD_PRESSURE_SYS: days.map((d, i) => ({ day: d, value: 120 + i * 2 })),
(buildComprehensiveAggregate as ReturnType<typeof vi.fn>).mockResolvedValue(
{
summaries: {},
bpRawRows: { sys: [], dia: [] },
dailyByType: {
WEIGHT: days.map((d, i) => ({ day: d, value: 80 + i })),
BLOOD_PRESSURE_SYS: days.map((d, i) => ({
day: d,
value: 120 + i * 2,
})),
},
firstMeasurementAt: new Date(),
totalMeasurements: 10,
},
firstMeasurementAt: new Date(),
totalMeasurements: 10,
});
);
const res = await callGet(makeReq());
const body = (await res.json()) as {
@@ -454,9 +459,9 @@ describe("GET /api/insights/comprehensive — stale-while-revalidate marker", ()
it("marks a cold (inline-computed) read revalidating: false", async () => {
vi.mocked(getSession).mockResolvedValue(SESSION_OK as never);
(
buildComprehensiveAggregate as ReturnType<typeof vi.fn>
).mockResolvedValue(EMPTY_AGGREGATE);
(buildComprehensiveAggregate as ReturnType<typeof vi.fn>).mockResolvedValue(
EMPTY_AGGREGATE,
);
const res = await callGet(makeReq());
expect(res.status).toBe(200);
@@ -466,9 +471,9 @@ describe("GET /api/insights/comprehensive — stale-while-revalidate marker", ()
it("marks a stale-served read revalidating: true and a converged read false again", async () => {
vi.mocked(getSession).mockResolvedValue(SESSION_OK as never);
(
buildComprehensiveAggregate as ReturnType<typeof vi.fn>
).mockResolvedValue(EMPTY_AGGREGATE);
(buildComprehensiveAggregate as ReturnType<typeof vi.fn>).mockResolvedValue(
EMPTY_AGGREGATE,
);
// Prime the cache entry (cold compute).
await callGet(makeReq());
+4 -7
View File
@@ -74,9 +74,9 @@ export const GET = apiHandler(async () => {
// the response, never inside the cached body, so a warmed entry
// doesn't freeze the flag.
const { value: body, outcome } = await cachedSwrWithMeta(
caches.analytics as ServerCache<Awaited<
ReturnType<typeof buildComprehensiveResponse>
>>,
caches.analytics as ServerCache<
Awaited<ReturnType<typeof buildComprehensiveResponse>>
>,
`${user.id}|comprehensive`,
() => buildComprehensiveResponse(user),
annotate,
@@ -241,10 +241,7 @@ export async function buildComprehensiveResponse(user: AuthedUser) {
// small semantic shift to bound the raw-row footprint).
const weightDaily = aggregate.dailyByType.WEIGHT ?? [];
const sysDaily = aggregate.dailyByType.BLOOD_PRESSURE_SYS ?? [];
const weightBpPairs: PairedPoint[] = joinDailyByDay(
weightDaily,
sysDaily,
);
const weightBpPairs: PairedPoint[] = joinDailyByDay(weightDaily, sysDaily);
const weightBpCorrelation = pearsonCorrelation(weightBpPairs);
const scatterData = weightBpPairs.map((p) => ({
weight: p.a,
@@ -5,7 +5,9 @@ vi.mock("@/lib/db", () => ({
prisma: {
auditLog: { create: vi.fn() },
appSettings: { findUnique: vi.fn().mockResolvedValue(null) },
user: { findUnique: vi.fn().mockResolvedValue({ timezone: "Europe/Berlin" }) },
user: {
findUnique: vi.fn().mockResolvedValue({ timezone: "Europe/Berlin" }),
},
measurement: { findMany: vi.fn().mockResolvedValue([]) },
moodEntry: { findMany: vi.fn().mockResolvedValue([]) },
},
+10 -3
View File
@@ -117,7 +117,10 @@ export const GET = apiHandler(async () => {
}),
]);
const measurementsByType = new Map<string, Array<{ value: number; at: Date }>>();
const measurementsByType = new Map<
string,
Array<{ value: number; at: Date }>
>();
for (const m of measurements) {
const list = measurementsByType.get(m.type) ?? [];
list.push({ value: m.value, at: m.measuredAt });
@@ -134,12 +137,16 @@ export const GET = apiHandler(async () => {
const series: NamedSeries[] = [];
for (const key of DISCOVERY_BEHAVIOURS) {
const points =
key === "MOOD" ? moodDaily : toDailyMeans(measurementsByType.get(key) ?? [], tz);
key === "MOOD"
? moodDaily
: toDailyMeans(measurementsByType.get(key) ?? [], tz);
series.push({ key, role: "behaviour", points });
}
for (const key of DISCOVERY_OUTCOMES) {
const points =
key === "MOOD" ? moodDaily : toDailyMeans(measurementsByType.get(key) ?? [], tz);
key === "MOOD"
? moodDaily
: toDailyMeans(measurementsByType.get(key) ?? [], tz);
series.push({ key, role: "outcome", points });
}
@@ -59,10 +59,28 @@ vi.mock("@/lib/insights/derived", async (importOriginal) => {
...actual,
computeDerivedMetric: vi.fn(async () => ({
status: "ok",
value: { type: "RESTING_HEART_RATE", center: 55, low: 48, high: 62, spread: 7, sampleDays: 30, k: 3 },
coverage: { requiredInputs: 1, presentInputs: 1, historyDays: 30, missing: [] },
value: {
type: "RESTING_HEART_RATE",
center: 55,
low: 48,
high: 62,
spread: 7,
sampleDays: 30,
k: 3,
},
coverage: {
requiredInputs: 1,
presentInputs: 1,
historyDays: 30,
missing: [],
},
confidence: { score: 100, band: "high" },
provenance: { inputs: ["RESTING_HEART_RATE"], source: "DAY", windowDays: 30, computedAt: "2026-06-02T07:00:00+02:00" },
provenance: {
inputs: ["RESTING_HEART_RATE"],
source: "DAY",
windowDays: 30,
computedAt: "2026-06-02T07:00:00+02:00",
},
})),
};
});
@@ -74,7 +92,12 @@ import { computeDerivedMetric } from "@/lib/insights/derived";
const SESSION_OK = {
session: { id: "sess-1", expiresAt: new Date(Date.now() + 3_600_000) },
user: { id: "user-1", username: "testuser", role: "USER" as const, locale: "en" },
user: {
id: "user-1",
username: "testuser",
role: "USER" as const,
locale: "en",
},
};
const callGet = GET as unknown as (req: NextRequest) => Promise<Response>;
@@ -152,14 +175,29 @@ describe("GET /api/insights/derived", () => {
vi.mocked(getSession).mockResolvedValue(SESSION_OK as never);
vi.mocked(computeDerivedMetric).mockResolvedValueOnce({
status: "insufficient",
coverage: { requiredInputs: 1, presentInputs: 0, historyDays: 0, missing: ["RESTING_HEART_RATE"] },
provenance: { inputs: ["RESTING_HEART_RATE"], source: "none", windowDays: 30, computedAt: "2026-06-02T07:00:00+02:00" },
coverage: {
requiredInputs: 1,
presentInputs: 0,
historyDays: 0,
missing: ["RESTING_HEART_RATE"],
},
provenance: {
inputs: ["RESTING_HEART_RATE"],
source: "none",
windowDays: 30,
computedAt: "2026-06-02T07:00:00+02:00",
},
reason: "no_readings_in_window",
} as never);
const res = await callGet(makeReq("VITALS_BASELINE"));
expect(res.status).toBe(200);
const body = (await res.json()) as {
data: { status: string; value: unknown; confidence: unknown; reason: string } | null;
data: {
status: string;
value: unknown;
confidence: unknown;
reason: string;
} | null;
};
expect(body.data?.status).toBe("insufficient");
expect(body.data?.value).toBeNull();
+5 -1
View File
@@ -103,7 +103,11 @@ export const GET = apiHandler(async (request: NextRequest) => {
ninetyDaysAgo.setDate(ninetyDaysAgo.getDate() - 90);
const moods = await prisma.moodEntry.findMany({
// v1.7.0 sync — exclude tombstoned rows.
where: { userId: user.id, deletedAt: null, moodLoggedAt: { gte: ninetyDaysAgo } },
where: {
userId: user.id,
deletedAt: null,
moodLoggedAt: { gte: ninetyDaysAgo },
},
select: { moodLoggedAt: true, tags: true },
orderBy: { moodLoggedAt: "desc" },
});
@@ -36,8 +36,7 @@ vi.mock("@/lib/db-compat", () => ({
}));
vi.mock("@/lib/logging/context", async (importOriginal) => {
const actual =
await importOriginal<typeof import("@/lib/logging/context")>();
const actual = await importOriginal<typeof import("@/lib/logging/context")>();
return {
...actual,
annotate: vi.fn(),
@@ -240,7 +239,8 @@ describe("PUT /api/insights/layout — happy path", () => {
expect(returnedIds).not.toContain("medikamente");
// The persisted blob is canonical too.
const updateCall = vi.mocked(prisma.user.update).mock.calls[0]?.[0] as unknown as {
const updateCall = vi.mocked(prisma.user.update).mock
.calls[0]?.[0] as unknown as {
data: { insightsLayoutJson: { tiles: Array<{ id: string }> } };
};
const persistedIds = updateCall.data.insightsLayoutJson.tiles.map(
@@ -291,9 +291,9 @@ describe("PUT /api/insights/layout — accepts version 1 (iOS contract)", () =>
data: { insightsLayoutJson: { version: number; sections: unknown[] } };
};
expect(updateCall.data.insightsLayoutJson.version).toBe(2);
expect(
Array.isArray(updateCall.data.insightsLayoutJson.sections),
).toBe(true);
expect(Array.isArray(updateCall.data.insightsLayoutJson.sections)).toBe(
true,
);
});
});
@@ -396,9 +396,9 @@ describe("PUT /api/insights/layout — full metric-slug universe", () => {
const body = (await res.json()) as {
details: { issues: Array<{ path: string }> };
};
expect(
body.details.issues.some((i) => i.path.startsWith("tiles")),
).toBe(true);
expect(body.details.issues.some((i) => i.path.startsWith("tiles"))).toBe(
true,
);
});
});
@@ -436,9 +436,9 @@ describe("PUT /api/insights/layout — 422 multi-issue envelope", () => {
const body = (await res.json()) as {
details: { issues: Array<{ path: string }> };
};
expect(
body.details.issues.some((i) => i.path.startsWith("tiles")),
).toBe(true);
expect(body.details.issues.some((i) => i.path.startsWith("tiles"))).toBe(
true,
);
});
it("writes one audit-ledger row keyed insights.layout.validation-failed", async () => {
@@ -488,11 +488,13 @@ describe("PUT /api/insights/layout — 422 multi-issue envelope", () => {
const res = await callPut(makeReq(payload));
expect(res.status).toBe(422);
const annotated = vi.mocked(annotate).mock.calls.find(
(call) =>
(call[0] as { action?: { name?: string } })?.action?.name ===
"insights.layout.validation-failed",
);
const annotated = vi
.mocked(annotate)
.mock.calls.find(
(call) =>
(call[0] as { action?: { name?: string } })?.action?.name ===
"insights.layout.validation-failed",
);
expect(annotated, "validation-failed annotate call").toBeTruthy();
const meta = (annotated![0] as { meta?: Record<string, unknown> }).meta!;
@@ -524,11 +526,13 @@ describe("PUT /api/insights/layout — 422 multi-issue envelope", () => {
const res = await callPut(makeReq(payload));
expect(res.status).toBe(422);
const annotated = vi.mocked(annotate).mock.calls.find(
(call) =>
(call[0] as { action?: { name?: string } })?.action?.name ===
"insights.layout.validation-failed",
);
const annotated = vi
.mocked(annotate)
.mock.calls.find(
(call) =>
(call[0] as { action?: { name?: string } })?.action?.name ===
"insights.layout.validation-failed",
);
const meta = (annotated![0] as { meta?: Record<string, unknown> }).meta!;
const excerpt = meta.received_shape_excerpt as string;
@@ -578,7 +582,9 @@ describe("PUT /api/insights/layout — v2 sections", () => {
expect(res.status).toBe(200);
const body = (await res.json()) as {
data: { sections: Array<{ id: string; visible: boolean; order: number }> };
data: {
sections: Array<{ id: string; visible: boolean; order: number }>;
};
};
// Saved sections surface in saved order, dense 0-based.
expect(body.data.sections[0]).toEqual({
@@ -651,9 +657,9 @@ describe("PUT /api/insights/layout — v2 sections", () => {
const body = (await res.json()) as {
details: { issues: Array<{ path: string }> };
};
expect(
body.details.issues.some((i) => i.path.startsWith("sections")),
).toBe(true);
expect(body.details.issues.some((i) => i.path.startsWith("sections"))).toBe(
true,
);
});
});
@@ -58,7 +58,12 @@ import { generateMetricStatus } from "@/lib/insights/metric-status";
const SESSION_OK = {
session: { id: "sess-1", expiresAt: new Date(Date.now() + 3_600_000) },
user: { id: "user-1", username: "testuser", role: "USER" as const, locale: "en" },
user: {
id: "user-1",
username: "testuser",
role: "USER" as const,
locale: "en",
},
};
const callGet = GET as unknown as (req: NextRequest) => Promise<Response>;
+1 -4
View File
@@ -19,10 +19,7 @@
*/
import { NextRequest } from "next/server";
import { z } from "zod/v4";
import {
apiSuccess,
returnAllZodIssues,
} from "@/lib/api-response";
import { apiSuccess, returnAllZodIssues } from "@/lib/api-response";
import { apiHandler, requireAuth } from "@/lib/api-handler";
import { annotate } from "@/lib/logging/context";
import { resolveServerLocale } from "@/lib/i18n/server-locale";
@@ -58,9 +58,7 @@ const SESSION_OK = {
const callGet = GET as unknown as (req: NextRequest) => Promise<Response>;
function makeReq(): NextRequest {
return new NextRequest(
new URL("http://localhost/api/insights/mood-status"),
);
return new NextRequest(new URL("http://localhost/api/insights/mood-status"));
}
/** Persisted module-preferences blob for the gated user row. */
@@ -81,9 +79,7 @@ beforeEach(() => {
describe("GET /api/insights/mood-status — module gate", () => {
it("200s when the mood module is enabled (default-on, no blob)", async () => {
vi.mocked(getSession).mockResolvedValue(SESSION_OK as never);
vi.mocked(prisma.user.findUnique).mockResolvedValue(
userRow(null) as never,
);
vi.mocked(prisma.user.findUnique).mockResolvedValue(userRow(null) as never);
const res = await callGet(makeReq());
expect(res.status).toBe(200);
const body = (await res.json()) as { data: { text: string } | null };
+4 -1
View File
@@ -20,7 +20,10 @@ import { resolveServerLocale } from "@/lib/i18n/server-locale";
import { requireAssistantSurface } from "@/lib/feature-flags";
import { requireModuleEnabled } from "@/lib/modules/gate";
import { readPeriodNarrative } from "@/lib/insights/narrative/period-narrative-generate";
import { PERIOD_DAYS, type NarrativePeriod } from "@/lib/insights/narrative/period-narrative";
import {
PERIOD_DAYS,
type NarrativePeriod,
} from "@/lib/insights/narrative/period-narrative";
import { enqueueNarrativeWarm } from "@/lib/jobs/period-narrative-shared";
export const dynamic = "force-dynamic";
@@ -57,15 +57,19 @@ import { enqueueForceWarm } from "@/lib/jobs/insight-pregenerate-shared";
const SESSION_OK = {
session: { id: "sess-1", expiresAt: new Date(Date.now() + 3_600_000) },
user: { id: "user-1", username: "testuser", role: "USER" as const, locale: "en" },
user: {
id: "user-1",
username: "testuser",
role: "USER" as const,
locale: "en",
},
};
const callPost = POST as unknown as (req: NextRequest) => Promise<Response>;
function makeReq(): NextRequest {
return new NextRequest(
new URL("http://localhost/api/insights/pregenerate"),
{ method: "POST" },
);
return new NextRequest(new URL("http://localhost/api/insights/pregenerate"), {
method: "POST",
});
}
beforeEach(() => {
@@ -51,12 +51,19 @@ import { prisma } from "@/lib/db";
const SESSION_OK = {
session: { id: "sess-1", expiresAt: new Date(Date.now() + 3_600_000) },
user: { id: "user-1", username: "testuser", role: "USER" as const, locale: "en" },
user: {
id: "user-1",
username: "testuser",
role: "USER" as const,
locale: "en",
},
};
const callGet = GET as unknown as (req: NextRequest) => Promise<Response>;
function makeReq(): NextRequest {
return new NextRequest(new URL("http://localhost/api/insights/rhythm-events"));
return new NextRequest(
new URL("http://localhost/api/insights/rhythm-events"),
);
}
beforeEach(() => {
+3 -1
View File
@@ -39,7 +39,9 @@ export const dynamic = "force-dynamic";
// these a year; the cap is a defensive bound, not an expected ceiling.
const MAX_EVENTS = 200;
const EVENT_TYPE_LIST = Array.from(EVENT_MEASUREMENT_TYPES) as MeasurementType[];
const EVENT_TYPE_LIST = Array.from(
EVENT_MEASUREMENT_TYPES,
) as MeasurementType[];
export const GET = apiHandler(async () => {
const { user } = await requireAuth();
@@ -69,12 +69,14 @@ vi.mock("next/headers", () => ({
// `recomputeUserMoodRollups` aggregate. The warm-up is fire-and-forget
// on the route so its return value is irrelevant for parity.
vi.mock("@/lib/rollups/mood-rollups", async () => {
const actual = await vi.importActual<typeof import("@/lib/rollups/mood-rollups")>(
"@/lib/rollups/mood-rollups",
);
const actual = await vi.importActual<
typeof import("@/lib/rollups/mood-rollups")
>("@/lib/rollups/mood-rollups");
return {
...actual,
ensureUserMoodRollupsFresh: vi.fn().mockResolvedValue({ recomputed: false }),
ensureUserMoodRollupsFresh: vi
.fn()
.mockResolvedValue({ recomputed: false }),
};
});
@@ -160,11 +162,61 @@ describe("GET /api/insights/targets — mood-rollup tier swap", () => {
(
prisma.moodEntryRollup.findMany as ReturnType<typeof vi.fn>
).mockResolvedValue([
{ userId: SESSION_USER.id, granularity: "DAY", bucketStart: day(0), count: 1, mean: 4, minScore: 4, maxScore: 4, sd: null, computedAt: new Date() },
{ userId: SESSION_USER.id, granularity: "DAY", bucketStart: day(1), count: 1, mean: 4, minScore: 4, maxScore: 4, sd: null, computedAt: new Date() },
{ userId: SESSION_USER.id, granularity: "DAY", bucketStart: day(2), count: 1, mean: 5, minScore: 5, maxScore: 5, sd: null, computedAt: new Date() },
{ userId: SESSION_USER.id, granularity: "DAY", bucketStart: day(3), count: 1, mean: 4, minScore: 4, maxScore: 4, sd: null, computedAt: new Date() },
{ userId: SESSION_USER.id, granularity: "DAY", bucketStart: day(4), count: 1, mean: 4.5, minScore: 4, maxScore: 5, sd: 0.5, computedAt: new Date() },
{
userId: SESSION_USER.id,
granularity: "DAY",
bucketStart: day(0),
count: 1,
mean: 4,
minScore: 4,
maxScore: 4,
sd: null,
computedAt: new Date(),
},
{
userId: SESSION_USER.id,
granularity: "DAY",
bucketStart: day(1),
count: 1,
mean: 4,
minScore: 4,
maxScore: 4,
sd: null,
computedAt: new Date(),
},
{
userId: SESSION_USER.id,
granularity: "DAY",
bucketStart: day(2),
count: 1,
mean: 5,
minScore: 5,
maxScore: 5,
sd: null,
computedAt: new Date(),
},
{
userId: SESSION_USER.id,
granularity: "DAY",
bucketStart: day(3),
count: 1,
mean: 4,
minScore: 4,
maxScore: 4,
sd: null,
computedAt: new Date(),
},
{
userId: SESSION_USER.id,
granularity: "DAY",
bucketStart: day(4),
count: 1,
mean: 4.5,
minScore: 4,
maxScore: 5,
sd: 0.5,
computedAt: new Date(),
},
]);
// Latest score read — one bounded row.
(prisma.moodEntry.findFirst as ReturnType<typeof vi.fn>).mockResolvedValue({