164 lines · 6.5 KB · TypeScript
RawDownload
import { describe, expect, it } from "@effect/vitest";import { Effect, Option, Schema } from "effect";import { FetchHttpClient } from "effect/unstable/http";import { HttpApi, HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi"; import { createExecutor, sha256Hex } from "@executor-js/sdk";import { makeTestConfig, memoryCredentialsPlugin } from "@executor-js/sdk/testing"; import { openApiPlugin } from "./plugin";import { applySpecOverrides, type SpecOverrides } from "./spec-overrides";import { serveMutableOpenApiSpecTestServer } from "../testing"; const testPlugins = () =>  [openApiPlugin({ httpClientLayer: FetchHttpClient.layer }), memoryCredentialsPlugin()] as const; const oauthSpec = {  openapi: "3.1.0",  info: { title: "Scoped API", version: "1.0.0" },  paths: {    "/me": {      get: {        operationId: "getMe",        security: [{ OAuth2: ["current_user:read"] }],        responses: { "200": { description: "ok" } },      },    },  },  components: {    securitySchemes: {      OAuth2: {        type: "oauth2",        flows: {          authorizationCode: {            authorizationUrl: "https://example.com/oauth/authorize",            tokenUrl: "https://example.com/oauth/token",            scopes: {              "current_user:read": "Read the current user",              "files:read": "Read files",              "file_variables:read": "Read variables",            },          },        },      },    },  },  security: [{ OAuth2: ["current_user:read"] }],}; const scopeOverrides: SpecOverrides = [  {    op: "replace",    path: "/components/securitySchemes/OAuth2/flows/authorizationCode/scopes",    value: { "current_user:read": "Read the current user" },  },]; const encodeJsonText = Schema.encodeUnknownSync(Schema.UnknownFromJsonString); describe("OpenAPI spec override lifecycle", () => {  it.effect("applies scope overrides to preview and persists raw and patched hashes", () =>    Effect.scoped(      Effect.gen(function* () {        const executor = yield* createExecutor(makeTestConfig({ plugins: testPlugins() }));        const sourceText = encodeJsonText(oauthSpec);         const preview = yield* executor.openapi.previewSpec({          spec: sourceText,          specOverrides: scopeOverrides,        });        expect(preview.oauth2Presets).toHaveLength(1);        expect(preview.oauth2Presets[0]?.scopes).toEqual({          "current_user:read": "Read the current user",        });         yield* executor.openapi.addSpec({          spec: { kind: "blob", value: sourceText },          slug: "scoped_api",          specOverrides: scopeOverrides,        });         const config = yield* executor.openapi.getConfig("scoped_api");        const patchedDocument = yield* applySpecOverrides(oauthSpec, scopeOverrides);        const patchedText = encodeJsonText(patchedDocument);        expect(config?.specOverrides).toEqual(scopeOverrides);        expect(config?.sourceSpecHash).toBe(yield* sha256Hex(sourceText));        expect(config?.specHash).toBe(yield* sha256Hex(patchedText));        const oauth = config?.authenticationTemplate?.find((method) => method.kind === "oauth2");        expect(oauth).toMatchObject({ kind: "oauth2", scopes: ["current_user:read"] });      }),    ),  );   it.effect("changes and clears overrides for an inline spec without repasting the source", () =>    Effect.scoped(      Effect.gen(function* () {        const executor = yield* createExecutor(makeTestConfig({ plugins: testPlugins() }));        const sourceText = encodeJsonText(oauthSpec);        yield* executor.openapi.addSpec({          spec: { kind: "blob", value: sourceText },          slug: "inline_overrides",          specOverrides: scopeOverrides,        });         const titleOverrides: SpecOverrides = [          { op: "replace", path: "/info/title", value: "Renamed API" },        ];        yield* executor.openapi.updateSpec("inline_overrides", {          specOverrides: titleOverrides,        });        const renamed = yield* executor.openapi.getConfig("inline_overrides");        const renamedDocument = yield* applySpecOverrides(oauthSpec, titleOverrides);        expect(renamed?.specOverrides).toEqual(titleOverrides);        expect(renamed?.specHash).toBe(yield* sha256Hex(encodeJsonText(renamedDocument)));        expect(renamed?.sourceSpecHash).toBe(yield* sha256Hex(sourceText));         yield* executor.openapi.updateSpec("inline_overrides", { specOverrides: [] });        const cleared = yield* executor.openapi.getConfig("inline_overrides");        expect(cleared?.specOverrides).toBeUndefined();        expect(cleared?.sourceSpecHash).toBeUndefined();        expect(cleared?.specHash).toBe(yield* sha256Hex(sourceText));      }),    ),  );   it.effect("reapplies overrides when a URL-hosted spec changes upstream", () =>    Effect.scoped(      Effect.gen(function* () {        const Items = HttpApiGroup.make("items")          .add(HttpApiEndpoint.get("list", "/items", { success: Schema.Array(Schema.String) }))          .add(HttpApiEndpoint.post("create", "/items", { success: Schema.String }));        const InitialApi = HttpApi.make("initial").add(Items);        const server = yield* serveMutableOpenApiSpecTestServer({ initialApi: InitialApi });        const executor = yield* createExecutor(makeTestConfig({ plugins: testPlugins() }));        const removeList: SpecOverrides = [{ op: "remove", path: "/paths/~1items/get" }];         yield* executor.openapi.addSpec({          spec: { kind: "url", url: server.specUrl },          slug: "refresh_overrides",          specOverrides: removeList,        });        const initialPreview = yield* executor.openapi.previewSpec({          spec: server.specUrl,          specOverrides: removeList,        });        expect(initialPreview.operations.map((operation) => operation.operationId)).not.toContain(          "items.list",        );         const Widgets = HttpApiGroup.make("widgets").add(          HttpApiEndpoint.get("list", "/widgets", { success: Schema.Array(Schema.String) }),        );        const EvolvedApi = HttpApi.make("evolved").add(Items).add(Widgets);        yield* server.setApi(EvolvedApi);         const updated = yield* executor.openapi.updateSpec("refresh_overrides");        expect(updated.addedTools).toEqual(["widgets.list"]);        expect(updated.removedTools).toEqual([]);        const config = yield* executor.openapi.getConfig("refresh_overrides");        expect(config?.specOverrides).toEqual(removeList);        expect(Option.isSome(initialPreview.title)).toBe(true);      }),    ),  );});