mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-15 06:52:04 +00:00
test(achievements): coverage for evaluators + UI states
Three new layers of coverage for the v1.4.18 expansion set: - Unit (expansion-metrics.test.ts) — drives synthetic data through every new evaluator (count by type, mood mean improvement, weekend warrior, hidden Easter-egg triggers including the Berlin TZ classifier for night-owl / early-bird) and the earnability flag builder. 13 new assertions. - Integration (achievements-expansion.test.ts) — round-trips real prisma rows (MoodEntry's YYYY-MM-DD `date` string, Measurement type discrimination, audit log row) through `buildExpansionMetricValues` so a schema drift in any of the row shapes surfaces here rather than at runtime. We don't drive the full HTTP route in this spec because of pre-existing medication_schedules.days_of_week migration drift. - E2E (achievements.spec.ts) — the existing sidebar smoke gets a sibling describe-block exercising all four card states (unlocked / locked-earnable / hidden-locked / hidden-unlocked) and asserting hidden-locked cards never leak their real strings to the rendered HTML, which is the entire point of the "hidden" state. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
b88f2de865
commit
6912d4e2b6
3 files changed
+601
No files matched your search
@@ -0,0 +1,180 @@
|
||||
/**
|
||||
* Integration test for the v1.4.18 achievement expansion.
|
||||
*
|
||||
* Drives a fresh Postgres user through the moments when expansion
|
||||
* predicates fire, verifying:
|
||||
* - mood entries persisted as `MoodEntry` rows are picked up by
|
||||
* `getMoodMetrics` (the row shape uses YYYY-MM-DD strings, not
|
||||
* Date objects, which is the most common drift surface)
|
||||
* - measurement counts are split by `MeasurementType` correctly
|
||||
* - earnability flags reflect what the user actually has
|
||||
*
|
||||
* We don't drive the full HTTP route here because the existing
|
||||
* `medication_schedules.days_of_week` migration drift makes the route
|
||||
* fail in the integration container; that's a pre-existing issue
|
||||
* tracked separately. Instead we round-trip the prisma rows through
|
||||
* `buildExpansionMetricValues` and `getEarnabilityFlags`, which is
|
||||
* the same code-path the route exercises after the fetch.
|
||||
*/
|
||||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
buildExpansionMetricValues,
|
||||
getEarnabilityFlags,
|
||||
} from "@/lib/gamification/expansion-metrics";
|
||||
|
||||
import { getPrismaClient, truncateAllTables } from "./setup";
|
||||
|
||||
beforeEach(async () => {
|
||||
await truncateAllTables(getPrismaClient());
|
||||
});
|
||||
|
||||
describe("achievements expansion — fresh user, real prisma rows", () => {
|
||||
it("recognises a single mood entry as `hasMood: true`", async () => {
|
||||
const prisma = getPrismaClient();
|
||||
const user = await prisma.user.create({
|
||||
data: {
|
||||
username: "mood-tracker",
|
||||
email: "mood@example.test",
|
||||
createdAt: new Date("2026-01-01T00:00:00Z"),
|
||||
},
|
||||
});
|
||||
await prisma.moodEntry.create({
|
||||
data: {
|
||||
userId: user.id,
|
||||
date: "2026-04-15",
|
||||
mood: "GUT",
|
||||
score: 4,
|
||||
moodLoggedAt: new Date("2026-04-15T08:00:00Z"),
|
||||
},
|
||||
});
|
||||
|
||||
const moodEntries = await prisma.moodEntry.findMany({
|
||||
where: { userId: user.id },
|
||||
select: { date: true, score: true, moodLoggedAt: true },
|
||||
});
|
||||
|
||||
const result = buildExpansionMetricValues({
|
||||
measurements: [],
|
||||
moodEntries,
|
||||
intakeEvents: [],
|
||||
auditEvents: [],
|
||||
});
|
||||
|
||||
expect(result.moodEntryCount).toBe(1);
|
||||
|
||||
const flags = getEarnabilityFlags({
|
||||
hasMedication: false,
|
||||
moodEntryCount: result.moodEntryCount,
|
||||
measurementCounts: {
|
||||
weightCount: result.weightMeasurementCount,
|
||||
bpCount: result.bpMeasurementCount,
|
||||
pulseCount: result.pulseMeasurementCount,
|
||||
},
|
||||
});
|
||||
expect(flags.hasMood).toBe(true);
|
||||
expect(flags.hasWeight).toBe(false);
|
||||
});
|
||||
|
||||
it("counts measurements by type from real DB rows", async () => {
|
||||
const prisma = getPrismaClient();
|
||||
const user = await prisma.user.create({
|
||||
data: {
|
||||
username: "many-readings",
|
||||
email: "readings@example.test",
|
||||
createdAt: new Date("2026-01-01T00:00:00Z"),
|
||||
},
|
||||
});
|
||||
await prisma.measurement.createMany({
|
||||
data: [
|
||||
{
|
||||
userId: user.id,
|
||||
type: "WEIGHT",
|
||||
value: 80,
|
||||
unit: "kg",
|
||||
measuredAt: new Date("2026-04-01T08:00:00Z"),
|
||||
},
|
||||
{
|
||||
userId: user.id,
|
||||
type: "WEIGHT",
|
||||
value: 80.5,
|
||||
unit: "kg",
|
||||
measuredAt: new Date("2026-04-02T08:00:00Z"),
|
||||
},
|
||||
{
|
||||
userId: user.id,
|
||||
type: "BLOOD_PRESSURE_SYS",
|
||||
value: 120,
|
||||
unit: "mmHg",
|
||||
measuredAt: new Date("2026-04-01T08:00:00Z"),
|
||||
},
|
||||
{
|
||||
userId: user.id,
|
||||
type: "BLOOD_PRESSURE_DIA",
|
||||
value: 80,
|
||||
unit: "mmHg",
|
||||
measuredAt: new Date("2026-04-01T08:00:00Z"),
|
||||
},
|
||||
{
|
||||
userId: user.id,
|
||||
type: "PULSE",
|
||||
value: 65,
|
||||
unit: "bpm",
|
||||
measuredAt: new Date("2026-04-03T08:00:00Z"),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const measurements = await prisma.measurement.findMany({
|
||||
where: {
|
||||
userId: user.id,
|
||||
type: {
|
||||
in: ["WEIGHT", "BLOOD_PRESSURE_SYS", "BLOOD_PRESSURE_DIA", "PULSE"],
|
||||
},
|
||||
},
|
||||
select: { type: true, measuredAt: true },
|
||||
});
|
||||
|
||||
const result = buildExpansionMetricValues({
|
||||
measurements,
|
||||
moodEntries: [],
|
||||
intakeEvents: [],
|
||||
auditEvents: [],
|
||||
});
|
||||
|
||||
expect(result.weightMeasurementCount).toBe(2);
|
||||
expect(result.bpMeasurementCount).toBe(1); // SYS canonical, DIA not double-counted
|
||||
expect(result.pulseMeasurementCount).toBe(1);
|
||||
});
|
||||
|
||||
it("fires the doctor-PDF hidden trigger from a real audit-log row", async () => {
|
||||
const prisma = getPrismaClient();
|
||||
const user = await prisma.user.create({
|
||||
data: {
|
||||
username: "exporter",
|
||||
email: "exporter@example.test",
|
||||
createdAt: new Date("2026-01-01T00:00:00Z"),
|
||||
},
|
||||
});
|
||||
await prisma.auditLog.create({
|
||||
data: {
|
||||
userId: user.id,
|
||||
action: "doctor-report.export",
|
||||
},
|
||||
});
|
||||
|
||||
const auditEvents = await prisma.auditLog.findMany({
|
||||
where: { userId: user.id, action: "doctor-report.export" },
|
||||
select: { action: true, createdAt: true },
|
||||
});
|
||||
|
||||
const result = buildExpansionMetricValues({
|
||||
measurements: [],
|
||||
moodEntries: [],
|
||||
intakeEvents: [],
|
||||
auditEvents,
|
||||
});
|
||||
|
||||
expect(result.doctorPdfCount).toBe(1);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user