mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
fix(workouts): read a route that does not open as no track
A damaged or foreign-key route ciphertext threw out of readRouteGeometry, so one bad track answered the workout detail with a 500 and failed the activity insight job. It now reads as no track with a warning on the request event, and still never falls back to the readable column.
This commit is contained in:
1 parent
e7f3385374
commit
c1aa235e27
2 files changed
+30
-7
No files matched your search
@@ -1,6 +1,11 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { Buffer } from "node:buffer";
|
||||
|
||||
const addWarning = vi.hoisted(() => vi.fn());
|
||||
vi.mock("@/lib/logging/context", () => ({
|
||||
getEvent: () => ({ addWarning }),
|
||||
}));
|
||||
|
||||
import { _resetCryptoCacheForTests, encryptBytes } from "@/lib/crypto";
|
||||
import {
|
||||
decryptRouteGeometry,
|
||||
@@ -56,11 +61,16 @@ describe("route geometry cipher", () => {
|
||||
expect(() => decryptRouteGeometry(new Uint8Array(other))).toThrow();
|
||||
});
|
||||
|
||||
it("throws on a ciphertext that does not open instead of using the legacy column", () => {
|
||||
it("reads a ciphertext that does not open as no track, with a warning, never the legacy column", () => {
|
||||
addWarning.mockClear();
|
||||
const sealed = encryptRouteGeometry(TRACK);
|
||||
sealed[sealed.byteLength - 1] ^= 0xff;
|
||||
expect(() =>
|
||||
expect(
|
||||
readRouteGeometry({ geometry: TRACK, geometryEncrypted: sealed }),
|
||||
).toThrow();
|
||||
).toBeNull();
|
||||
expect(addWarning).toHaveBeenCalledTimes(1);
|
||||
expect(String(addWarning.mock.calls[0][0])).toContain(
|
||||
"workout route decrypt failed",
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -11,8 +11,10 @@
|
||||
*
|
||||
* Every reader goes through {@link readRouteGeometry}: the ciphertext when a
|
||||
* row has one, the legacy readable column for a row the boot-time backfill has
|
||||
* not reached yet. A ciphertext that does not open throws (fail-closed), the
|
||||
* same posture as `readNote`; it is never replaced by the readable column.
|
||||
* not reached yet. A ciphertext that does not open reads as no track, with a
|
||||
* warning on the request's event: it is never replaced by the readable column,
|
||||
* and one damaged track must not fail the workout detail or the insight job
|
||||
* that reads it. `decryptRouteGeometry` itself still throws.
|
||||
*
|
||||
* The wire shape is unchanged: readers hand back the same GeoJSON object the
|
||||
* JSONB column used to return.
|
||||
@@ -21,6 +23,7 @@ import { Buffer } from "node:buffer";
|
||||
|
||||
import { decryptBytes, encryptBytes } from "@/lib/crypto";
|
||||
import { WORKOUT_ROUTE_GEOMETRY_AAD } from "@/lib/crypto/encrypted-columns";
|
||||
import { getEvent } from "@/lib/logging/context";
|
||||
|
||||
/** Seal a route's GeoJSON geometry for `WorkoutRoute.geometryEncrypted`. */
|
||||
export function encryptRouteGeometry(
|
||||
@@ -43,14 +46,24 @@ export function decryptRouteGeometry(sealed: Uint8Array): unknown {
|
||||
|
||||
/**
|
||||
* The geometry a route row holds: the ciphertext first, the legacy readable
|
||||
* column only when there is no ciphertext. Null when the row holds neither.
|
||||
* column only when there is no ciphertext. Null when the row holds neither,
|
||||
* and null (with a warning) when the ciphertext does not open.
|
||||
*/
|
||||
export function readRouteGeometry(row: {
|
||||
geometry?: unknown;
|
||||
geometryEncrypted?: Uint8Array | null;
|
||||
}): unknown {
|
||||
if (row.geometryEncrypted && row.geometryEncrypted.byteLength > 0) {
|
||||
return decryptRouteGeometry(row.geometryEncrypted);
|
||||
try {
|
||||
return decryptRouteGeometry(row.geometryEncrypted);
|
||||
} catch (err) {
|
||||
getEvent()?.addWarning(
|
||||
`workout route decrypt failed: ${
|
||||
err instanceof Error ? err.message : String(err)
|
||||
}`,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return row.geometry ?? null;
|
||||
}
|
||||
Reference in new issue
Block a user