540 lines
17 KiB
TypeScript
540 lines
17 KiB
TypeScript
import { afterAll, beforeEach, describe, expect, test } from "bun:test";
|
|
|
|
import type { DenMcpToken, DenSettings } from "../src/app/lib/den";
|
|
import type { OpenworkCloudMcpHealth, OpenworkCloudMcpReconcilePayload } from "../src/app/lib/openwork-server";
|
|
import {
|
|
__setCloudMcpUserStateStorageForTest,
|
|
readCloudMcpSyncMarker,
|
|
writeCloudMcpUserState,
|
|
} from "../src/react-app/domains/connections/cloud-mcp-user-state";
|
|
import { cleanupOpenworkCloudMcpAfterSignOut } from "../src/react-app/domains/connections/cloud-mcp-reconciler";
|
|
import {
|
|
getSessionMcpMaintenanceTargetKey,
|
|
runCloudMcpMaintenanceWithRetry,
|
|
runSessionMcpMaintenanceTask,
|
|
syncCloudControlMcpInBackground,
|
|
} from "../src/react-app/domains/connections/use-session-mcp-maintenance";
|
|
|
|
const NOW = Date.parse("2026-07-09T12:00:00.000Z");
|
|
const WORKSPACE_ID = "workspace_1";
|
|
const SETTINGS: DenSettings = {
|
|
baseUrl: "https://app.openwork.test",
|
|
authToken: "session-token",
|
|
activeOrgId: "organization_1",
|
|
};
|
|
const MINTED: DenMcpToken = {
|
|
token: "mcp-token",
|
|
appHostToken: "app-host-token",
|
|
expiresAt: new Date(NOW + 7 * 24 * 60 * 60 * 1000).toISOString(),
|
|
appHostExpiresAt: new Date(NOW + 7 * 24 * 60 * 60 * 1000).toISOString(),
|
|
organizationId: "organization_1",
|
|
scopes: ["mcp:read", "mcp:write"],
|
|
resource: "https://api.openwork.test/mcp",
|
|
};
|
|
|
|
function cloudHealth(usable: boolean): OpenworkCloudMcpHealth {
|
|
return {
|
|
schemaVersion: 1,
|
|
phase: usable ? "ready" : "missing_desired",
|
|
usable,
|
|
usableByCurrentModel: usable ? true : null,
|
|
connectCatalogEnabled: true,
|
|
workspace: { id: WORKSPACE_ID, type: "local", directory: "/workspace", path: "/workspace" },
|
|
desired: {
|
|
present: usable,
|
|
name: "openwork-cloud",
|
|
revision: usable ? "rev_ready" : null,
|
|
config: null,
|
|
token: { present: usable, metadata: {} },
|
|
},
|
|
delivery: {
|
|
state: usable ? "ready" : "not_desired",
|
|
desiredRevision: usable ? "rev_ready" : null,
|
|
appliedRevision: usable ? "rev_ready" : null,
|
|
updatedAt: usable ? NOW : null,
|
|
appliedAt: usable ? NOW : null,
|
|
lastAttemptAt: usable ? NOW : null,
|
|
},
|
|
engine: { status: usable ? "connected" : "not_checked" },
|
|
tools: {
|
|
expected: ["openwork-cloud_search_capabilities", "openwork-cloud_execute_capability"],
|
|
present: usable ? ["openwork-cloud_search_capabilities", "openwork-cloud_execute_capability"] : [],
|
|
missing: usable ? [] : ["openwork-cloud_search_capabilities", "openwork-cloud_execute_capability"],
|
|
providerProjection: {
|
|
checked: usable,
|
|
provider: "openwork",
|
|
model: "gpt-5",
|
|
present: usable ? ["openwork-cloud_search_capabilities", "openwork-cloud_execute_capability"] : [],
|
|
missing: [],
|
|
},
|
|
},
|
|
pluginCanaries: { expected: [], present: [], missing: [] },
|
|
toolDenies: [],
|
|
firstFailure: usable ? null : {
|
|
code: "cloud_desired_missing",
|
|
stage: "desired",
|
|
retryable: false,
|
|
recommendedAction: "Connect OpenWork Cloud",
|
|
message: "missing",
|
|
},
|
|
checkedAt: new Date(NOW).toISOString(),
|
|
};
|
|
}
|
|
|
|
function retryableCloudHealth(): OpenworkCloudMcpHealth {
|
|
const health = cloudHealth(false);
|
|
return {
|
|
...health,
|
|
firstFailure: health.firstFailure
|
|
? { ...health.firstFailure, retryable: true }
|
|
: null,
|
|
};
|
|
}
|
|
|
|
function missingMcpTokenHealth(): OpenworkCloudMcpHealth {
|
|
const health = cloudHealth(false);
|
|
return {
|
|
...health,
|
|
phase: "auth_expired",
|
|
engine: { status: "connected" },
|
|
firstFailure: {
|
|
code: "missing_mcp_token",
|
|
stage: "transport_auth",
|
|
retryable: false,
|
|
recommendedAction: "Refresh OpenWork Cloud authentication",
|
|
message: "openwork-cloud token is missing.",
|
|
aliases: ["openwork_cloud_auth_required"],
|
|
},
|
|
};
|
|
}
|
|
|
|
function installStorageStub() {
|
|
const values = new Map<string, string>();
|
|
__setCloudMcpUserStateStorageForTest({
|
|
getItem: (key) => values.get(key) ?? null,
|
|
setItem: (key, value) => values.set(key, value),
|
|
removeItem: (key) => values.delete(key),
|
|
});
|
|
}
|
|
|
|
afterAll(() => {
|
|
__setCloudMcpUserStateStorageForTest(null);
|
|
});
|
|
|
|
describe("session MCP maintenance", () => {
|
|
beforeEach(() => installStorageStub());
|
|
|
|
test("mints and hot-updates the Cloud MCP without opening Settings", async () => {
|
|
const writes: Array<{ workspaceId: string; payload: OpenworkCloudMcpReconcilePayload }> = [];
|
|
const client = {
|
|
baseUrl: "https://worker.openwork.test",
|
|
listMcp: async () => ({ items: [] }),
|
|
getOpenworkCloudMcpHealth: async () => cloudHealth(false),
|
|
reconcileOpenworkCloudMcp: async (workspaceId: string, payload: OpenworkCloudMcpReconcilePayload) => {
|
|
writes.push({ workspaceId, payload });
|
|
return cloudHealth(true);
|
|
},
|
|
};
|
|
|
|
await expect(syncCloudControlMcpInBackground({
|
|
client,
|
|
workspaceId: WORKSPACE_ID,
|
|
settings: SETTINGS,
|
|
now: NOW,
|
|
mintToken: async () => MINTED,
|
|
})).resolves.toMatchObject({ outcome: "ready", status: "synced" });
|
|
|
|
expect(writes).toEqual([{
|
|
workspaceId: WORKSPACE_ID,
|
|
payload: {
|
|
workspaceId: WORKSPACE_ID,
|
|
name: "openwork-cloud",
|
|
config: {
|
|
type: "remote",
|
|
enabled: true,
|
|
url: "https://api.openwork.test/mcp/agent",
|
|
headers: { Authorization: "Bearer mcp-token" },
|
|
oauth: false,
|
|
},
|
|
appHostAuthorization: "Bearer app-host-token",
|
|
tokenMetadata: {
|
|
organizationId: "organization_1",
|
|
expiresAt: MINTED.expiresAt,
|
|
resource: "https://api.openwork.test/mcp",
|
|
scopes: "mcp:read mcp:write",
|
|
},
|
|
org: { id: "organization_1", slug: null, name: null },
|
|
connectCatalogEnabled: true,
|
|
trigger: "desktop-background",
|
|
},
|
|
}]);
|
|
expect(readCloudMcpSyncMarker({
|
|
denBaseUrl: SETTINGS.baseUrl,
|
|
serverBaseUrl: client.baseUrl,
|
|
orgId: SETTINGS.activeOrgId ?? "",
|
|
workspaceId: WORKSPACE_ID,
|
|
})).toEqual({
|
|
denBaseUrl: SETTINGS.baseUrl,
|
|
serverBaseUrl: client.baseUrl,
|
|
orgId: "organization_1",
|
|
workspaceId: WORKSPACE_ID,
|
|
expiresAt: MINTED.expiresAt,
|
|
});
|
|
});
|
|
|
|
test("keeps a retryable injection failure visible until a bounded retry restores the tools", async () => {
|
|
let reconcileCount = 0;
|
|
const waits: number[] = [];
|
|
const attempts: Array<{ outcome: string; attempt: number; willRetry: boolean }> = [];
|
|
const client = {
|
|
baseUrl: "https://worker.openwork.test",
|
|
listMcp: async () => ({
|
|
items: [{
|
|
name: "openwork-cloud",
|
|
config: { type: "remote", enabled: true, url: "https://api.openwork.test/mcp/agent" },
|
|
}],
|
|
}),
|
|
getOpenworkCloudMcpHealth: async () => retryableCloudHealth(),
|
|
reconcileOpenworkCloudMcp: async () => {
|
|
reconcileCount += 1;
|
|
return reconcileCount === 3 ? cloudHealth(true) : retryableCloudHealth();
|
|
},
|
|
};
|
|
|
|
const result = await runCloudMcpMaintenanceWithRetry({
|
|
attempt: () => syncCloudControlMcpInBackground({
|
|
client,
|
|
workspaceId: WORKSPACE_ID,
|
|
settings: SETTINGS,
|
|
now: NOW,
|
|
mintToken: async () => MINTED,
|
|
}),
|
|
retryDelaysMs: [25, 50],
|
|
wait: async (delayMs) => {
|
|
waits.push(delayMs);
|
|
},
|
|
onAttempt: (attempt) => {
|
|
attempts.push({
|
|
outcome: attempt.result.outcome,
|
|
attempt: attempt.attempt,
|
|
willRetry: attempt.willRetry,
|
|
});
|
|
},
|
|
});
|
|
|
|
expect(result).toMatchObject({ outcome: "ready", status: "synced" });
|
|
expect(reconcileCount).toBe(3);
|
|
expect(waits).toEqual([25, 50]);
|
|
expect(attempts).toEqual([
|
|
{ outcome: "failed", attempt: 1, willRetry: true },
|
|
{ outcome: "failed", attempt: 2, willRetry: true },
|
|
{ outcome: "ready", attempt: 3, willRetry: false },
|
|
]);
|
|
});
|
|
|
|
test("a fresh per-workspace marker prevents repeated token and config writes", async () => {
|
|
let mintCount = 0;
|
|
let writeCount = 0;
|
|
let healthReady = false;
|
|
const client = {
|
|
baseUrl: "https://worker.openwork.test",
|
|
listMcp: async () => ({
|
|
items: [{
|
|
name: "openwork-cloud",
|
|
config: { type: "remote", enabled: true, url: "https://api.openwork.test/mcp/agent" },
|
|
}],
|
|
}),
|
|
getOpenworkCloudMcpHealth: async () => cloudHealth(healthReady),
|
|
reconcileOpenworkCloudMcp: async () => {
|
|
writeCount += 1;
|
|
healthReady = true;
|
|
return cloudHealth(true);
|
|
},
|
|
};
|
|
|
|
await syncCloudControlMcpInBackground({
|
|
client,
|
|
workspaceId: WORKSPACE_ID,
|
|
settings: SETTINGS,
|
|
now: NOW,
|
|
mintToken: async () => MINTED,
|
|
});
|
|
mintCount = 0;
|
|
writeCount = 0;
|
|
|
|
await expect(syncCloudControlMcpInBackground({
|
|
client,
|
|
workspaceId: WORKSPACE_ID,
|
|
settings: SETTINGS,
|
|
now: NOW + 1_000,
|
|
mintToken: async () => {
|
|
mintCount += 1;
|
|
return MINTED;
|
|
},
|
|
})).resolves.toMatchObject({ outcome: "ready", status: "unchanged" });
|
|
expect(mintCount).toBe(0);
|
|
expect(writeCount).toBe(0);
|
|
});
|
|
|
|
test("direct-probes session maintenance so upgraded desktops silently remint missing MCP bearer failures", async () => {
|
|
let writeCount = 0;
|
|
const probeOptionsSeen: Array<{ probe?: boolean } | undefined> = [];
|
|
const client = {
|
|
baseUrl: "https://worker.openwork.test",
|
|
listMcp: async () => ({
|
|
items: [{
|
|
name: "openwork-cloud",
|
|
config: { type: "remote", enabled: true, url: "https://api.openwork.test/mcp/agent" },
|
|
}],
|
|
}),
|
|
getOpenworkCloudMcpHealth: async (
|
|
_workspaceId: string,
|
|
_providerModel?: unknown,
|
|
options?: { probe?: boolean },
|
|
) => {
|
|
probeOptionsSeen.push(options);
|
|
return options?.probe ? missingMcpTokenHealth() : cloudHealth(true);
|
|
},
|
|
reconcileOpenworkCloudMcp: async () => {
|
|
writeCount += 1;
|
|
return cloudHealth(true);
|
|
},
|
|
};
|
|
|
|
await expect(syncCloudControlMcpInBackground({
|
|
client,
|
|
workspaceId: WORKSPACE_ID,
|
|
settings: SETTINGS,
|
|
now: NOW,
|
|
mintToken: async () => MINTED,
|
|
})).resolves.toMatchObject({ outcome: "ready", status: "synced" });
|
|
|
|
expect(probeOptionsSeen).toEqual([{ probe: true }]);
|
|
expect(writeCount).toBe(1);
|
|
});
|
|
|
|
test("keeps independent markers when switching between workspaces", async () => {
|
|
let mintCount = 0;
|
|
const writes: string[] = [];
|
|
const readyWorkspaces = new Set<string>();
|
|
const client = {
|
|
baseUrl: "https://worker.openwork.test",
|
|
listMcp: async () => ({
|
|
items: [{
|
|
name: "openwork-cloud",
|
|
config: { type: "remote", enabled: true, url: "https://api.openwork.test/mcp/agent" },
|
|
}],
|
|
}),
|
|
addMcp: async (workspaceId: string) => {
|
|
writes.push(workspaceId);
|
|
return { items: [] };
|
|
},
|
|
getOpenworkCloudMcpHealth: async (workspaceId: string) => cloudHealth(readyWorkspaces.has(workspaceId)),
|
|
reconcileOpenworkCloudMcp: async (workspaceId: string) => {
|
|
writes.push(workspaceId);
|
|
readyWorkspaces.add(workspaceId);
|
|
return cloudHealth(true);
|
|
},
|
|
};
|
|
const mintToken = async () => {
|
|
mintCount += 1;
|
|
return MINTED;
|
|
};
|
|
|
|
await expect(syncCloudControlMcpInBackground({
|
|
client,
|
|
workspaceId: "workspace_a",
|
|
settings: SETTINGS,
|
|
now: NOW,
|
|
mintToken,
|
|
})).resolves.toMatchObject({ outcome: "ready", status: "synced" });
|
|
await expect(syncCloudControlMcpInBackground({
|
|
client,
|
|
workspaceId: "workspace_b",
|
|
settings: SETTINGS,
|
|
now: NOW,
|
|
mintToken,
|
|
})).resolves.toMatchObject({ outcome: "ready", status: "synced" });
|
|
await expect(syncCloudControlMcpInBackground({
|
|
client,
|
|
workspaceId: "workspace_a",
|
|
settings: SETTINGS,
|
|
now: NOW + 1_000,
|
|
mintToken,
|
|
})).resolves.toMatchObject({ outcome: "ready", status: "unchanged" });
|
|
|
|
expect(mintCount).toBe(2);
|
|
expect(writes).toEqual(["workspace_a", "workspace_b"]);
|
|
});
|
|
|
|
test("keeps same-named remote workspaces separate across workers", async () => {
|
|
let mintCount = 0;
|
|
const writes: string[] = [];
|
|
const readyWorkers = new Set<string>();
|
|
const makeClient = (baseUrl: string) => ({
|
|
baseUrl,
|
|
listMcp: async () => ({
|
|
items: [{
|
|
name: "openwork-cloud",
|
|
config: { type: "remote", enabled: true, url: "https://api.openwork.test/mcp/agent" },
|
|
}],
|
|
}),
|
|
addMcp: async () => {
|
|
writes.push(baseUrl);
|
|
return { items: [] };
|
|
},
|
|
getOpenworkCloudMcpHealth: async () => cloudHealth(readyWorkers.has(baseUrl)),
|
|
reconcileOpenworkCloudMcp: async () => {
|
|
writes.push(baseUrl);
|
|
readyWorkers.add(baseUrl);
|
|
return cloudHealth(true);
|
|
},
|
|
});
|
|
const workerA = makeClient("https://worker-a.openwork.test");
|
|
const workerB = makeClient("https://worker-b.openwork.test");
|
|
const mintToken = async () => {
|
|
mintCount += 1;
|
|
return MINTED;
|
|
};
|
|
|
|
for (const client of [workerA, workerB, workerA]) {
|
|
await syncCloudControlMcpInBackground({
|
|
client,
|
|
workspaceId: "workspace_shared_id",
|
|
settings: SETTINGS,
|
|
now: NOW,
|
|
mintToken,
|
|
});
|
|
}
|
|
|
|
expect(mintCount).toBe(2);
|
|
expect(writes).toEqual([workerA.baseUrl, workerB.baseUrl]);
|
|
});
|
|
|
|
test("explicit removal keeps background maintenance disabled", async () => {
|
|
writeCloudMcpUserState("removed", {
|
|
denBaseUrl: SETTINGS.baseUrl,
|
|
serverBaseUrl: "https://worker.openwork.test",
|
|
orgId: SETTINGS.activeOrgId ?? "",
|
|
workspaceId: WORKSPACE_ID,
|
|
});
|
|
let reconciled = false;
|
|
let minted = false;
|
|
|
|
await expect(syncCloudControlMcpInBackground({
|
|
client: {
|
|
baseUrl: "https://worker.openwork.test",
|
|
// The engine list is consulted (an existing enabled entry must stay
|
|
// maintained even under recorded intent), but with no entry present
|
|
// the recorded removal keeps provisioning skipped.
|
|
listMcp: async () => ({ items: [] }),
|
|
getOpenworkCloudMcpHealth: async () => cloudHealth(false),
|
|
reconcileOpenworkCloudMcp: async () => {
|
|
reconciled = true;
|
|
return cloudHealth(true);
|
|
},
|
|
},
|
|
workspaceId: WORKSPACE_ID,
|
|
settings: SETTINGS,
|
|
mintToken: async () => {
|
|
minted = true;
|
|
return MINTED;
|
|
},
|
|
})).resolves.toEqual({ outcome: "skipped", status: "skipped", reason: "disabled", health: null });
|
|
expect(reconciled).toBe(false);
|
|
expect(minted).toBe(false);
|
|
});
|
|
|
|
test("pre-signout cleanup removes runtime MCP and disconnects the exact active workspace before resolving", async () => {
|
|
const events: string[] = [];
|
|
await cleanupOpenworkCloudMcpAfterSignOut({
|
|
context: {
|
|
denBaseUrl: SETTINGS.baseUrl,
|
|
serverBaseUrl: "https://worker.openwork.test",
|
|
orgId: SETTINGS.activeOrgId ?? "",
|
|
workspaceId: WORKSPACE_ID,
|
|
},
|
|
openworkClient: {
|
|
baseUrl: "https://worker.openwork.test",
|
|
removeMcp: async (workspaceId, name) => {
|
|
events.push(`remove:${workspaceId}:${name}`);
|
|
},
|
|
},
|
|
opencodeClient: {
|
|
mcp: {
|
|
disconnect: async (input) => {
|
|
events.push(`disconnect:${input.directory}:${input.name}`);
|
|
},
|
|
},
|
|
},
|
|
directory: "/workspace/exact",
|
|
});
|
|
events.push("auth-cleared");
|
|
|
|
expect(events.slice(0, 2).sort()).toEqual([
|
|
"disconnect:/workspace/exact:openwork-cloud",
|
|
`remove:${WORKSPACE_ID}:openwork-cloud`,
|
|
].sort());
|
|
expect(events[2]).toBe("auth-cleared");
|
|
});
|
|
|
|
test("deduplicates the same target without blocking another workspace", async () => {
|
|
const firstClient = { baseUrl: "https://worker.openwork.test" };
|
|
const recreatedClient = { baseUrl: "https://worker.openwork.test/" };
|
|
const targetA = getSessionMcpMaintenanceTargetKey({
|
|
client: firstClient,
|
|
cloudSignedIn: true,
|
|
denBaseUrl: SETTINGS.baseUrl,
|
|
orgId: SETTINGS.activeOrgId,
|
|
workspaceId: "workspace_a",
|
|
});
|
|
const recreatedTargetA = getSessionMcpMaintenanceTargetKey({
|
|
client: recreatedClient,
|
|
cloudSignedIn: true,
|
|
denBaseUrl: SETTINGS.baseUrl,
|
|
orgId: SETTINGS.activeOrgId,
|
|
workspaceId: "workspace_a",
|
|
});
|
|
const targetB = getSessionMcpMaintenanceTargetKey({
|
|
client: recreatedClient,
|
|
cloudSignedIn: true,
|
|
denBaseUrl: SETTINGS.baseUrl,
|
|
orgId: SETTINGS.activeOrgId,
|
|
workspaceId: "workspace_b",
|
|
});
|
|
let releaseTargetA = () => {};
|
|
let targetARuns = 0;
|
|
let targetBRuns = 0;
|
|
const targetABlocked = new Promise<void>((resolve) => {
|
|
releaseTargetA = resolve;
|
|
});
|
|
|
|
const firstTargetA = runSessionMcpMaintenanceTask({
|
|
targetKey: targetA,
|
|
task: async () => {
|
|
targetARuns += 1;
|
|
await targetABlocked;
|
|
},
|
|
});
|
|
await Promise.resolve();
|
|
|
|
await expect(runSessionMcpMaintenanceTask({
|
|
targetKey: recreatedTargetA,
|
|
task: async () => {
|
|
targetARuns += 1;
|
|
},
|
|
})).resolves.toBe(false);
|
|
await expect(runSessionMcpMaintenanceTask({
|
|
targetKey: targetB,
|
|
task: async () => {
|
|
targetBRuns += 1;
|
|
},
|
|
})).resolves.toBe(true);
|
|
|
|
releaseTargetA();
|
|
await expect(firstTargetA).resolves.toBe(true);
|
|
expect(targetARuns).toBe(1);
|
|
expect(targetBRuns).toBe(1);
|
|
});
|
|
});
|