268 lines · 9.3 KB · TypeScript
RawDownload
import { describe, expect, it } from "@effect/vitest";import {  AuthTemplateSlug,  ConnectionAddress,  ConnectionName,  IntegrationSlug,  OAuthClientSlug,  ProviderKey,  type Connection,} from "@executor-js/sdk/shared"; import type { ProviderAccount } from "../lib/provider-accounts";import {  activeEnableServiceStep,  applyEnableServiceStepResult,  buildEnableServiceOAuthStartPayload,  continueEnableServiceStep,  createEnableServicesQueue,  EnableServicesModal,  retryEnableServiceStep,  type EnableServiceIntegration,  type EnableServiceQueue,  type EnableServiceStepStatus,} from "./enable-services-modal";import type { OAuthStartPayload } from "../plugins/oauth-sign-in"; const service = (  slug: string,  name: string,  template = `${slug}_oauth`,): EnableServiceIntegration => ({  slug: IntegrationSlug.make(slug),  name,  kind: "google",  authMethods: [    {      id: "oauth",      label: "OAuth",      kind: "oauth",      template,    },  ],}); const gmail = service("google_gmail", "Gmail", "gmail_oauth");const calendar = service("google_calendar", "Google Calendar", "calendar_oauth");const drive = service("google_drive", "Google Drive", "drive_oauth"); const connection = (input?: { readonly oauthClientOwner?: "org" | "user" | null }): Connection => ({  owner: "user",  name: ConnectionName.make("gmail"),  integration: IntegrationSlug.make("google_gmail"),  template: AuthTemplateSlug.make("gmail_oauth"),  provider: ProviderKey.make("default"),  address: ConnectionAddress.make("tools.google_gmail.user.gmail"),  identityLabel: "user@example.com",  description: null,  expiresAt: null,  oauthClient: OAuthClientSlug.make("google-app"),  oauthClientOwner: input && "oauthClientOwner" in input ? input.oauthClientOwner : "org",  oauthScope: null,  lastHealth: null,}); const account = (input?: {  readonly label?: string;  readonly oauthClientOwner?: "org" | "user" | null;}): ProviderAccount<Connection, EnableServiceIntegration> => ({  family: "google",  owner: "user",  identityKey: "user:google:user@example.com",  label: input?.label ?? "user@example.com",  connections: [{ connection: connection(input), integration: gmail }],}); describe("enable services queue", () => {  it("advances on success, keeps the remainder on failure, and retries", () => {    let queue = createEnableServicesQueue([gmail, calendar, drive]);    expect(activeEnableServiceStep(queue)?.integration.slug).toBe(gmail.slug);     queue = applyEnableServiceStepResult(queue, gmail.slug, "done");    expect(activeEnableServiceStep(queue)?.integration.slug).toBe(calendar.slug);     queue = applyEnableServiceStepResult(queue, calendar.slug, "failed");    expect(activeEnableServiceStep(queue)?.integration.slug).toBe(calendar.slug);    expect(queue.steps.map((step) => [String(step.integration.slug), step.status])).toEqual([      ["google_gmail", "done"],      ["google_calendar", "failed"],      ["google_drive", "pending"],    ]);     queue = retryEnableServiceStep(queue, calendar.slug);    expect(activeEnableServiceStep(queue)?.integration.slug).toBe(calendar.slug);    expect(queue.steps[1]?.status).toBe("pending");     queue = applyEnableServiceStepResult(queue, calendar.slug, "done");    expect(activeEnableServiceStep(queue)?.integration.slug).toBe(drive.slug);  });}); describe("buildEnableServiceOAuthStartPayload", () => {  it("builds the startOAuth payload with loginHint, mirrored client, and service template", () => {    const calls: OAuthStartPayload[] = [];    const startOAuth = (payload: OAuthStartPayload) => calls.push(payload);    const payload = buildEnableServiceOAuthStartPayload({      account: account(),      integration: calendar,      organizationId: "org_123",    });     expect(payload).not.toBeNull();    startOAuth(payload!);     expect(calls).toHaveLength(1);    expect(calls[0]).toMatchObject({      client: OAuthClientSlug.make("google-app"),      clientOwner: "org",      owner: "user",      name: ConnectionName.make("userGoogleCalendar"),      integration: IntegrationSlug.make("google_calendar"),      template: AuthTemplateSlug.make("calendar_oauth"),      identityLabel: "user Google Calendar",      loginHint: "user@example.com",    });  });   it("derives distinct names for two same-owner accounts enabling the same service", () => {    const first = buildEnableServiceOAuthStartPayload({      account: account({ label: "rhys@example.com" }),      integration: calendar,      organizationId: "org_123",    });    const second = buildEnableServiceOAuthStartPayload({      account: account({ label: "work@example.com" }),      integration: calendar,      organizationId: "org_123",    });     expect(first?.name).toBe(ConnectionName.make("rhysGoogleCalendar"));    expect(second?.name).toBe(ConnectionName.make("workGoogleCalendar"));    expect(first?.identityLabel).toBe("rhys Google Calendar");    expect(second?.identityLabel).toBe("work Google Calendar");    expect(first?.name).not.toBe(second?.name);  });   it("uses the connection owner when the existing connection has no client owner", () => {    const payload = buildEnableServiceOAuthStartPayload({      account: account({ oauthClientOwner: null }),      integration: drive,      organizationId: "org_123",    });     expect(payload?.clientOwner).toBe("user");  });   it("does not build a payload without an email account label", () => {    expect(      buildEnableServiceOAuthStartPayload({        account: account({ label: "Personal Google" }),        integration: calendar,        organizationId: "org_123",      }),    ).toBeNull();  });}); describe("continueEnableServiceStep", () => {  /** Drives the queue exactly like EnableServicesModalBody: `continueStep`   *  mirrors handleContinue (Continue click), and step completion flows back   *  through markActive, so the opener-call count is observable per click. */  const harness = () => {    let queue: EnableServiceQueue<EnableServiceIntegration> = createEnableServicesQueue([      gmail,      calendar,    ]);    const started: {      readonly payload: OAuthStartPayload;      readonly onSuccess: () => void;      readonly onError: () => void;    }[] = [];    const markActive = (status: EnableServiceStepStatus) => {      const active = activeEnableServiceStep(queue);      if (active) queue = applyEnableServiceStepResult(queue, active.integration.slug, status);    };    const continueStep = () => {      const active = activeEnableServiceStep(queue);      if (!active) return;      continueEnableServiceStep({        account: account(),        integration: active.integration,        organizationId: "org_123",        start: (input) => started.push(input),        markActive,      });    };    return {      started,      continueStep,      queue: () => queue,    };  };   it("opens exactly one popup per Continue click and never auto-chains on success", () => {    const { started, continueStep, queue } = harness();     continueStep();    expect(started).toHaveLength(1);    expect(started[0]?.payload.integration).toBe(gmail.slug);     // The popup completes: the step is marked done and the queue advances,    // but no second popup opens without a fresh Continue click.    started[0]!.onSuccess();    expect(queue().steps[0]?.status).toBe("done");    expect(activeEnableServiceStep(queue())?.integration.slug).toBe(calendar.slug);    expect(started).toHaveLength(1);     continueStep();    expect(started).toHaveLength(2);    expect(started[1]?.payload.integration).toBe(calendar.slug);  });   it("does not auto-retry or auto-advance when a step fails", () => {    const { started, continueStep, queue } = harness();     continueStep();    started[0]!.onError();    expect(queue().steps[0]?.status).toBe("failed");    expect(activeEnableServiceStep(queue())?.integration.slug).toBe(gmail.slug);    expect(started).toHaveLength(1);  });   it("marks the step failed without opening a popup when no payload can be built", () => {    const started: OAuthStartPayload[] = [];    const statuses: EnableServiceStepStatus[] = [];    continueEnableServiceStep({      account: account({ label: "Personal Google" }),      integration: calendar,      organizationId: "org_123",      start: (input) => started.push(input.payload),      markActive: (status) => statuses.push(status),    });    expect(started).toHaveLength(0);    expect(statuses).toEqual(["failed"]);  });}); describe("EnableServicesModal mount boundary", () => {  // The wrapper renders nothing while closed, so the body (and with it the  // useOAuthPopupFlow instance owning the in-flight popup) unmounts on close;  // the hook's unmount effect cancels the session. The wrapper has no hooks,  // so calling it as a plain function is safe here.  const wrapperProps = {    integrations: [gmail, calendar],    onOpenChange: () => {},  };   it("renders nothing when closed, unmounting the body and its OAuth flow", () => {    expect(EnableServicesModal({ ...wrapperProps, open: false, account: account() })).toBeNull();  });   it("renders nothing without an account", () => {    expect(EnableServicesModal({ ...wrapperProps, open: true, account: null })).toBeNull();  });   it("mounts the body only while open with an account", () => {    expect(EnableServicesModal({ ...wrapperProps, open: true, account: account() })).not.toBeNull();  });});