Rhys Sullivanrework naminge5818557 months agoHistory
446 lines · 12.5 KB · TypeScript
RawDownload
import { join } from "node:path";import {  HttpApi,  HttpApiEndpoint,  HttpApiGroup,  HttpApiSchema,  HttpApiBuilder,  HttpApiClient,  FileSystem,} from "@effect/platform";import {  NodeFileSystem,  NodeHttpServer,} from "@effect/platform-node";import {  describe,  expect,  it,} from "@effect/vitest";import * as Either from "effect/Either";import {  assertInclude,} from "@effect/vitest/utils";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer";import * as Schema from "effect/Schema";import {  makeOpenApiTestServer,} from "@executor/effect-test-utils"; import {  createExecutorBackend,} from "./index";import {  createExecutorEffect,  type ExecutorEffect as Executor,} from "./effect";import {  type LocalInstallation,  ScopeIdSchema,} from "./schema";import {  createLocalExecutorRepositoriesEffect,} from "../../sdk-file/src/effect";import {  createExecutorApiLayer,} from "../../api/src/http";import {  ExecutorApi,} from "../../api/src/index"; type LocalExecutorServices = Effect.Effect.Success<  ReturnType<typeof createLocalExecutorRepositoriesEffect>>; const createClientLayer = (executor: Executor) =>  HttpApiBuilder.serve().pipe(    Layer.provide(createExecutorApiLayer(executor)),    Layer.provideMerge(NodeHttpServer.layerTest),  ); const createExecutorApiClient = () =>  HttpApiClient.make(ExecutorApi, {  }); type ExecutorApiClient = Effect.Effect.Success<  ReturnType<typeof createExecutorApiClient>>; const withExecutorApiClient = <A, E>(  executor: Executor,  f: (client: ExecutorApiClient) => Effect.Effect<A, E, never>,): Effect.Effect<A, E, never> =>  Effect.gen(function* () {    const client = yield* createExecutorApiClient();    return yield* f(client);  }).pipe(Effect.provide(createClientLayer(executor).pipe(Layer.orDie))); const expectLeft = <A, E>(effect: Effect.Effect<A, E, never>) =>  Effect.either(effect).pipe(    Effect.flatMap((result) =>      Either.isLeft(result)        ? Effect.succeed(result.left)        : Effect.die("Expected effect to fail"),    ),  ); type OpenApiSpecServer = {  baseUrl: string;  specUrl: string;  close: () => Promise<void>;}; const ownerParam = HttpApiSchema.param("owner", Schema.String);const repoParam = HttpApiSchema.param("repo", Schema.String);const issueIdParam = HttpApiSchema.param("issueId", Schema.String); class WorkspaceSourceReposApi extends HttpApiGroup.make("repos")  .add(    HttpApiEndpoint.get("getRepo")`/repos/${ownerParam}/${repoParam}`      .addSuccess(Schema.Unknown),  ){} class WorkspaceSourceIssuesApi extends HttpApiGroup.make("issues")  .add(    HttpApiEndpoint.get("getIssue")`/issues/${issueIdParam}`      .addSuccess(Schema.Unknown),  )  .add(    HttpApiEndpoint.get("listIssues")`/issues`      .addSuccess(Schema.Unknown),  ){} class WorkspaceSourceReposRootApi extends HttpApi.make("workspaceSourceRepos")  .add(WorkspaceSourceReposApi){} class WorkspaceSourceIssuesRootApi extends HttpApi.make("workspaceSourceIssues")  .add(WorkspaceSourceIssuesApi){} const workspaceSourceReposLive = HttpApiBuilder.group(  WorkspaceSourceReposRootApi,  "repos",  (handlers) =>    handlers.handle("getRepo", ({ path }) =>      Effect.succeed({        full_name: `${path.owner}/${path.repo}`,        private: false,      }),    ),); const workspaceSourceIssuesLive = HttpApiBuilder.group(  WorkspaceSourceIssuesRootApi,  "issues",  (handlers) =>    handlers      .handle("getIssue", ({ path }) =>        Effect.succeed({          id: path.issueId,          title: `Issue ${path.issueId}`,        }),      )      .handle("listIssues", () =>        Effect.succeed([          {            id: "1",            title: "Issue 1",          },        ]),      ),); const workspaceSourceReposApiLayer = HttpApiBuilder.api(  WorkspaceSourceReposRootApi,).pipe(Layer.provide(workspaceSourceReposLive)); const workspaceSourceIssuesApiLayer = HttpApiBuilder.api(  WorkspaceSourceIssuesRootApi,).pipe(Layer.provide(workspaceSourceIssuesLive)); const createScopedExecutor = (input: {  services: LocalExecutorServices;  installation: LocalInstallation;}) =>  createExecutorEffect({    backend: createExecutorBackend({      loadRepositories: () =>        Effect.succeed({          ...input.services,          scope: {            ...input.services.scope,            actorScopeId: input.installation.actorScopeId,            resolutionScopeIds: input.installation.resolutionScopeIds,          },          installation: {            load: () => Effect.succeed(input.installation),            getOrProvision: () => Effect.succeed(input.installation),          },        }),    }),    getLocalServerBaseUrl: () => "http://127.0.0.1",  }); const makeSharedScopeExecutors = Effect.acquireRelease(  Effect.gen(function* () {    const fs = yield* FileSystem.FileSystem;    const sharedScopeId = ScopeIdSchema.make("org_shared_sdk_pair_test");     const workspaceRootA = yield* fs.makeTempDirectoryScoped({      prefix: "executor-sdk-scope-a-",    });    const workspaceRootB = yield* fs.makeTempDirectoryScoped({      prefix: "executor-sdk-scope-b-",    });     const servicesA = yield* createLocalExecutorRepositoriesEffect({      workspaceRoot: workspaceRootA,      homeConfigPath: join(workspaceRootA, ".executor-home.jsonc"),      homeStateDirectory: join(workspaceRootA, ".executor-home-state"),    });    const servicesB = yield* createLocalExecutorRepositoriesEffect({      workspaceRoot: workspaceRootB,      homeConfigPath: join(workspaceRootB, ".executor-home.jsonc"),      homeStateDirectory: join(workspaceRootB, ".executor-home-state"),    });     const baseInstallationA =      yield* servicesA.installation.getOrProvision();    const baseInstallationB =      yield* servicesB.installation.getOrProvision();     const sharedStorageClose = (() => {      let closed = false;      return async () => {        if (closed) {          return;        }        closed = true;        await servicesA.close?.();        await servicesB.close?.();      };    })();     const sharedStorage = {      close: sharedStorageClose,    };     const installationA: LocalInstallation = {      ...baseInstallationA,      actorScopeId: ScopeIdSchema.make("acc_sdk_executor_a"),      resolutionScopeIds: [        baseInstallationA.scopeId,        sharedScopeId,        ScopeIdSchema.make("acc_sdk_executor_a"),      ],    };    const installationB: LocalInstallation = {      ...baseInstallationB,      actorScopeId: ScopeIdSchema.make("acc_sdk_executor_b"),      resolutionScopeIds: [        baseInstallationB.scopeId,        sharedScopeId,        ScopeIdSchema.make("acc_sdk_executor_b"),      ],    };     const executorA = yield* createScopedExecutor({      services: {        ...servicesA,        ...sharedStorage,      },      installation: installationA,    });    const executorB = yield* createScopedExecutor({      services: {        ...servicesB,        ...sharedStorage,      },      installation: installationB,    });     return {      executorA,      executorB,      sharedScopeId,    };  }).pipe(Effect.provide(NodeFileSystem.layer)),  ({ executorA, executorB }) =>    Effect.promise(async () => {      await executorA.close();      await executorB.close();    }).pipe(Effect.orDie),); describe("executor", () => {  it.scoped(    "keeps workspace sources isolated across executors that share an ancestor scope",    () =>      Effect.gen(function* () {        const { executorA, executorB, sharedScopeId } =          yield* makeSharedScopeExecutors;        const openApiServer: OpenApiSpecServer = yield* makeOpenApiTestServer({          apiLayer: workspaceSourceReposApiLayer,        });         expect(executorA.resolutionScopeIds).toContain(sharedScopeId);        expect(executorB.resolutionScopeIds).toContain(sharedScopeId);         const sourceA = yield* withExecutorApiClient(executorA, (client) =>          client.sources.create({            path: {              workspaceId: executorA.scopeId,            },            payload: {              name: "Workspace A Source",              kind: "openapi",              endpoint: openApiServer.baseUrl,              namespace: "workspace.a",              binding: {                specUrl: openApiServer.specUrl,                defaultHeaders: null,              },              auth: { kind: "none" },            },          }),        );        const sourceB = yield* withExecutorApiClient(executorB, (client) =>          client.sources.create({            path: {              workspaceId: executorB.scopeId,            },            payload: {              name: "Workspace B Source",              kind: "openapi",              endpoint: openApiServer.baseUrl,              namespace: "workspace.b",              binding: {                specUrl: openApiServer.specUrl,                defaultHeaders: null,              },              auth: { kind: "none" },            },          }),        );         const listedA = yield* withExecutorApiClient(executorA, (client) =>          client.sources.list({            path: {              workspaceId: executorA.scopeId,            },          }),        );        const listedB = yield* withExecutorApiClient(executorB, (client) =>          client.sources.list({            path: {              workspaceId: executorB.scopeId,            },          }),        );         expect(listedA.map((source) => source.id)).toEqual([sourceA.id]);        expect(listedB.map((source) => source.id)).toEqual([sourceB.id]);         const lookupFromSibling = yield* expectLeft(          withExecutorApiClient(executorB, (client) =>            client.sources.get({              path: {                workspaceId: executorB.scopeId,                sourceId: sourceA.id,              },            }),          ),        );        assertInclude(String(lookupFromSibling), "Source not found");      }),  );   it.scoped(    "keeps each executor's tool surface tied to its own workspace sources",    () =>      Effect.gen(function* () {        const { executorA, executorB } =          yield* makeSharedScopeExecutors;        const openApiServerA: OpenApiSpecServer = yield* makeOpenApiTestServer({          apiLayer: workspaceSourceReposApiLayer,        });        const openApiServerB: OpenApiSpecServer = yield* makeOpenApiTestServer({          apiLayer: workspaceSourceIssuesApiLayer,        });         const sourceA = yield* withExecutorApiClient(executorA, (client) =>          client.sources.create({            path: {              workspaceId: executorA.scopeId,            },            payload: {              name: "Workspace A Tools",              kind: "openapi",              endpoint: openApiServerA.baseUrl,              namespace: "workspace.a",              binding: {                specUrl: openApiServerA.specUrl,                defaultHeaders: null,              },              auth: { kind: "none" },            },          }),        );        const sourceB = yield* withExecutorApiClient(executorB, (client) =>          client.sources.create({            path: {              workspaceId: executorB.scopeId,            },            payload: {              name: "Workspace B Tools",              kind: "openapi",              endpoint: openApiServerB.baseUrl,              namespace: "workspace.b",              binding: {                specUrl: openApiServerB.specUrl,                defaultHeaders: null,              },              auth: { kind: "none" },            },          }),        );         const inspectionA = yield* withExecutorApiClient(executorA, (client) =>          client.sources.inspection({            path: {              workspaceId: executorA.scopeId,              sourceId: sourceA.id,            },          }),        );        const inspectionB = yield* withExecutorApiClient(executorB, (client) =>          client.sources.inspection({            path: {              workspaceId: executorB.scopeId,              sourceId: sourceB.id,            },          }),        );         expect(inspectionA.toolCount).toBe(1);        expect(inspectionB.toolCount).toBe(2);         const siblingInspection = yield* expectLeft(          withExecutorApiClient(executorA, (client) =>            client.sources.inspection({              path: {                workspaceId: executorA.scopeId,                sourceId: sourceB.id,              },            }),          ),        );        assertInclude(String(siblingInspection), "Source not found");      }),    60_000,  );});