From 13e232c836d4fa45af37523a42ad4cb7eee611b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Wed, 4 Nov 2026 10:14:17 +0100 Subject: [PATCH] test(garmin): pin the key exchange, the mapping and a sync against a fake Garmin Unit tests cover the token cache, the single re-exchange on a refused token, the key's end read from a capped token, the 429 wait, the range chunking, and every resource's mapping over made-up records shaped from Garmin's document. The card test pins the shared anatomy, the menu and the expiry sentences. The integration test runs the sync, the backfill, connect and disconnect against a fake Garmin served over HTTP and the real schema: rows per resource under GARMIN, no second row on a re-sync, a refused key parked after one retry, a 429 that stops the run, a history walked to its start across slices, and a purge on disconnect that keeps the imported history. --- .../__tests__/garmin-card.test.tsx | 165 +++++ src/lib/garmin/__tests__/client.test.ts | 319 +++++++++ src/lib/garmin/__tests__/map.test.ts | 380 +++++++++++ tests/integration/garmin-sync.test.ts | 646 ++++++++++++++++++ 4 files changed, 1510 insertions(+) create mode 100644 src/components/settings/integrations/__tests__/garmin-card.test.tsx create mode 100644 src/lib/garmin/__tests__/client.test.ts create mode 100644 src/lib/garmin/__tests__/map.test.ts create mode 100644 tests/integration/garmin-sync.test.ts diff --git a/src/components/settings/integrations/__tests__/garmin-card.test.tsx b/src/components/settings/integrations/__tests__/garmin-card.test.tsx new file mode 100644 index 000000000..c9beb65c5 --- /dev/null +++ b/src/components/settings/integrations/__tests__/garmin-card.test.tsx @@ -0,0 +1,165 @@ +/** + * Garmin on the shared integration card. + * + * 1. Not connected: the key form, the link to Garmin Connect's key page, + * the validity choice and Connect, inside the shared card. + * 2. Connected: the same menu as Nightscout, a refused key opens the form, + * and disconnect asks first and invalidates the envelope. + * 3. The key's end: a sentence in the body while it is close, past or + * unknown, and the date in the details while it is comfortably valid. + */ +import { describe, expect, it, vi } from "vitest"; +import { createElement } from "react"; +import { renderToStaticMarkup } from "react-dom/server"; + +import { queryKeys } from "@/lib/query-keys"; + +const invalidateSpy = vi.fn(); +const capturedDisconnectOnSuccess: { fn: (() => void) | null } = { fn: null }; + +vi.mock("@tanstack/react-query", () => ({ + useMutation: ({ onSuccess }: { onSuccess?: () => void }) => { + capturedDisconnectOnSuccess.fn = onSuccess ?? null; + return { mutate: vi.fn(), isPending: false }; + }, + useQueryClient: () => ({ invalidateQueries: invalidateSpy }), +})); + +let lastCardProps: IntegrationCardProps | null = null; +vi.mock("../integration-card", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + IntegrationCard: (props: IntegrationCardProps) => { + lastCardProps = props; + return createElement(actual.IntegrationCard, props); + }, + }; +}); + +import { I18nProvider } from "@/lib/i18n/context"; +import type { GarminKeyExpiry } from "@/lib/garmin/key-expiry"; +import type { IntegrationCardProps } from "../integration-card"; +import { GarminCard, GARMIN_KEY_SETTINGS_URL } from "../garmin-card"; +import type { IntegrationStatusViewModel } from "../shared"; +import { healthFixture } from "./health-fixture"; + +function render(viewModel?: Partial) { + return renderToStaticMarkup( + + + , + ); +} + +function connected(keyExpiry: GarminKeyExpiry | null) { + return render({ + connected: true, + configured: true, + health: healthFixture(), + keyExpiry, + metricFreshness: [], + }); +} + +describe("GarminCard on the shared card", () => { + it("shows the key form inside the shared card while not connected", () => { + const html = render({ connected: false, configured: false, health: null }); + expect(html).toContain('data-slot="integration-card"'); + expect(html).toContain('data-state="not_connected"'); + expect(html).toContain('id="garmin-api-key"'); + expect(html).toContain('id="garmin-key-validity"'); + expect(html).toContain(`href="${GARMIN_KEY_SETTINGS_URL}"`); + expect(html).toContain('data-testid="garmin-connect"'); + expect(html).toContain( + 'href="https://docs.healthlog.dev/guides/connect-garmin/"', + ); + // 30, 90, 180 and 365 days, plus "I don't know". + expect(html.match(/