147 lines
4.6 KiB
TypeScript
147 lines
4.6 KiB
TypeScript
import { beforeEach, describe, expect, test } from "bun:test";
|
|
|
|
import type { DenExternalMcpConnection } from "../src/app/lib/den";
|
|
import {
|
|
clearCloudInventoryCache,
|
|
loadConnectCapabilities,
|
|
loadOrgMcpConnections,
|
|
readCachedOrgMcpConnections,
|
|
type OrgMcpConnectionClient,
|
|
} from "../src/react-app/domains/connections/cloud-inventory-cache";
|
|
import type { ConnectCapabilityClient } from "../src/react-app/domains/session/surface/connect-capability-inventory";
|
|
|
|
const scope = { baseUrl: "https://den.example", organizationId: "org_1" };
|
|
|
|
function connection(id: string): DenExternalMcpConnection {
|
|
return {
|
|
id,
|
|
organizationId: scope.organizationId,
|
|
name: id,
|
|
url: `https://${id}.example/mcp`,
|
|
credentialMode: "per_member",
|
|
connected: true,
|
|
connectedForMe: true,
|
|
status: "active",
|
|
};
|
|
}
|
|
|
|
function countingClient(result: DenExternalMcpConnection[]) {
|
|
let calls = 0;
|
|
const client: OrgMcpConnectionClient = {
|
|
listMcpConnections: async () => {
|
|
calls += 1;
|
|
return result;
|
|
},
|
|
};
|
|
return { client, calls: () => calls };
|
|
}
|
|
|
|
describe("cloud inventory cache", () => {
|
|
beforeEach(() => {
|
|
clearCloudInventoryCache();
|
|
});
|
|
|
|
test("serves a fresh answer without going back to Den", async () => {
|
|
const { client, calls } = countingClient([connection("notion")]);
|
|
|
|
await loadOrgMcpConnections({ client, scope });
|
|
await loadOrgMcpConnections({ client, scope });
|
|
|
|
expect(calls()).toBe(1);
|
|
expect(readCachedOrgMcpConnections(scope)?.map((entry) => entry.id)).toEqual(["notion"]);
|
|
});
|
|
|
|
test("shares one request between callers that ask at the same time", async () => {
|
|
const { client, calls } = countingClient([connection("linear")]);
|
|
|
|
await Promise.all([
|
|
loadOrgMcpConnections({ client, scope }),
|
|
loadOrgMcpConnections({ client, scope }),
|
|
]);
|
|
|
|
expect(calls()).toBe(1);
|
|
});
|
|
|
|
test("refetches when the caller asks for a fresher answer", async () => {
|
|
const { client, calls } = countingClient([connection("notion")]);
|
|
|
|
await loadOrgMcpConnections({ client, scope });
|
|
await loadOrgMcpConnections({ client, scope, maxAgeMs: 0 });
|
|
|
|
expect(calls()).toBe(2);
|
|
});
|
|
|
|
test("keeps nothing for a different organization", async () => {
|
|
const { client } = countingClient([connection("notion")]);
|
|
|
|
await loadOrgMcpConnections({ client, scope });
|
|
|
|
expect(readCachedOrgMcpConnections({ ...scope, organizationId: "org_2" })).toBeNull();
|
|
});
|
|
|
|
test("keeps the last good answer when a refresh fails", async () => {
|
|
const { client } = countingClient([connection("notion")]);
|
|
await loadOrgMcpConnections({ client, scope });
|
|
|
|
const failing: OrgMcpConnectionClient = {
|
|
listMcpConnections: async () => {
|
|
throw new Error("den is down");
|
|
},
|
|
};
|
|
await expect(loadOrgMcpConnections({ client: failing, scope, maxAgeMs: 0 })).rejects.toThrow("den is down");
|
|
|
|
expect(readCachedOrgMcpConnections(scope)?.map((entry) => entry.id)).toEqual(["notion"]);
|
|
});
|
|
|
|
test("does not keep an empty Connect inventory as fresh", async () => {
|
|
let libraryRound = 0;
|
|
const client: ConnectCapabilityClient = {
|
|
listAssignedMarketplaceCapabilities: async () => [],
|
|
listMeLibraryPlugins: async () => {
|
|
libraryRound += 1;
|
|
return libraryRound === 1
|
|
? []
|
|
: [{ id: "plugin_mine", name: "Mine", description: null }];
|
|
},
|
|
listOrgMarketplaces: async () => [],
|
|
getOrgMarketplaceResolved: async () => {
|
|
throw new Error("unused");
|
|
},
|
|
getOrgPluginResolved: async (_organizationId, plugin) => ({
|
|
plugin,
|
|
memberships: [
|
|
{
|
|
id: "membership_1",
|
|
pluginId: plugin.id,
|
|
configObjectId: "skill_1",
|
|
configObject: {
|
|
id: "skill_1",
|
|
objectType: "skill",
|
|
title: "Mine skill",
|
|
description: null,
|
|
currentFileName: "SKILL.md",
|
|
currentFileExtension: "md",
|
|
currentRelativePath: "skills/mine/SKILL.md",
|
|
status: "active",
|
|
updatedAt: null,
|
|
latestVersion: {
|
|
id: "version_1",
|
|
rawSourceText: "# Mine",
|
|
normalizedPayloadJson: null,
|
|
sourceRevisionRef: null,
|
|
createdAt: null,
|
|
},
|
|
},
|
|
},
|
|
],
|
|
}),
|
|
};
|
|
|
|
await loadConnectCapabilities({ client, scope });
|
|
const second = await loadConnectCapabilities({ client, scope });
|
|
|
|
expect(libraryRound).toBe(2);
|
|
expect(second.plugins.map((plugin) => plugin.pluginId)).toEqual(["plugin_mine"]);
|
|
});
|
|
});
|