mirror of
https://github.com/davidkaya/aryx.git
synced 2026-07-25 22:18:40 +02:00
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
279 lines
7.7 KiB
TypeScript
279 lines
7.7 KiB
TypeScript
import { beforeEach, describe, expect, mock, test } from 'bun:test';
|
|
|
|
import type { ProjectRecord } from '@shared/domain/project';
|
|
import { createWorkspaceSeed, type WorkspaceState } from '@shared/domain/workspace';
|
|
|
|
const TIMESTAMP = '2026-03-28T00:00:00.000Z';
|
|
|
|
type MockProbeResult = {
|
|
serverId: string;
|
|
serverName: string;
|
|
tools: Array<{ name: string; description?: string }>;
|
|
status: 'success' | 'failed';
|
|
error?: string;
|
|
};
|
|
|
|
const probeCalls: string[][] = [];
|
|
let probeResults: MockProbeResult[] = [];
|
|
|
|
mock.module('electron', () => {
|
|
const electronMock = {
|
|
app: {
|
|
isPackaged: false,
|
|
getAppPath: () => 'C:\\workspace\\personal\\repositories\\aryx',
|
|
getPath: () => 'C:\\workspace\\personal\\repositories\\aryx\\tests\\fixtures',
|
|
},
|
|
dialog: {
|
|
showOpenDialog: async () => ({ canceled: true, filePaths: [] }),
|
|
},
|
|
shell: {
|
|
openPath: async () => '',
|
|
},
|
|
};
|
|
|
|
return {
|
|
...electronMock,
|
|
default: electronMock,
|
|
};
|
|
});
|
|
|
|
mock.module('keytar', () => ({
|
|
default: {
|
|
getPassword: async () => null,
|
|
setPassword: async () => undefined,
|
|
deletePassword: async () => false,
|
|
},
|
|
}));
|
|
|
|
const { AryxAppService } = await import('@main/AryxAppService');
|
|
|
|
beforeEach(() => {
|
|
probeCalls.length = 0;
|
|
probeResults = [];
|
|
});
|
|
|
|
function createProject(overrides?: Partial<ProjectRecord>): ProjectRecord {
|
|
return {
|
|
id: 'project-alpha',
|
|
name: 'alpha',
|
|
path: 'C:\\workspace\\alpha',
|
|
addedAt: TIMESTAMP,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function cloneWorkspaceState(workspace: WorkspaceState): WorkspaceState {
|
|
return JSON.parse(JSON.stringify(workspace)) as WorkspaceState;
|
|
}
|
|
|
|
function createService(workspace: WorkspaceState): {
|
|
service: InstanceType<typeof AryxAppService>;
|
|
snapshots: WorkspaceState[];
|
|
} {
|
|
const service = new AryxAppService();
|
|
const internals = service as unknown as Record<string, unknown>;
|
|
const snapshots: WorkspaceState[] = [];
|
|
|
|
internals.loadWorkspace = async () => workspace;
|
|
internals.persistAndBroadcast = async (nextWorkspace: WorkspaceState) => {
|
|
snapshots.push(cloneWorkspaceState(nextWorkspace));
|
|
return nextWorkspace;
|
|
};
|
|
internals.probeMcpServers = async (
|
|
servers: Array<{ id: string }>,
|
|
_tokenLookup?: (serverUrl: string) => string | undefined,
|
|
onResult?: (result: MockProbeResult) => void | Promise<void>,
|
|
) => {
|
|
probeCalls.push(servers.map((server) => server.id));
|
|
for (const result of probeResults) {
|
|
await onResult?.(result);
|
|
}
|
|
return probeResults;
|
|
};
|
|
|
|
return { service, snapshots };
|
|
}
|
|
|
|
describe('AryxAppService MCP probing', () => {
|
|
test('probes accepted discovered and manual MCP servers in one batch with incremental workspace updates', async () => {
|
|
const workspace = createWorkspaceSeed();
|
|
const project = createProject({
|
|
discoveredTooling: {
|
|
mcpServers: [
|
|
{
|
|
id: 'project-server',
|
|
name: 'Project MCP',
|
|
transport: 'local',
|
|
command: 'project-mcp',
|
|
args: [],
|
|
tools: [],
|
|
scope: 'project',
|
|
scannerId: 'vscode-mcp',
|
|
sourcePath: 'C:\\workspace\\alpha\\.vscode\\mcp.json',
|
|
sourceLabel: '.vscode\\mcp.json',
|
|
fingerprint: 'project-fingerprint',
|
|
status: 'accepted',
|
|
},
|
|
],
|
|
lastScannedAt: TIMESTAMP,
|
|
},
|
|
});
|
|
|
|
workspace.projects = [project];
|
|
workspace.settings.discoveredUserTooling = {
|
|
mcpServers: [
|
|
{
|
|
id: 'user-server',
|
|
name: 'User MCP',
|
|
transport: 'local',
|
|
command: 'user-mcp',
|
|
args: [],
|
|
tools: [],
|
|
scope: 'user',
|
|
scannerId: 'copilot-user-mcp',
|
|
sourcePath: 'C:\\Users\\tester\\.copilot\\mcp.json',
|
|
sourceLabel: '~\\.copilot\\mcp.json',
|
|
fingerprint: 'user-fingerprint',
|
|
status: 'accepted',
|
|
},
|
|
],
|
|
lastScannedAt: TIMESTAMP,
|
|
};
|
|
workspace.settings.tooling.mcpServers = [
|
|
{
|
|
id: 'manual-server',
|
|
name: 'Manual MCP',
|
|
transport: 'local',
|
|
command: 'manual-mcp',
|
|
args: [],
|
|
tools: [],
|
|
createdAt: TIMESTAMP,
|
|
updatedAt: TIMESTAMP,
|
|
},
|
|
];
|
|
|
|
probeResults = [
|
|
{
|
|
serverId: 'project-server',
|
|
serverName: 'Project MCP',
|
|
status: 'success',
|
|
tools: [{ name: 'project.status' }],
|
|
},
|
|
{
|
|
serverId: 'manual-server',
|
|
serverName: 'Manual MCP',
|
|
status: 'success',
|
|
tools: [{ name: 'manual.status' }],
|
|
},
|
|
{
|
|
serverId: 'user-server',
|
|
serverName: 'User MCP',
|
|
status: 'failed',
|
|
tools: [],
|
|
error: 'boom',
|
|
},
|
|
];
|
|
|
|
const { service, snapshots } = createService(workspace);
|
|
|
|
await (
|
|
service as unknown as {
|
|
probeAllAcceptedMcpServers: (nextWorkspace: WorkspaceState) => Promise<void>;
|
|
}
|
|
).probeAllAcceptedMcpServers(workspace);
|
|
|
|
expect(probeCalls).toEqual([[
|
|
'user-server',
|
|
'project-server',
|
|
'manual-server',
|
|
]]);
|
|
expect(snapshots.map((snapshot) => snapshot.mcpProbingServerIds)).toEqual([
|
|
['user-server', 'project-server', 'manual-server'],
|
|
['user-server', 'manual-server'],
|
|
['user-server'],
|
|
undefined,
|
|
]);
|
|
expect(workspace.projects[0]?.discoveredTooling?.mcpServers[0]?.probedTools).toEqual([
|
|
{ name: 'project.status' },
|
|
]);
|
|
expect(workspace.settings.tooling.mcpServers[0]?.probedTools).toEqual([
|
|
{ name: 'manual.status' },
|
|
]);
|
|
expect(workspace.settings.discoveredUserTooling.mcpServers[0]?.probedTools).toBeUndefined();
|
|
});
|
|
|
|
test('tracks probing progress while re-probing matching remote MCP servers after OAuth', async () => {
|
|
const workspace = createWorkspaceSeed();
|
|
workspace.settings.discoveredUserTooling = {
|
|
mcpServers: [
|
|
{
|
|
id: 'discovered-remote',
|
|
name: 'Discovered Remote',
|
|
transport: 'http',
|
|
url: 'https://example.com/mcp',
|
|
headers: { 'X-Test': '1' },
|
|
tools: [],
|
|
scope: 'user',
|
|
scannerId: 'copilot-user-mcp',
|
|
sourcePath: 'C:\\Users\\tester\\.copilot\\mcp.json',
|
|
sourceLabel: '~\\.copilot\\mcp.json',
|
|
fingerprint: 'remote-fingerprint',
|
|
status: 'accepted',
|
|
},
|
|
],
|
|
lastScannedAt: TIMESTAMP,
|
|
};
|
|
workspace.settings.tooling.mcpServers = [
|
|
{
|
|
id: 'manual-remote',
|
|
name: 'Manual Remote',
|
|
transport: 'http',
|
|
url: 'https://example.com/mcp',
|
|
headers: { Authorization: 'Bearer token' },
|
|
tools: [],
|
|
createdAt: TIMESTAMP,
|
|
updatedAt: TIMESTAMP,
|
|
},
|
|
];
|
|
|
|
probeResults = [
|
|
{
|
|
serverId: 'manual-remote',
|
|
serverName: 'Manual Remote',
|
|
status: 'success',
|
|
tools: [{ name: 'manual.remote' }],
|
|
},
|
|
{
|
|
serverId: 'discovered-remote',
|
|
serverName: 'Discovered Remote',
|
|
status: 'success',
|
|
tools: [{ name: 'discovered.remote' }],
|
|
},
|
|
];
|
|
|
|
const { service, snapshots } = createService(workspace);
|
|
|
|
await (
|
|
service as unknown as {
|
|
reprobeServerByUrl: (serverUrl: string) => Promise<void>;
|
|
}
|
|
).reprobeServerByUrl('https://example.com/mcp');
|
|
|
|
expect(probeCalls).toEqual([[
|
|
'manual-remote',
|
|
'discovered-remote',
|
|
]]);
|
|
expect(snapshots.map((snapshot) => snapshot.mcpProbingServerIds)).toEqual([
|
|
['manual-remote', 'discovered-remote'],
|
|
['discovered-remote'],
|
|
undefined,
|
|
]);
|
|
expect(workspace.settings.tooling.mcpServers[0]?.probedTools).toEqual([
|
|
{ name: 'manual.remote' },
|
|
]);
|
|
expect(workspace.settings.discoveredUserTooling.mcpServers[0]?.probedTools).toEqual([
|
|
{ name: 'discovered.remote' },
|
|
]);
|
|
});
|
|
});
|