Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 64 additions & 15 deletions apps/cloud/src/auth/v2-moved.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { describe, expect, it } from "@effect/vitest";
import { Effect, Layer } from "effect";

import { v2ImportAnswer, type V2ImportSource } from "./v2-import";
import { v2ImportAnswer, v2McpOrigin, type V2ImportSource } from "./v2-import";
import {
liveV2MovedSource,
parseV2Moved,
Expand All @@ -16,6 +16,7 @@ import { WorkOSError } from "./errors";
const USER = "user_signed_in";
const ORG = "org_example";
const V2_ORIGIN = new URL("https://app.example.test");
const V2_MCP_ORIGIN = new URL("https://mcp.example.test");
const MOVED_AT = "2026-10-09T17:30:00.000Z";

const movedMetadata = (overrides: Record<string, unknown> = {}) => ({
Expand All @@ -26,30 +27,39 @@ const movedMetadata = (overrides: Record<string, unknown> = {}) => ({

describe("v2 moved metadata", () => {
it("reads the v2 slug and move time into the org's v2 page", () => {
expect(parseV2Moved(movedMetadata(), V2_ORIGIN)).toEqual({
expect(parseV2Moved(movedMetadata(), V2_ORIGIN, null)).toEqual({
v2Url: "https://app.example.test/org/example-team",
v2Slug: "example-team",
movedAt: MOVED_AT,
});
});

it("adds v2's OAuth MCP server, not the org-pinned path, when v2's MCP origin is known", () => {
expect(parseV2Moved(movedMetadata(), V2_ORIGIN, V2_MCP_ORIGIN)?.v2McpUrl).toBe(
"https://mcp.example.test/mcp",
);
});

it("ignores unrelated metadata keys", () => {
expect(parseV2Moved(movedMetadata({ plan_note: "anything" }), V2_ORIGIN)?.v2Slug).toBe(
expect(parseV2Moved(movedMetadata({ plan_note: "anything" }), V2_ORIGIN, null)?.v2Slug).toBe(
"example-team",
);
});

it("normalizes a timestamp with an offset to UTC", () => {
expect(
parseV2Moved(movedMetadata({ [V2_MOVED_AT_KEY]: "2026-10-09T10:30:00-07:00" }), V2_ORIGIN)
?.movedAt,
parseV2Moved(
movedMetadata({ [V2_MOVED_AT_KEY]: "2026-10-09T10:30:00-07:00" }),
V2_ORIGIN,
null,
)?.movedAt,
).toBe(MOVED_AT);
});

it("is not moved without both keys", () => {
expect(parseV2Moved({}, V2_ORIGIN)).toBeNull();
expect(parseV2Moved({ [V2_MOVED_SLUG_KEY]: "example-team" }, V2_ORIGIN)).toBeNull();
expect(parseV2Moved({ [V2_MOVED_AT_KEY]: MOVED_AT }, V2_ORIGIN)).toBeNull();
expect(parseV2Moved({}, V2_ORIGIN, null)).toBeNull();
expect(parseV2Moved({ [V2_MOVED_SLUG_KEY]: "example-team" }, V2_ORIGIN, null)).toBeNull();
expect(parseV2Moved({ [V2_MOVED_AT_KEY]: MOVED_AT }, V2_ORIGIN, null)).toBeNull();
});

it("is not moved when the slug is not a v2 slug", () => {
Expand All @@ -68,12 +78,12 @@ describe("v2 moved metadata", () => {
null,
]) {
expect(
parseV2Moved(movedMetadata({ [V2_MOVED_SLUG_KEY]: slug }), V2_ORIGIN),
parseV2Moved(movedMetadata({ [V2_MOVED_SLUG_KEY]: slug }), V2_ORIGIN, null),
String(slug),
).toBeNull();
}
expect(
parseV2Moved(movedMetadata({ [V2_MOVED_SLUG_KEY]: "a".repeat(45) }), V2_ORIGIN),
parseV2Moved(movedMetadata({ [V2_MOVED_SLUG_KEY]: "a".repeat(45) }), V2_ORIGIN, null),
"45 characters is v2's longest slug",
).not.toBeNull();
});
Expand All @@ -90,7 +100,7 @@ describe("v2 moved metadata", () => {
null,
]) {
expect(
parseV2Moved(movedMetadata({ [V2_MOVED_AT_KEY]: movedAt }), V2_ORIGIN),
parseV2Moved(movedMetadata({ [V2_MOVED_AT_KEY]: movedAt }), V2_ORIGIN, null),
String(movedAt),
).toBeNull();
}
Expand Down Expand Up @@ -125,15 +135,15 @@ describe("v2 moved read", () => {
it.effect("reads the selected org's metadata", () =>
Effect.gen(function* () {
const { source, reads } = movedSourceOf(movedMetadata());
expect((yield* v2Moved(source, ORG, V2_ORIGIN))?.v2Slug).toBe("example-team");
expect((yield* v2Moved(source, ORG, V2_ORIGIN, null))?.v2Slug).toBe("example-team");
expect(reads).toEqual([ORG]);
}),
);

it.effect("is not moved when the read fails", () =>
Effect.gen(function* () {
const { source } = movedSourceOf("unavailable");
expect(yield* v2Moved(source, ORG, V2_ORIGIN)).toBeNull();
expect(yield* v2Moved(source, ORG, V2_ORIGIN, null)).toBeNull();
}),
);
});
Expand Down Expand Up @@ -165,7 +175,7 @@ describe("v2 moved live read", () => {

const movedLive = (layer: Layer.Layer<WorkOSClient>) =>
Effect.gen(function* () {
return yield* v2Moved(yield* liveV2MovedSource, ORG, V2_ORIGIN);
return yield* v2Moved(yield* liveV2MovedSource, ORG, V2_ORIGIN, null);
}).pipe(Effect.provide(layer));

it.effect("reads the move from the WorkOS organization's metadata", () =>
Expand Down Expand Up @@ -205,7 +215,10 @@ describe("v2 import answer", () => {
};

const answer = (
env: typeof configured | Record<string, never>,
env:
| typeof configured
| (typeof configured & { V2_MCP_ORIGIN: string })
| Record<string, never>,
moved: ReturnType<typeof movedSourceOf>,
importing: ReturnType<typeof importSourceOf>,
) =>
Expand Down Expand Up @@ -233,6 +246,17 @@ describe("v2 import answer", () => {
}),
);

it.effect("adds v2's MCP server when V2_MCP_ORIGIN is set", () =>
Effect.gen(function* () {
const result = yield* answer(
{ ...configured, V2_MCP_ORIGIN: "https://mcp.example.test" },
movedSourceOf(movedMetadata()),
importSourceOf(true),
);
expect(result.moved?.v2McpUrl).toBe("https://mcp.example.test/mcp");
}),
);

it.effect("offers the import to an eligible org that has not moved", () =>
Effect.gen(function* () {
expect(yield* answer(configured, movedSourceOf({}), importSourceOf(true))).toEqual({
Expand Down Expand Up @@ -271,3 +295,28 @@ describe("v2 import answer", () => {
}),
);
});

describe("v2 MCP origin", () => {
it("reads a bare http(s) origin", () => {
expect(v2McpOrigin({ V2_MCP_ORIGIN: "https://mcp.example.test" })?.href).toBe(
"https://mcp.example.test/",
);
expect(v2McpOrigin({ V2_MCP_ORIGIN: "https://mcp.example.test/" })?.href).toBe(
"https://mcp.example.test/",
);
});

it("is unknown when unset or not a bare origin", () => {
for (const value of [
undefined,
"",
"mcp.example.test",
"/mcp",
"javascript:alert(1)",
"https://mcp.example.test/mcp",
"https://mcp.example.test/?x=1",
]) {
expect(v2McpOrigin({ V2_MCP_ORIGIN: value }), String(value)).toBeNull();
}
});
});
27 changes: 21 additions & 6 deletions apps/cloud/src/edge/production-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Schema } from "effect";
import { unstable_readConfig } from "wrangler";

import { frontResponse } from "./front";
import { v2McpOrigin } from "../auth/v2-import";
import { parseV2Edge, v2EdgeResponse, type V2EdgeEnv, type V2Service } from "./marketing";

// The deployed edge reads its v2 settings from wrangler.jsonc. These tests read
Expand Down Expand Up @@ -93,6 +94,12 @@ describe("production v2 edge settings", () => {
expect(edge.errorTunnelPath).toBe("/api/fd6fab1fbb4883e1/submit");
});

it("points moved orgs' agents at v2's MCP host", () => {
expect(v2McpOrigin({ V2_MCP_ORIGIN: stringVar("V2_MCP_ORIGIN") })?.href).toBe(
"https://mcp.executor.sh/",
);
});

it("keeps the MARKETING binding for v1's terms", () => {
expect(config.services.filter((service) => service.binding === "MARKETING")).toHaveLength(1);
});
Expand All @@ -110,16 +117,24 @@ describe("the executor.sh front Worker's settings", () => {
expect(edgeConfig.placement).toBeUndefined();
});

it("ships the same v2 settings as executor-cloud", () => {
const v2Vars = (vars: Readonly<Record<string, unknown>>) =>
Object.fromEntries(Object.entries(vars).filter(([name]) => name.startsWith("V2_")));
expect(Object.keys(v2Vars(edgeConfig.vars)).toSorted()).toEqual([
// The edge's v2 settings only: `V2_MCP_ORIGIN` is read by v1's dashboard and
// MCP sessions, which the front Worker does not serve.
it("ships the same v2 edge settings as executor-cloud", () => {
const edgeKeys = [
"V2_ANALYTICS_PROXY_PATH",
"V2_ERROR_TUNNEL_PATH",
"V2_OAUTH_STATE_PREFIX",
"V2_SIGN_UP_URL",
]);
expect(v2Vars(edgeConfig.vars)).toEqual(v2Vars(config.vars));
];
const v2Vars = (vars: Readonly<Record<string, unknown>>) =>
Object.fromEntries(Object.entries(vars).filter(([name]) => name.startsWith("V2_")));
expect(Object.keys(v2Vars(edgeConfig.vars)).toSorted()).toEqual(edgeKeys);
expect(v2Vars(edgeConfig.vars)).toEqual(
Object.fromEntries(edgeKeys.map((name) => [name, config.vars[name]])),
);
expect(Object.keys(v2Vars(config.vars)).toSorted()).toEqual(
[...edgeKeys, "V2_MCP_ORIGIN"].toSorted(),
);
});

it("binds the same v2 and marketing Workers as executor-cloud", () => {
Expand Down
125 changes: 125 additions & 0 deletions apps/cloud/src/mcp/v2-moved-instructions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import { describe, expect, it } from "@effect/vitest";
import { Effect, Fiber, Layer } from "effect";
import { TestClock } from "effect/testing";

import { V2_MOVED_AT_KEY, V2_MOVED_SLUG_KEY } from "../auth/v2-moved";
import { WorkOSClient, type WorkOSClientService } from "../auth/workos";
import { WorkOSError } from "../auth/errors";
import {
V2_MOVED_READ_TIMEOUT_MS,
mustReinitializeForV2Move,
v2MovedInstructionsText,
v2MovedMcpInstructions,
} from "./v2-moved-instructions";

const ORG = "org_example";

const env = {
V2: { fetch: () => Promise.resolve(new Response(null)) },
V2_SIGN_UP_URL: "https://app.example.test/login?mode=signup",
V2_OAUTH_STATE_PREFIX: "x2.",
V2_ANALYTICS_PROXY_PATH: "/api/0123456789abcdef",
V2_ERROR_TUNNEL_PATH: "/api/fedcba9876543210/submit",
V2_MCP_ORIGIN: "https://mcp.example.test",
};

const EXPECTED =
"This organization moved to Executor v2 on October 9, 2026. " +
"Its MCP server is now https://mcp.example.test/mcp; ask the user to reconnect their agent there " +
"and choose the example-team organization when signing in. " +
"Changes made through this server won't reach v2.";

const workosAnswering = (answer: () => Effect.Effect<unknown, unknown>) =>
Layer.succeed(
WorkOSClient,
new Proxy({} as WorkOSClientService, {
get: (_target, prop) =>
prop === "getOrganization"
? answer
: () => Effect.die(`unexpected WorkOSClient.${String(prop)} call`),
}),
);

const organization = (metadata: Record<string, string>) => ({ id: ORG, metadata });

const moved = organization({
[V2_MOVED_SLUG_KEY]: "example-team",
[V2_MOVED_AT_KEY]: "2026-10-09T12:00:00.000Z",
});

describe("v2 moved MCP instructions", () => {
it("names the move date and the org's v2 MCP server", () => {
expect(
v2MovedInstructionsText({
v2Url: "https://app.example.test/org/example-team",
v2Slug: "example-team",
movedAt: "2026-10-09T12:00:00.000Z",
v2McpUrl: "https://mcp.example.test/mcp",
}),
).toBe(EXPECTED);
});

it("says nothing without a v2 MCP URL", () => {
expect(
v2MovedInstructionsText({
v2Url: "https://app.example.test/org/example-team",
v2Slug: "example-team",
movedAt: "2026-10-09T12:00:00.000Z",
}),
).toBeNull();
});

it.effect("reads a moved org's instructions from WorkOS", () =>
Effect.gen(function* () {
expect(yield* v2MovedMcpInstructions(env, ORG)).toBe(EXPECTED);
}).pipe(Effect.provide(workosAnswering(() => Effect.succeed(moved)))),
);

it.effect("gives none to an org that has not moved", () =>
Effect.gen(function* () {
expect(yield* v2MovedMcpInstructions(env, ORG)).toBeUndefined();
}).pipe(Effect.provide(workosAnswering(() => Effect.succeed(organization({}))))),
);

it.effect("gives none, without reading WorkOS, when V2_MCP_ORIGIN is unset", () =>
Effect.gen(function* () {
const { V2_MCP_ORIGIN: _unset, ...withoutMcp } = env;
expect(yield* v2MovedMcpInstructions(withoutMcp, ORG)).toBeUndefined();
}).pipe(Effect.provide(workosAnswering(() => Effect.die("WorkOS was read")))),
);

it.effect("gives none when WorkOS fails", () =>
Effect.gen(function* () {
expect(yield* v2MovedMcpInstructions(env, ORG)).toBeUndefined();
}).pipe(Effect.provide(workosAnswering(() => Effect.fail(new WorkOSError({ status: 503 }))))),
);

it.effect("gives up on a slow WorkOS instead of stalling the session", () =>
Effect.gen(function* () {
const read = yield* Effect.forkChild(v2MovedMcpInstructions(env, ORG));
yield* TestClock.adjust(`${V2_MOVED_READ_TIMEOUT_MS} millis`);
expect(yield* Fiber.join(read)).toBeUndefined();
}).pipe(Effect.provide(workosAnswering(() => Effect.never))),
);
});

describe("re-initializing a session for the v2 move", () => {
it("re-initializes a client that got no notice once the org has moved", () => {
expect(mustReinitializeForV2Move(undefined, EXPECTED)).toBe(true);
});

it("re-initializes a client whose notice is out of date", () => {
expect(
mustReinitializeForV2Move(EXPECTED.replace("example-team", "other-team"), EXPECTED),
).toBe(true);
});

it("leaves a client that got the current notice alone", () => {
expect(mustReinitializeForV2Move(EXPECTED, EXPECTED)).toBe(false);
});

it("leaves a client alone when the org has not moved or the read failed", () => {
expect(mustReinitializeForV2Move(undefined, undefined)).toBe(false);
expect(mustReinitializeForV2Move(EXPECTED, undefined)).toBe(false);
});
});
Loading
Loading