import { describe, expect, it } from "@effect/vitest"; import { mkdtemp, rm } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { Effect, Layer, Ref } from "effect"; import { HttpClient, HttpClientRequest, HttpClientResponse } from "effect/unstable/http"; import { NodeFileSystem } from "@effect/platform-node"; import { IntegrationsRegistry, buildUserAgent, isFetchDisabled, layer as integrationsRegistryLayer, } from "./registry"; const TEST_USER_AGENT = "executor/dev/test/cli"; // Records every outgoing request so tests can assert on URL + headers. const makeRecordingHttpClient = ( body: () => string = () => `{}`, ): Effect.Effect<{ readonly layer: Layer.Layer; readonly requests: Ref.Ref>; }> => Effect.gen(function* () { const requests = yield* Ref.make>([]); const layer = Layer.succeed(HttpClient.HttpClient)( HttpClient.make((request: HttpClientRequest.HttpClientRequest) => Effect.gen(function* () { yield* Ref.update(requests, (xs) => [ ...xs, { url: request.url, userAgent: request.headers["user-agent"] ?? "" }, ]); return HttpClientResponse.fromWeb( request, new Response(body(), { status: 200, headers: { "content-type": "application/json" }, }), ); }), ), ); return { layer, requests }; }); describe("buildUserAgent", () => { it("formats executor///", () => { expect(buildUserAgent({ channel: "stable", version: "1.2.3", client: "cli" })).toBe( "executor/stable/1.2.3/cli", ); expect(buildUserAgent({ channel: "beta", version: "1.2.3-beta.0", client: "desktop" })).toBe( "executor/beta/1.2.3-beta.0/desktop", ); }); it("contains the substring 'executor' so the worker filter matches", () => { const ua = buildUserAgent({ channel: "dev", version: "0.0.0", client: "cli" }); expect(ua.includes("executor")).toBe(true); }); }); describe("isFetchDisabled", () => { it("honors DO_NOT_TRACK", () => { expect(isFetchDisabled({ DO_NOT_TRACK: "1" })).toBe(true); expect(isFetchDisabled({ DO_NOT_TRACK: "true" })).toBe(true); expect(isFetchDisabled({ DO_NOT_TRACK: "0" })).toBe(false); }); it("honors EXECUTOR_DISABLE_INTEGRATIONS_FETCH", () => { expect(isFetchDisabled({ EXECUTOR_DISABLE_INTEGRATIONS_FETCH: "1" })).toBe(true); expect(isFetchDisabled({ EXECUTOR_DISABLE_INTEGRATIONS_FETCH: "yes" })).toBe(true); }); it("defaults to enabled when neither env var is set", () => { expect(isFetchDisabled({})).toBe(false); }); }); const withTempCache = ( body: (cacheDir: string) => Effect.Effect, ): Effect.Effect => Effect.acquireUseRelease( Effect.promise(() => mkdtemp(join(tmpdir(), "integrations-registry-test-"))), body, (dir) => Effect.promise(() => rm(dir, { recursive: true, force: true })), ); describe("IntegrationsRegistry", () => { it.effect("disabled flag short-circuits — no network, empty registry", () => withTempCache((cacheDir) => Effect.gen(function* () { const { layer: httpLayer, requests } = yield* makeRecordingHttpClient(); const program = Effect.gen(function* () { const registry = yield* IntegrationsRegistry; return yield* registry.get(); }); const result = yield* program.pipe( Effect.provide( integrationsRegistryLayer({ userAgent: TEST_USER_AGENT, disabled: true, cacheDir, }).pipe(Layer.provide(httpLayer), Layer.provide(NodeFileSystem.layer)), ), ); expect(result).toBeUndefined(); const sent = yield* Ref.get(requests); expect(sent).toHaveLength(0); }), ), ); it.effect("happy path — fetches, parses, sends User-Agent header", () => withTempCache((cacheDir) => Effect.gen(function* () { const payload = { providers: { acme: { name: "Acme" } } }; const { layer: httpLayer, requests } = yield* makeRecordingHttpClient(() => JSON.stringify(payload), ); const program = Effect.gen(function* () { const registry = yield* IntegrationsRegistry; return yield* registry.get(); }); const result = yield* program.pipe( Effect.provide( integrationsRegistryLayer({ userAgent: TEST_USER_AGENT, // Pinned: this case asserts the fetch happens, so it must not // inherit an ambient DO_NOT_TRACK / EXECUTOR_DISABLE_INTEGRATIONS_FETCH // (CI sets both). Mirrors the analytics package's test config. disabled: false, cacheDir, url: "https://integrations.test/api.json", }).pipe(Layer.provide(httpLayer), Layer.provide(NodeFileSystem.layer)), ), ); expect(result).toEqual(payload); const sent = yield* Ref.get(requests); expect(sent).toHaveLength(1); expect(sent[0]?.url).toBe("https://integrations.test/api.json"); expect(sent[0]?.userAgent).toBe(TEST_USER_AGENT); }), ), ); it.effect("cache hit — second get returns cached value without re-fetching", () => withTempCache((cacheDir) => Effect.gen(function* () { const { layer: httpLayer, requests } = yield* makeRecordingHttpClient(() => JSON.stringify({ ok: true }), ); const program = Effect.gen(function* () { const registry = yield* IntegrationsRegistry; const first = yield* registry.get(); const second = yield* registry.get(); return { first, second }; }); const { first, second } = yield* program.pipe( Effect.provide( integrationsRegistryLayer({ userAgent: TEST_USER_AGENT, // Pinned: this case asserts the fetch happens, so it must not // inherit an ambient DO_NOT_TRACK / EXECUTOR_DISABLE_INTEGRATIONS_FETCH // (CI sets both). Mirrors the analytics package's test config. disabled: false, cacheDir, url: "https://integrations.test/api.json", }).pipe(Layer.provide(httpLayer), Layer.provide(NodeFileSystem.layer)), ), ); expect(first).toEqual({ ok: true }); expect(second).toEqual({ ok: true }); const sent = yield* Ref.get(requests); // Either 0 (disk hit) or 1 (network), but never 2 — the second // `get()` is served by the in-memory cached effect. expect(sent.length).toBeLessThanOrEqual(1); }), ), ); });