test: point guards at the split provider modules

The page-walk exception for the Google Health list walk follows
forEachDataPointPage into data-points.ts, and the workout provenance guard
reads the raw Polar and Strava sport labels where the mappers now live.
The write-step helper's tests assert the result it returns, including a
failed fold and a caller that re-warms the status cards itself.
This commit is contained in:
Marc-André Bombeck committed 2026-10-23 13:44:21 +02:00
1 parent 8b3c095df5
commit c2a63307ed
3 files changed
+29 -7

No files matched your search

+1 -1
View File
@@ -35,7 +35,7 @@ const LIB = join(process.cwd(), "src", "lib");
* three outcomes cannot carry.
*/
const EXCEPTIONS: Readonly<Record<string, { matches: number; why: string }>> = {
"google-health/client.ts": {
"google-health/data-points.ts": {
matches: 1,
why: "progress-bounded streaming list walk with slice budget and resume (forEachDataPointPage)",
},
@@ -83,7 +83,7 @@ const PROVIDER_INGESTS: ProviderIngest[] = [
provider: "Polar",
mapper: "mapPolarSportType",
mapperFile: "lib/polar/sport-map.ts",
captureFile: "lib/polar/client.ts",
captureFile: "lib/polar/map.ts",
captureField: "detailed_sport_info",
writeFile: "lib/polar/sync-workouts.ts",
metadataKey: "polarSport",
@@ -92,7 +92,7 @@ const PROVIDER_INGESTS: ProviderIngest[] = [
provider: "Strava",
mapper: "mapStravaSportType",
mapperFile: "lib/strava/sport-map.ts",
captureFile: "lib/strava/client.ts",
captureFile: "lib/strava/map.ts",
captureField: "rawSportType",
writeFile: "lib/strava/sync.ts",
metadataKey: "stravaType",
@@ -93,7 +93,9 @@ describe("afterMeasurementMutation", () => {
});
it("is a no-op for an empty row set", async () => {
await afterMeasurementMutation("user-1", [], { source: "manual" });
await expect(
afterMeasurementMutation("user-1", [], { source: "manual" }),
).resolves.toEqual({ rollupFailed: false });
expect(mocks.recompute).not.toHaveBeenCalled();
expect(mocks.invalidate).not.toHaveBeenCalled();
});
@@ -101,8 +103,9 @@ describe("afterMeasurementMutation", () => {
it("still invalidates status insights when the rollup recompute fails", async () => {
mocks.recompute.mockRejectedValue(new Error("populator down"));
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
let result: { rollupFailed: boolean } | undefined;
try {
await afterMeasurementMutation(
result = await afterMeasurementMutation(
"user-1",
[
{
@@ -115,10 +118,29 @@ describe("afterMeasurementMutation", () => {
} finally {
warn.mockRestore();
}
// The rollup failure never bubbles AND never gates the second leg.
// The rollup failure never bubbles AND never gates the second leg; the
// caller learns of it from the result.
expect(result).toEqual({ rollupFailed: true });
expect(mocks.invalidate).toHaveBeenCalledTimes(1);
});
it("leaves the status cards alone when the caller invalidates them itself", async () => {
await expect(
afterMeasurementMutation(
"user-1",
[
{
type: "WEIGHT" as MeasurementType,
measuredAt: day("2026-05-10T08:00:00Z"),
},
],
{ source: "sync", invalidateStatus: false },
),
).resolves.toEqual({ rollupFailed: false });
expect(mocks.recompute).toHaveBeenCalledTimes(1);
expect(mocks.invalidate).not.toHaveBeenCalled();
});
it("never rejects when the status-insight invalidation fails", async () => {
mocks.invalidate.mockRejectedValue(new Error("cache down"));
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
@@ -134,7 +156,7 @@ describe("afterMeasurementMutation", () => {
],
{ source: "manual" },
),
).resolves.toBeUndefined();
).resolves.toEqual({ rollupFailed: false });
// Let the fire-and-forget rejection settle inside the helper's catch.
await new Promise((resolve) => setImmediate(resolve));
} finally {