Files
openwork/apps/app/tests/org-mcp-connections.test.ts

184 lines
6.5 KiB
TypeScript

import { describe, expect, test } from "bun:test";
import {
isOrgMcpPollScopeCurrent,
orgMcpConnectFailureMessage,
resolveOrgMcpConnectionCardState,
} from "../src/react-app/domains/connections/use-org-mcp-connections";
describe("resolveOrgMcpConnectionCardState", () => {
test("a shared connection is shown as managed by the org, never actionable by a member", () => {
const state = resolveOrgMcpConnectionCardState({
credentialMode: "shared",
connected: true,
connectedForMe: false,
});
expect(state.connected).toBe(true);
expect(state.actionLabelKey).toBe("mcp.org_connection_managed_label");
expect(state.descriptionKey).toBe("mcp.org_connection_desc_shared");
});
test("an unconnected shared connection still isn't actionable — admin manages it, not the member", () => {
const state = resolveOrgMcpConnectionCardState({
credentialMode: "shared",
connected: false,
connectedForMe: false,
});
expect(state.connected).toBe(false);
expect(state.actionLabelKey).toBe("mcp.org_connection_managed_label");
});
test("a per_member connection the caller has NOT connected yet is actionable", () => {
const state = resolveOrgMcpConnectionCardState({
credentialMode: "per_member",
connected: true, // published/usable at the org level
connectedForMe: false, // but this member hasn't signed in
});
expect(state.connected).toBe(false);
expect(state.actionLabelKey).toBe("mcp.org_connection_connect_action");
expect(state.descriptionKey).toBe("mcp.org_connection_desc_per_member");
});
test("a per_member connection the caller HAS connected shows connected, not managed", () => {
const state = resolveOrgMcpConnectionCardState({
credentialMode: "per_member",
connected: true,
connectedForMe: true,
});
expect(state.connected).toBe(true);
expect(state.actionLabelKey).toBe("mcp.org_connection_connected_label");
expect(state.descriptionKey).toBe("mcp.org_connection_desc_per_member_connected");
});
test("missing features keep a connected per-member account in reconnect state", () => {
const state = resolveOrgMcpConnectionCardState({
credentialMode: "per_member",
connected: true,
connectedForMe: true,
needsReconnect: false,
missingFeatures: ["driveFile"],
});
expect(state.connected).toBe(false);
expect(state.actionLabelKey).toBe("mcp.org_connection_reconnect_action");
});
});
describe("organization MCP OAuth polling", () => {
test("terminal connection failures provide actionable feedback", () => {
expect(orgMcpConnectFailureMessage("missing_context")).toContain("Sign in");
expect(orgMcpConnectFailureMessage("missing_authorization_url")).toContain("OAuth configuration");
expect(orgMcpConnectFailureMessage("timeout")).toContain("provider redirect URI");
});
test("an in-flight response cannot apply org A rows after switching to org B", async () => {
let releaseResponse = () => {};
const responsePending = new Promise<void>((resolve) => {
releaseResponse = resolve;
});
const pollScope = { generation: 4, organizationId: "org_a" };
let currentGeneration = 4;
let activeOrganizationId = "org_a";
let appliedRows: string[] = [];
const response = (async () => {
await responsePending;
if (isOrgMcpPollScopeCurrent(pollScope, currentGeneration, activeOrganizationId)) {
appliedRows = ["org-a-row"];
}
})();
currentGeneration += 1;
activeOrganizationId = "org_b";
releaseResponse();
await response;
expect(appliedRows).toEqual([]);
});
test("requires both the captured generation and organization id", () => {
const scope = { generation: 2, organizationId: "org_a" };
expect(isOrgMcpPollScopeCurrent(scope, 2, "org_a")).toBe(true);
expect(isOrgMcpPollScopeCurrent(scope, 3, "org_a")).toBe(false);
expect(isOrgMcpPollScopeCurrent(scope, 2, "org_b")).toBe(false);
});
test("the already-connected fast path cannot clear a newer org action after its refresh awaits", async () => {
let releaseRefresh = () => {};
let markRefreshStarted = () => {};
const refreshPending = new Promise<void>((resolve) => {
releaseRefresh = resolve;
});
const refreshStarted = new Promise<void>((resolve) => {
markRefreshStarted = resolve;
});
const scope = { generation: 7, organizationId: "org_a" };
let currentGeneration = 7;
let activeOrganizationId = "org_a";
let connectingId: string | null = "org-a-connection";
const connectedFastPath = (async () => {
await Promise.resolve();
if (!isOrgMcpPollScopeCurrent(scope, currentGeneration, activeOrganizationId)) return;
markRefreshStarted();
await refreshPending;
if (!isOrgMcpPollScopeCurrent(scope, currentGeneration, activeOrganizationId)) return;
connectingId = null;
})();
await refreshStarted;
currentGeneration += 1;
activeOrganizationId = "org_b";
connectingId = "org-b-connection";
releaseRefresh();
await connectedFastPath;
expect(connectingId).toBe("org-b-connection");
});
test("disconnect completion and cleanup cannot mutate a newer org after either await", async () => {
let releaseRefresh = () => {};
let markRefreshStarted = () => {};
const refreshPending = new Promise<void>((resolve) => {
releaseRefresh = resolve;
});
const refreshStarted = new Promise<void>((resolve) => {
markRefreshStarted = resolve;
});
const scope = { generation: 11, organizationId: "org_a" };
let currentGeneration = 11;
let activeOrganizationId = "org_a";
let disconnectingId: string | null = "org-a-provider";
let appliedRows: string[] = [];
const disconnect = (async () => {
try {
await Promise.resolve();
if (!isOrgMcpPollScopeCurrent(scope, currentGeneration, activeOrganizationId)) return;
markRefreshStarted();
await refreshPending;
if (!isOrgMcpPollScopeCurrent(scope, currentGeneration, activeOrganizationId)) return;
appliedRows = ["org-a-row"];
} finally {
if (isOrgMcpPollScopeCurrent(scope, currentGeneration, activeOrganizationId)) {
disconnectingId = null;
}
}
})();
await refreshStarted;
currentGeneration += 1;
activeOrganizationId = "org_b";
disconnectingId = "org-b-provider";
releaseRefresh();
await disconnect;
expect(appliedRows).toEqual([]);
expect(disconnectingId).toBe("org-b-provider");
});
});