/* 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; readonly descriptor: () => AppDescriptor | null; } => { const blobs = new Map(); const sources = new Map(); 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; }) => ({ 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 }); }), ); });