Files
HealthLog/tests/integration/workout-routes-opt-in-migration.test.ts
T
Marc-André Bombeck 81e1f4cad1 fix(workouts): keep tracks and climb for accounts that already had them
Migration 0434 turns the route opt-in on for every account that already
holds a stored workout track, so its tracks keep arriving after the
upgrade; only accounts without one start off.

Workouts stored before this release with a track but no elevation get
their metres climbed written into Workout.elevationM by a one-time
background pass. It runs detached at worker boot, reads one track at a
time with a pause between batches of 100, and keeps its position in
app_settings, so a restart resumes and a finished pass ends at once.

The "starting" health reason is bounded: after five minutes the probe
says plain degraded and the process logs one line, and the time the
background role took to come up is logged either way. The compose
healthcheck's ten-minute start period already keeps those probes from
counting; its comment now says so.
2026-10-21 20:51:13 +02:00

87 lines
2.5 KiB
TypeScript

/**
* Migration 0434: an account that already holds a stored workout track keeps
* the route opt-in ON after the upgrade; an account without one starts OFF.
*
* The container applies every migration before any row exists, so the test
* seeds both kinds of account and runs the migration's UPDATE again, read
* from the migration file itself rather than copied, so it tests the shipped
* statement.
*/
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { beforeEach, describe, expect, it } from "vitest";
import { getPrismaClient, truncateAllTables } from "./setup";
const MIGRATION = join(
process.cwd(),
"prisma/migrations/0434_workout_routes_keep_and_climb_cursor/migration.sql",
);
function shippedUpdate(): string {
const sql = readFileSync(MIGRATION, "utf8");
const match = /UPDATE "users"[\s\S]*?;\n/.exec(sql);
if (!match) throw new Error("0434 carries no UPDATE of users");
return match[0];
}
beforeEach(async () => {
await truncateAllTables(getPrismaClient());
});
describe("0434 — route opt-in for accounts that already keep tracks", () => {
it("turns it on only where a track is stored", async () => {
const prisma = getPrismaClient();
for (const id of ["with-route", "without-route", "workout-no-route"]) {
await prisma.user.create({
data: { id, username: id, email: `${id}@example.test` },
});
}
const started = new Date("2026-09-01T07:00:00Z");
const ended = new Date("2026-09-01T08:00:00Z");
const withRoute = await prisma.workout.create({
data: {
userId: "with-route",
sportType: "running",
startedAt: started,
endedAt: ended,
durationSec: 3600,
source: "APPLE_HEALTH",
externalId: "w-1",
},
});
await prisma.workoutRoute.create({
data: {
workoutId: withRoute.id,
geometryEncrypted: Buffer.from("sealed"),
},
});
await prisma.workout.create({
data: {
userId: "workout-no-route",
sportType: "running",
startedAt: started,
endedAt: ended,
durationSec: 3600,
source: "APPLE_HEALTH",
externalId: "w-2",
},
});
await prisma.$executeRawUnsafe(shippedUpdate());
const flags = Object.fromEntries(
(
await prisma.user.findMany({
select: { id: true, storeWorkoutRoutes: true },
})
).map((u) => [u.id, u.storeWorkoutRoutes]),
);
expect(flags).toEqual({
"with-route": true,
"without-route": false,
"workout-no-route": false,
});
});
});