Files
HealthLog/src/lib/polar/__tests__/response-classifier.test.ts
T
Marc-André Bombeck 87e570abfe fix(polar): lift invalid_grant to reauth and harden the credentials surface
classifyPolarError now lifts a 400 carrying invalid_grant on the token
endpoint from persistent to reauth_required, mirroring Oura via the shared
isOAuthInvalidGrant helper. A stale or replayed authorization code at connect
time now surfaces the reconnect prompt instead of silently parking after 24 h.

The BYO-credentials DELETE now audit-logs and parks the integration ledger at
disconnected for parity with the dedicated disconnect route, and the PUT
audit-logs the credential write. Refresh the connect-route comment that still
described Polar as env-only OAuth; it has resolved BYO-first since v1.17.1.

Add the invalid_grant classification test plus the previously-missing
callback (happy path, CSRF mismatch, cross-user, off-spec token), connect, and
credentials route tests.
2026-06-15 00:09:22 +02:00

65 lines
1.9 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
PolarApiError,
classifyPolarError,
isInvalidGrant,
} from "../response-classifier";
describe("classifyPolarError — invalid_grant reauth lift (H-1)", () => {
it("lifts a 400 invalid_grant on the token endpoint to reauth_required", () => {
const err = new PolarApiError({
verb: "exchangeCode",
classification: "persistent",
httpStatus: 400,
reason: "http_400",
upstreamError: "invalid_grant",
});
expect(isInvalidGrant(err)).toBe(true);
expect(classifyPolarError(err)).toBe("reauth_required");
});
it("recovers the verdict from the message shape after a lost prototype", () => {
// A pg-boss retry can strip the PolarApiError prototype; the message-shape
// fallback must still recognise the invalid_grant signal.
const bare = new Error("Polar exchangeCode error: 400 - invalid_grant");
expect(isInvalidGrant(bare)).toBe(true);
expect(classifyPolarError(bare)).toBe("reauth_required");
});
it("leaves a non-invalid_grant 400 as persistent", () => {
const err = new PolarApiError({
verb: "exchangeCode",
classification: "persistent",
httpStatus: 400,
reason: "http_400",
upstreamError: "invalid_client",
});
expect(isInvalidGrant(err)).toBe(false);
expect(classifyPolarError(err)).toBe("persistent");
});
it("keeps a 401 reauth and a 500 transient classification", () => {
expect(
classifyPolarError(
new PolarApiError({
verb: "fetchSleeps",
classification: "reauth_required",
httpStatus: 401,
reason: "http_401",
}),
),
).toBe("reauth_required");
expect(
classifyPolarError(
new PolarApiError({
verb: "fetchSleeps",
classification: "transient",
httpStatus: 500,
reason: "http_500",
}),
),
).toBe("transient");
});
});