200 lines · 5.8 KB · TypeScript
RawDownload
import { mkdtempSync } from "node:fs";import { tmpdir } from "node:os";import { join } from "node:path"; import { afterAll, beforeAll, expect, test } from "@effect/vitest";import { connectionIdentifier } from "@executor-js/sdk/shared"; import { mintInviteCode } from "./testing/mint-invite"; // Real Better Auth path with multiple accounts.process.env.EXECUTOR_DATA_DIR = mkdtempSync(join(tmpdir(), "eh-multi-"));process.env.BETTER_AUTH_SECRET = "multi-user-secret-0123456789-abcdefghij-klmn";process.env.EXECUTOR_BOOTSTRAP_ADMIN_EMAIL = "admin@multi.test";process.env.EXECUTOR_BOOTSTRAP_ADMIN_PASSWORD = "admin-pass-123456"; let handler!: (request: Request) => Promise<Response>;let dispose: () => Promise<void> = async () => {}; beforeAll(async () => {  const { makeSelfHostApiHandler } = await import("./app");  const app = await makeSelfHostApiHandler();  handler = app.handler;  dispose = app.dispose;});afterAll(() => dispose()); const BASE = "http://localhost:4788";const connectionName = (name: string): string => String(connectionIdentifier(name)); const TINY_SPEC = JSON.stringify({  openapi: "3.0.0",  info: { title: "Tiny", version: "1.0.0" },  servers: [{ url: "https://httpbin.org" }],  paths: {    "/get": {      get: {        operationId: "httpGet",        summary: "GET",        responses: { "200": { description: "ok" } },      },    },  },}); const signUp = async (email: string): Promise<string> => {  const inviteCode = await mintInviteCode(handler);  const res = await handler(    new Request(`${BASE}/api/auth/sign-up/email`, {      method: "POST",      headers: { "content-type": "application/json" },      body: JSON.stringify({        email,        password: "password-12345678",        name: email,        inviteCode,      }),    }),  );  expect(res.status).toBe(200);  const token = res.headers.get("set-auth-token") ?? "";  expect(token).not.toBe("");  return token;}; const orgIdOf = async (token: string): Promise<string> => {  const res = await handler(    new Request(`${BASE}/api/account/me`, {      headers: { authorization: `Bearer ${token}` },    }),  );  expect(res.status).toBe(200);  const body = (await res.json()) as { organization: { id: string } };  return body.organization.id;}; const addIntegration = (token: string, slug: string) =>  handler(    new Request(`${BASE}/api/openapi/specs`, {      method: "POST",      headers: {        authorization: `Bearer ${token}`,        "content-type": "application/json",      },      body: JSON.stringify({        spec: { kind: "blob", value: TINY_SPEC },        slug,        baseUrl: "",      }),    }),  ); const createConnection = (  token: string,  body: {    owner: "org" | "user";    name: string;    integration: string;    template: string;    value: string;  },) =>  handler(    new Request(`${BASE}/api/connections`, {      method: "POST",      headers: {        authorization: `Bearer ${token}`,        "content-type": "application/json",      },      body: JSON.stringify(body),    }),  ); const connectionAddresses = async (token: string): Promise<string[]> => {  const res = await handler(    new Request(`${BASE}/api/connections`, {      headers: { authorization: `Bearer ${token}` },    }),  );  expect(res.status).toBe(200);  const body = (await res.json()) as ReadonlyArray<{ address: string }>;  return body.map((c) => c.address);}; const runCode = async (token: string, code: string) => {  const res = await handler(    new Request(`${BASE}/api/executions`, {      method: "POST",      headers: {        authorization: `Bearer ${token}`,        "content-type": "application/json",      },      body: JSON.stringify({ code }),    }),  );  return res;}; test("multiple accounts share one org but isolate per-user connections", async () => {  const alice = await signUp("alice@multi.test");  const bob = await signUp("bob@multi.test");   // Same single org for both members.  const aliceOrg = await orgIdOf(alice);  const bobOrg = await orgIdOf(bob);  expect(aliceOrg).toBe(bobOrg);   // The integration is tenant-scoped; register it once.  expect((await addIntegration(alice, "tiny")).status).toBe(200);   // Alice attaches a USER-owned connection (private to her) and an ORG-owned  // connection (shared across the tenant).  expect(    (      await createConnection(alice, {        owner: "user",        name: "alice-private",        integration: "tiny",        template: "bearer",        value: "alice-token",      })    ).status,  ).toBe(200);  expect(    (      await createConnection(alice, {        owner: "org",        name: "team-shared",        integration: "tiny",        template: "bearer",        value: "shared-token",      })    ).status,  ).toBe(200);   // Alice sees both her user connection and the org connection.  const aliceConns = await connectionAddresses(alice);  expect(    aliceConns.some((a) => a.includes("user") && a.includes(connectionName("alice-private"))),  ).toBe(true);  expect(    aliceConns.some((a) => a.includes("org") && a.includes(connectionName("team-shared"))),  ).toBe(true);   // Bob — a different user in the SAME org — sees the org connection but NOT  // Alice's user-owned one.  const bobConns = await connectionAddresses(bob);  expect(bobConns.some((a) => a.includes("org") && a.includes(connectionName("team-shared")))).toBe(    true,  );  expect(bobConns.some((a) => a.includes(connectionName("alice-private")))).toBe(false);}); test("each account can execute code in its own scoped sandbox", async () => {  const carol = await signUp("carol@multi.test");  const res = await runCode(carol, "export default 21 * 2");  expect(res.status).toBe(200);  const body = (await res.json()) as { status: string; text: string };  expect(body.status).toBe("completed");  expect(body.text).toBe("42");});