190 lines · 6.7 KB · TypeScript
RawDownload
import { randomBytes } from "node:crypto"; import { expect } from "@effect/vitest";import { Effect } from "effect";import { composePluginApi } from "@executor-js/api/server";import {  MICROSOFT_AUTH_TEMPLATE_SLUG,  MICROSOFT_AUTHORIZATION_URL,  MICROSOFT_TOKEN_URL,  microsoftCatalog,  microsoftGraphAdapter,  microsoftGraphSliceUrl,} from "@executor-js/plugin-openapi/providers/microsoft";import { openApiHttpPlugin } from "@executor-js/plugin-openapi/api";import {  AuthTemplateSlug,  ConnectionName,  IntegrationSlug,  OAuthClientSlug,} from "@executor-js/sdk/shared"; import { scenario } from "../src/scenario";import { Api, Target } from "../src/services"; const api = composePluginApi([  openApiHttpPlugin({ presets: microsoftCatalog, specFormats: [microsoftGraphAdapter] }),] as const); type ToolView = {  readonly name: string;}; const unique = (prefix: string) => `${prefix}_${randomBytes(4).toString("hex")}`;const MICROSOFT_FILES_PRESET_ID = "files"; // Compiling the Graph spec inside dev workerd needs more headroom than GitHub's// 2-core runners have. Local runs and the production Workers streaming path are// unaffected, so CI keeps this narrow catalog scenario quarantined.const CI_GRAPH_SPEC_SKIP = process.env.CI  ? "compiling the full Microsoft Graph spec exhausts the 2-core CI runner and kills the dev stack for the rest of the shard"  : undefined; scenario(  "Microsoft Graph: catalog add stores the OneDrive files service",  { timeout: 180_000, skip: CI_GRAPH_SPEC_SKIP },  Effect.gen(function* () {    const target = yield* Target;    const { client: makeApiClient } = yield* Api;    const identity = yield* target.newIdentity();    const client = yield* makeApiClient(api, identity);     const integration = unique("msgraph_files");    const connection = ConnectionName.make("main");    const oauthClient = OAuthClientSlug.make(unique("msgraph_app"));     yield* Effect.ensuring(      Effect.gen(function* () {        // The add dialog previews before registering. This must stream: the        // whole-document parse of the Graph source OOMs the dev workerd        // isolate the same way it did the production one.        const preview = yield* client.openapi.previewSpec({          payload: {            spec: microsoftGraphSliceUrl(MICROSOFT_FILES_PRESET_ID),            specFormat: "microsoft-graph",          },        });        expect(          preview.operationCount,          "the preview streams the files selection without a whole-document parse",        ).toBeGreaterThan(10);        expect(          preview.healthCheckCandidates.length,          "the preview carries ranked health-check candidates for the selection",        ).toBeGreaterThan(0);         const added = yield* client.openapi.addSpec({          payload: {            spec: {              kind: "url",              url: microsoftGraphSliceUrl(MICROSOFT_FILES_PRESET_ID),            },            slug: integration,            name: "Microsoft Graph Files",            family: "microsoft",            specFormat: "microsoft-graph",          },        });        expect(added.slug, "the Microsoft Graph integration keeps the requested slug").toBe(          integration,        );        expect(          added.toolCount,          "the Microsoft files catalog preset extracts a focused operation subtree",        ).toBeGreaterThan(10);         const config = yield* client.openapi.getConfig({          params: { slug: integration },        });        const delegatedScopes = config?.authenticationTemplate?.flatMap((template) =>          template.slug === MICROSOFT_AUTH_TEMPLATE_SLUG && template.kind === "oauth2"            ? [...template.scopes]            : [],        );        expect(delegatedScopes, "the files delegated OAuth asks for file scopes").toContain(          "Files.ReadWrite.All",        );         yield* client.oauth.createClient({          payload: {            owner: "org",            slug: oauthClient,            authorizationUrl: MICROSOFT_AUTHORIZATION_URL,            tokenUrl: MICROSOFT_TOKEN_URL,            grant: "authorization_code",            clientId: "client-id",            clientSecret: "client-secret",          },        });         const started = yield* client.oauth.start({          payload: {            client: oauthClient,            clientOwner: "org",            owner: "org",            name: ConnectionName.make("oauth"),            integration: IntegrationSlug.make(integration),            template: AuthTemplateSlug.make(MICROSOFT_AUTH_TEMPLATE_SLUG),          },        });        expect(started.status, "authorization-code OAuth returns a browser redirect").toBe(          "redirect",        );        const authorizationUrl = started.status === "redirect" ? started.authorizationUrl : "";        const authorizeUrl = new URL(authorizationUrl || "https://invalid.example");        expect(          authorizeUrl.toString().length,          "Microsoft files OAuth authorize URLs stay under ordinary proxy limits",        ).toBeLessThan(2_000);        expect(          authorizeUrl.searchParams.get("scope"),          "Microsoft files OAuth asks for the focused scope set",        ).toBe(delegatedScopes?.join(" "));         yield* client.connections.create({          payload: {            owner: "org",            name: connection,            integration: IntegrationSlug.make(integration),            template: AuthTemplateSlug.make(MICROSOFT_AUTH_TEMPLATE_SLUG),            value: "token-xyz",          },        });         const tools = yield* client.tools.list({          query: { integration: IntegrationSlug.make(integration), connection },        });        const names = tools.map((tool: ToolView) => tool.name);        const driveTools = names.filter((name) => name.toLowerCase().includes("drive"));        const shareTools = names.filter((name) => name.toLowerCase().includes("share"));        expect(driveTools, "the retrieved catalog includes Microsoft drive operations").not.toEqual(          [],        );        expect(          shareTools,          "the retrieved catalog includes Microsoft sharing operations",        ).not.toEqual([]);      }),      Effect.gen(function* () {        yield* client.connections          .remove({            params: {              owner: "org",              integration: IntegrationSlug.make(integration),              name: connection,            },          })          .pipe(Effect.ignore);        yield* client.openapi          .removeSpec({ params: { slug: IntegrationSlug.make(integration) } })          .pipe(Effect.ignore);        yield* client.oauth          .removeClient({            params: { slug: oauthClient },            payload: { owner: "org" },          })          .pipe(Effect.ignore);      }),    );  }),);