diff --git a/src/__tests__/helpers/provider-dirs.ts b/src/__tests__/helpers/provider-dirs.ts new file mode 100644 index 000000000..9e2a187b4 --- /dev/null +++ b/src/__tests__/helpers/provider-dirs.ts @@ -0,0 +1,20 @@ +/** + * The directories under `src/lib` that hold a provider's client and sync, + * derived from the connection keys the product knows (`CONNECTION_KEYS`), + * so a guard over provider code cannot keep a list of its own that a new + * integration slips past. Each key is checked to be a directory: a key that + * names none fails here rather than narrowing a guard in silence. + */ +import { statSync } from "node:fs"; +import { join } from "node:path"; + +import { CONNECTION_KEYS } from "@/lib/integrations/connection-health"; + +const LIB = join(process.cwd(), "src", "lib"); + +export const PROVIDER_DIRS: readonly string[] = CONNECTION_KEYS.map((key) => { + if (!statSync(join(LIB, key), { throwIfNoEntry: false })?.isDirectory()) { + throw new Error(`provider-dirs: src/lib/${key} is not a directory`); + } + return key; +}); diff --git a/src/__tests__/page-walk-guard.test.ts b/src/__tests__/page-walk-guard.test.ts index bd843d704..a08129755 100644 --- a/src/__tests__/page-walk-guard.test.ts +++ b/src/__tests__/page-walk-guard.test.ts @@ -11,20 +11,10 @@ import { readFileSync } from "node:fs"; import { join } from "node:path"; import { describe, expect, it } from "vitest"; -import { walkSourceFiles } from "./helpers/source-files"; +import { PROVIDER_DIRS } from "./helpers/provider-dirs"; +import { stripComments, walkSourceFiles } from "./helpers/source-files"; const LIB = join(process.cwd(), "src", "lib"); -const PROVIDER_DIRS = [ - "dawarich", - "fitbit", - "google-health", - "nightscout", - "oura", - "polar", - "strava", - "whoop", - "withings", -]; /** * Page loops that stay outside `walkPages` on purpose, with how many matches * each file may hold and why. A file with more matches than its allowance has @@ -58,12 +48,6 @@ const EXCEPTIONS: Readonly> = { const PAGE_LOOP = /\b(?:pages?|pageCount|pagesRead|page)(?:\s*-\s*[A-Za-z_]\w*)?\s*(?:<|<=|>|>=)\s*(?:[A-Z_][A-Z0-9_]*|maxPages|[2-9]|[1-9]\d+)\b/g; -function strip(text: string): string { - return text - .replace(/\/\*[\s\S]*?\*\//g, "") - .replace(/(^|[^:])\/\/.*$/gm, "$1"); -} - describe("provider clients walk pages through walkPages", () => { const files = walkSourceFiles(LIB, { floor: 1000 }).filter( (f) => @@ -72,10 +56,20 @@ describe("provider clients walk pages through walkPages", () => { !f.endsWith(".test.ts"), ); const matches = (f: string) => - strip(readFileSync(join(LIB, f), "utf8")).match(PAGE_LOOP)?.length ?? 0; + stripComments(readFileSync(join(LIB, f), "utf8")).match(PAGE_LOOP) + ?.length ?? 0; it("walks the provider directories", () => { expect(files.length).toBeGreaterThanOrEqual(60); + expect(PROVIDER_DIRS).toEqual( + expect.arrayContaining(["strava", "whoop", "withings", "google-health"]), + ); + for (const dir of PROVIDER_DIRS) { + expect( + files.some((f) => f.startsWith(`${dir}/`)), + dir, + ).toBe(true); + } }); it("finds no page loop of their own beyond the exceptions", () => {