448 lines · 14.7 KB · TypeScript
RawDownload
/* oxlint-disable executor/no-try-catch-or-throw -- boundary: test temporarily overrides global fetch and restores it in finally */import { readFile } from "node:fs/promises";import { join } from "node:path"; import { describe, expect, it } from "@effect/vitest";import { Effect, Exit } from "effect";import { ToolResult } from "@executor-js/sdk"; import { FLUSH, pktLine } from "../git-client/pktline";import { bundleEntry } from "../pipeline/bundle";import { makeInProcessAppToolExecutor } from "../executor/app-tool-executor";import type { AppDescriptor } from "../pipeline/descriptor";import { makeAppsPlugin, projectAppsToolSchema } from "./apps-plugin";import type { AppSourceRecord, AppsStore } from "./store"; const bundle = (source: string) =>  bundleEntry({    files: new Map([["tools/sync.ts", source]]),    entry: "tools/sync.ts",  }); const concat = (parts: readonly Uint8Array[]): Uint8Array => {  const out = new Uint8Array(parts.reduce((sum, part) => sum + part.length, 0));  let offset = 0;  for (const part of parts) {    out.set(part, offset);    offset += part.length;  }  return out;}; const advertisement = (sha: string): Uint8Array =>  concat([    pktLine("# service=git-upload-pack\n"),    FLUSH,    pktLine(      `${sha} HEAD\0symref=HEAD:refs/heads/main multi_ack side-band-64k thin-pack ofs-delta\n`,    ),    pktLine(`${sha} refs/heads/main\n`),    FLUSH,  ]); const sideBand = (bytes: Uint8Array): Uint8Array => {  const payload = new Uint8Array(bytes.length + 1);  payload[0] = 1;  payload.set(bytes, 1);  return concat([pktLine("NAK\n"), pktLine(payload), FLUSH]);}; const makeSyncStore = (): AppsStore & {  readonly sources: Map<string, AppSourceRecord>;  readonly descriptor: () => AppDescriptor | null;} => {  const blobs = new Map<string, string>();  const sources = new Map<string, AppSourceRecord>();  let descriptor: AppDescriptor | null = null;  let descriptorKey: string | null = null;  return {    sources,    descriptor: () => descriptor,    putBlob: (body) =>      Effect.sync(() => {        const key = `blob:${blobs.size}`;        blobs.set(key, body);        return key;      }),    getBlob: (key) => Effect.sync(() => blobs.get(key) ?? null),    getDescriptorRecord: () =>      Effect.sync(() =>        descriptor ? { sourceRef: descriptor.sourceRef, descriptorKey: descriptorKey ?? "" } : null,      ),    putPublished: (next, nextDescriptorKey) =>      Effect.sync(() => {        descriptor = next;        descriptorKey = nextDescriptorKey;      }),    listActiveTools: () => Effect.sync(() => descriptor?.tools ?? []),    getTool: (name) =>      Effect.sync(() => {        const tool = descriptor?.tools.find((item) => item.name === name);        return tool          ? {              app: descriptor!.app,              name: tool.name,              bundleKey: tool.bundleKey,              description: tool.description,              inputSchema: tool.inputSchema,              outputSchema: tool.outputSchema,              integrations: tool.integrations,              annotations: tool.annotations,            }          : null;      }),    putSource: (record) =>      Effect.sync(() => {        sources.set(record.slug, record);      }),    listSources: () => Effect.sync(() => [...sources.values()]),    getSource: (slug) => Effect.sync(() => sources.get(slug) ?? null),    removeSource: (slug) =>      Effect.sync(() => {        sources.delete(slug);      }),  };}; const fixtureFetch = async () => {  const dir = join(import.meta.dirname, "..", "source", "fixtures");  const [shas, pack1, pack2] = await Promise.all([    readFile(join(dir, "git-fixture-shas.txt"), "utf8"),    readFile(join(dir, "git-fixture-v1.pack")),    readFile(join(dir, "git-fixture-v2.pack")),  ]);  const [sha1, sha2] = shas.trim().split("\n");  let current = { sha: sha1!, pack: new Uint8Array(pack1) };  let packRequests = 0;  return {    fetch: (async (rawUrl: string) => {      const url = new URL(rawUrl);      if (url.pathname === "/repo.git/info/refs") {        return new Response(new Uint8Array(advertisement(current.sha)).buffer, {          headers: { "content-type": "application/x-git-upload-pack-advertisement" },        });      }      if (url.pathname === "/repo.git/git-upload-pack") {        packRequests += 1;        return new Response(new Uint8Array(sideBand(current.pack)).buffer, {          headers: { "content-type": "application/x-git-upload-pack-result" },        });      }      return new Response("not found", { status: 404 });    }) as typeof fetch,    advance: () => {      current = { sha: sha2!, pack: new Uint8Array(pack2) };    },    packRequests: () => packRequests,  };}; const makeInvokeStore = (input: {  readonly bundle: string;  readonly integrations: AppDescriptor["tools"][number]["integrations"];}): AppsStore => ({  putBlob: () => Effect.succeed("bundle"),  getBlob: () => Effect.succeed(input.bundle),  getDescriptorRecord: () => Effect.succeed(null),  putPublished: () => Effect.void,  listActiveTools: () => Effect.succeed([]),  getTool: () =>    Effect.succeed({      app: "crm",      name: "sync",      bundleKey: "bundle",      description: "Sync",      integrations: input.integrations,    }),  putSource: () => Effect.void,  listSources: () => Effect.succeed([]),  getSource: () => Effect.succeed(null),  removeSource: () => Effect.void,}); const invokeCtx = (input: {  readonly bundle: string;  readonly execute: (address: string, args: unknown) => Effect.Effect<unknown, unknown>;}) =>  ({    storage: makeInvokeStore({      bundle: input.bundle,      integrations: { crm: { slug: "dealcloud", mode: "one" } },    }),    connections: {      list: () => Effect.succeed([]),      get: () =>        Effect.succeed({          address: "tools.dealcloud.org.main",          integration: "dealcloud",          owner: "org",          name: "main",        }),    },    execute: (address: string, args: unknown) => input.execute(String(address), args),  }) as never; describe("apps plugin schema projection", () => {  it.effect("narrows synthesized integration fields to connection address enums", () =>    Effect.gen(function* () {      const storage: AppsStore = {        putBlob: () => Effect.succeed("bundle"),        getBlob: () => Effect.succeed(null),        getDescriptorRecord: () => Effect.succeed(null),        putPublished: () => Effect.void,        listActiveTools: () => Effect.succeed([]),        putSource: () => Effect.void,        listSources: () => Effect.succeed([]),        getSource: () => Effect.succeed(null),        removeSource: () => Effect.void,        getTool: () =>          Effect.succeed({            app: "crm",            name: "sync",            bundleKey: "bundle",            description: "Sync",            integrations: {              crm: { slug: "dealcloud", mode: "one" },              inboxes: { slug: "gmail", mode: "many" },            },          }),      };      const result = yield* projectAppsToolSchema(        {          storage,          connections: {            list: ({ integration }: { readonly integration?: unknown }) =>              Effect.succeed(                String(integration) === "dealcloud"                  ? [{ address: "tools.dealcloud.org.main" }]                  : [{ address: "tools.gmail.org.work" }, { address: "tools.gmail.user.personal" }],              ),          },        } as never,        "sync",        {          type: "object",          properties: {            updatedSince: { type: "string" },            crm: { type: "string" },            inboxes: { type: "array", items: { type: "string" } },          },          required: ["crm", "inboxes"],        },        undefined,      );      expect(result.inputSchema).toMatchObject({        properties: {          crm: { enum: ["tools.dealcloud.org.main"] },          inboxes: {            items: {              enum: ["tools.gmail.org.work", "tools.gmail.user.personal"],            },          },        },        required: ["crm", "inboxes"],      });    }),  );}); describe("apps source sync", () => {  it.effect("rejects credential-bearing git URLs before storing a source", () =>    Effect.gen(function* () {      const store = makeSyncStore();      const plugin = makeAppsPlugin({ allowPrivateGitHosts: true });      const extension = plugin.extension!({        storage: store,        providers: {          setDefault: () => Effect.succeed("default"),          get: () => Effect.succeed(null),          remove: () => Effect.void,        },      } as never);      const exit = yield* Effect.exit(        extension.createSource({          kind: "git",          slug: "bad",          app: "bad",          url: "https://x:secret@example.test/repo",        }),      );      expect(Exit.isFailure(exit)).toBe(true);      expect(store.sources.size).toBe(0);    }),  );   it.effect("redacts query strings from persisted source diagnostics", () =>    Effect.gen(function* () {      const store = makeSyncStore();      store.sources.set("bad", {        slug: "bad",        app: "bad",        kind: "git",        config: { kind: "git", url: "https://example.test/repo?token=secret" },        status: { type: "pending" },        updatedAt: 1,      });      const plugin = makeAppsPlugin({        executor: makeInProcessAppToolExecutor(),        allowPrivateGitHosts: true,      });      const extension = plugin.extension!({        storage: store,        providers: {          setDefault: () => Effect.succeed("default"),          get: () => Effect.succeed(null),          remove: () => Effect.void,        },      } as never);      const result = yield* extension.syncSource("bad");      expect(result.status).toBe("failed");      const status = store.sources.get("bad")?.status;      const diagnostic = status?.type === "failed" ? status.errors[0]?.diagnostics?.[0]?.path : "";      expect(diagnostic).toBe("https://example.test/repo");    }),  );   it.effect("syncs git sources, no-ops unchanged refs, and republishes changed refs", () =>    Effect.gen(function* () {      const fixture = yield* Effect.promise(() => fixtureFetch());      const originalFetch = globalThis.fetch;      globalThis.fetch = fixture.fetch;      try {        const store = makeSyncStore();        const plugin = makeAppsPlugin({          executor: makeInProcessAppToolExecutor(),          allowPrivateGitHosts: true,        });        let connectionExists = false;        const extension = plugin.extension!({          storage: store,          providers: {            setDefault: () => Effect.succeed("default"),            get: () => Effect.succeed(null),            remove: () => Effect.void,          },          core: {            integrations: {              register: () => Effect.void,            },          },          connections: {            list: () => Effect.succeed([]),            resolveValue: () => Effect.succeed(null),            get: () => Effect.succeed(connectionExists ? ({} as never) : null),            create: () =>              Effect.sync(() => {                connectionExists = true;              }),            refresh: () => Effect.succeed([]),          },        } as never);         yield* extension.createSource({          kind: "git",          slug: "fixture",          app: "fixture",          url: "https://example.test/repo",        });        const first = yield* extension.syncSource("fixture");        expect(first.status).toBe("published");        expect(first.tools).toEqual(["greeter"]);        expect(fixture.packRequests()).toBe(1);         const unchanged = yield* extension.syncSource("fixture");        expect(unchanged.status).toBe("up-to-date");        expect(fixture.packRequests()).toBe(1);         fixture.advance();        const changed = yield* extension.syncSource("fixture");        expect(changed.status).toBe("published");        expect(changed.sourceRef).not.toBe(first.sourceRef);        expect(fixture.packRequests()).toBe(2);        expect(store.descriptor()?.sourceRef).toBe(changed.sourceRef);      } finally {        globalThis.fetch = originalFetch;      }    }),  );}); describe("apps plugin invocation", () => {  it.effect("surfaces uncaught inner tool failures without binding_error", () =>    Effect.gen(function* () {      const bundled = yield* bundle(`        import { z } from "zod";        import { defineTool, integration } from "executor:app";        export default defineTool({          description: "Sync",          integrations: { crm: integration("dealcloud") },          input: z.object({}),          async handler(_input, { crm }) {            await crm.deals.list({});            return { ok: true };          },        });      `);      const plugin = makeAppsPlugin({ executor: makeInProcessAppToolExecutor() });      const result = yield* plugin.invokeTool!({        ctx: invokeCtx({          bundle: bundled.code,          execute: () =>            Effect.succeed(              ToolResult.fail({ code: "upstream_failed", message: "CRM unavailable" }),            ),        }),        toolRow: { name: "sync" },        args: { crm: "tools.dealcloud.org.main" },      } as never);      expect(result).toMatchObject({        ok: false,        error: {          code: "upstream_failed",          message: expect.stringContaining("tools.dealcloud.org.main.deals.list"),        },      });      expect(result).toMatchObject({        error: { message: expect.stringContaining('"CRM unavailable"') },      });      expect((result as { readonly error: { readonly code: string } }).error.code).not.toBe(        "binding_error",      );    }),  );   it.effect("lets handlers catch inner tool failures and return a fallback", () =>    Effect.gen(function* () {      const bundled = yield* bundle(`        import { z } from "zod";        import { defineTool, integration } from "executor:app";        export default defineTool({          description: "Sync",          integrations: { crm: integration("dealcloud") },          input: z.object({}),          async handler(_input, { crm }) {            try {              await crm.deals.list({});              return { fallback: false };            } catch {              return { fallback: true };            }          },        });      `);      const plugin = makeAppsPlugin({ executor: makeInProcessAppToolExecutor() });      const result = yield* plugin.invokeTool!({        ctx: invokeCtx({          bundle: bundled.code,          execute: () =>            Effect.succeed(              ToolResult.fail({ code: "upstream_failed", message: "CRM unavailable" }),            ),        }),        toolRow: { name: "sync" },        args: { crm: "tools.dealcloud.org.main" },      } as never);      expect(result).toEqual({ fallback: true });    }),  );});