// Cross-target (browser): OpenAPI security strategies that require multiple// API key headers must collect one credential value per header. Cloudflare's// legacy API key auth is the concrete regression: one OpenAPI security object// requires both `api_email` and `api_key`, so Add connection must show two// credential inputs, not one shared token field.import { randomBytes } from "node:crypto"; import { expect } from "@effect/vitest";import { Effect } from "effect";import { composePluginApi } from "@executor-js/api/server";import { openApiHttpPlugin } from "@executor-js/plugin-openapi/api";import { IntegrationSlug } from "@executor-js/sdk/shared"; import { scenario } from "../src/scenario";import { Api, Browser, Target } from "../src/services";import { visit } from "../src/surfaces/browser"; const api = composePluginApi([openApiHttpPlugin()] as const); const cloudflareStyleSpec = (): string => JSON.stringify({ openapi: "3.0.3", info: { title: "Cloudflare Auth Fixture", version: "1.0.0" }, servers: [{ url: "https://api.cloudflare.test/client/v4" }], security: [{ api_email: [], api_key: [] }], components: { securitySchemes: { api_email: { type: "apiKey", in: "header", name: "X-Auth-Email" }, api_key: { type: "apiKey", in: "header", name: "X-Auth-Key" }, }, }, paths: { "/accounts": { get: { operationId: "listAccounts", summary: "List accounts", responses: { "200": { description: "ok" } }, }, }, }, }); scenario( "OpenAPI · the Cloudflare email and key auth method shows separate credential fields", {}, Effect.gen(function* () { const target = yield* Target; const { client: makeApiClient } = yield* Api; const browser = yield* Browser; const identity = yield* target.newIdentity(); const apiClient = yield* makeApiClient(api, identity); const slug = `openapi_auth_fields_${randomBytes(4).toString("hex")}`; yield* Effect.ensuring( Effect.gen(function* () { yield* apiClient.openapi.addSpec({ payload: { spec: { kind: "blob", value: cloudflareStyleSpec() }, slug, }, }); yield* browser.session(identity, async ({ page, step }) => { await step("Open the Cloudflare-style integration", async () => { await visit(page, `/integrations/${slug}`); await page.getByText("Connections").first().waitFor(); }); await step("Open the add-connection modal", async () => { await page.getByRole("button", { name: "Add connection" }).first().click(); await page.getByRole("dialog", { name: /Add connection/ }).waitFor(); }); await step("The legacy Cloudflare method asks for email and key separately", async () => { await page.getByRole("tab", { name: "API key (X-Auth-Email)" }).click(); const dialog = page.getByRole("dialog", { name: /Add connection/ }); const credentialInputs = dialog.locator('input[type="password"]'); await dialog.getByRole("textbox", { name: "X-Auth-Email" }).waitFor(); await dialog.getByRole("textbox", { name: "X-Auth-Key" }).waitFor(); expect( await credentialInputs.count(), "the modal renders one secret input for each required header", ).toBe(2); expect( await dialog.getByRole("textbox", { name: "X-Auth-Email" }).isVisible(), "email has its own input", ).toBe(true); expect( await dialog.getByRole("textbox", { name: "X-Auth-Key" }).isVisible(), "API key has its own input", ).toBe(true); }); }); }), apiClient.openapi .removeSpec({ params: { slug: IntegrationSlug.make(slug) } }) .pipe(Effect.ignore), ); }),);
// Cross-target (browser): OpenAPI security strategies that require multiple
// API key headers must collect one credential value per header. Cloudflare's
// legacy API key auth is the concrete regression: one OpenAPI security object
// requires both `api_email` and `api_key`, so Add connection must show two
// credential inputs, not one shared token field.
import { randomBytes } from "node:crypto";
import { expect } from "@effect/vitest";
import { Effect } from "effect";
import { composePluginApi } from "@executor-js/api/server";
import { openApiHttpPlugin } from "@executor-js/plugin-openapi/api";
import { IntegrationSlug } from "@executor-js/sdk/shared";
import { scenario } from "../src/scenario";
import { Api, Browser, Target } from "../src/services";
import { visit } from "../src/surfaces/browser";
const api = composePluginApi([openApiHttpPlugin()] as const);
const cloudflareStyleSpec = (): string =>
JSON.stringify({
openapi: "3.0.3",
info: { title: "Cloudflare Auth Fixture", version: "1.0.0" },
servers: [{ url: "https://api.cloudflare.test/client/v4" }],
security: [{ api_email: [], api_key: [] }],
components: {
securitySchemes: {
api_email: { type: "apiKey", in: "header", name: "X-Auth-Email" },
api_key: { type: "apiKey", in: "header", name: "X-Auth-Key" },
},
},
paths: {
"/accounts": {
get: {
operationId: "listAccounts",
summary: "List accounts",
responses: { "200": { description: "ok" } },
},
},
},
});
scenario(
"OpenAPI · the Cloudflare email and key auth method shows separate credential fields",
{},
Effect.gen(function* () {
const target = yield* Target;
const { client: makeApiClient } = yield* Api;
const browser = yield* Browser;
const identity = yield* target.newIdentity();
const apiClient = yield* makeApiClient(api, identity);
const slug = `openapi_auth_fields_${randomBytes(4).toString("hex")}`;
yield* Effect.ensuring(
Effect.gen(function* () {
yield* apiClient.openapi.addSpec({
payload: {
spec: { kind: "blob", value: cloudflareStyleSpec() },
slug,
},
});
yield* browser.session(identity, async ({ page, step }) => {
await step("Open the Cloudflare-style integration", async () => {
await visit(page, `/integrations/${slug}`);
await page.getByText("Connections").first().waitFor();
});
await step("Open the add-connection modal", async () => {
await page.getByRole("button", { name: "Add connection" }).first().click();
await page.getByRole("dialog", { name: /Add connection/ }).waitFor();
});
await step("The legacy Cloudflare method asks for email and key separately", async () => {
await page.getByRole("tab", { name: "API key (X-Auth-Email)" }).click();
const dialog = page.getByRole("dialog", { name: /Add connection/ });
const credentialInputs = dialog.locator('input[type="password"]');
await dialog.getByRole("textbox", { name: "X-Auth-Email" }).waitFor();
await dialog.getByRole("textbox", { name: "X-Auth-Key" }).waitFor();
expect(
await credentialInputs.count(),
"the modal renders one secret input for each required header",
).toBe(2);
expect(
await dialog.getByRole("textbox", { name: "X-Auth-Email" }).isVisible(),
"email has its own input",
).toBe(true);
expect(
await dialog.getByRole("textbox", { name: "X-Auth-Key" }).isVisible(),
"API key has its own input",
).toBe(true);
});
});
}),
apiClient.openapi
.removeSpec({ params: { slug: IntegrationSlug.make(slug) } })
.pipe(Effect.ignore),
);
}),
);