Skip to content

Commit 72bf884

Browse files
committed
test: cover bridge listChanged re-enable after manual sync
Add a second test that locks in the suppression-flag clearing contract: a manual sync after disconnect must re-enable listChanged-driven syncs, otherwise the flag silently swallows legitimate catalog-invalidation events from later reconnections.
1 parent fd55726 commit 72bf884

1 file changed

Lines changed: 55 additions & 22 deletions

File tree

src/integrations/xcode-tools-bridge/__tests__/manager.test.ts

Lines changed: 55 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,33 @@
11
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2+
import type { Tool } from '@modelcontextprotocol/sdk/types.js';
23
import { beforeEach, describe, expect, it, vi } from 'vitest';
34

4-
const { registryMocks, buildStatusMock, serviceMocks, onToolCatalogInvalidatedRef } = vi.hoisted(
5-
() => ({
6-
registryMocks: {
7-
clear: vi.fn(),
8-
getRegisteredCount: vi.fn(() => 0),
9-
sync: vi.fn(() => ({ added: 0, updated: 0, removed: 0, total: 0 })),
10-
},
11-
buildStatusMock: vi.fn(),
12-
serviceMocks: {
13-
setWorkflowEnabled: vi.fn(),
14-
disconnect: vi.fn(),
15-
getClientStatus: vi.fn(),
16-
getLastError: vi.fn(),
17-
listTools: vi.fn(),
18-
invokeTool: vi.fn(),
19-
},
20-
onToolCatalogInvalidatedRef: {
21-
current: undefined as (() => void) | undefined,
22-
},
23-
}),
24-
);
5+
const {
6+
registryMocks,
7+
buildStatusMock,
8+
serviceMocks,
9+
onToolCatalogInvalidatedRef,
10+
getMcpBridgeAvailabilityMock,
11+
} = vi.hoisted(() => ({
12+
registryMocks: {
13+
clear: vi.fn(),
14+
getRegisteredCount: vi.fn(() => 0),
15+
sync: vi.fn(() => ({ added: 0, updated: 0, removed: 0, total: 0 })),
16+
},
17+
buildStatusMock: vi.fn(),
18+
serviceMocks: {
19+
setWorkflowEnabled: vi.fn(),
20+
disconnect: vi.fn(),
21+
getClientStatus: vi.fn(),
22+
getLastError: vi.fn(),
23+
listTools: vi.fn(),
24+
invokeTool: vi.fn(),
25+
},
26+
onToolCatalogInvalidatedRef: {
27+
current: undefined as (() => void) | undefined,
28+
},
29+
getMcpBridgeAvailabilityMock: vi.fn(),
30+
}));
2531

2632
vi.mock('../registry.ts', () => ({
2733
XcodeToolsProxyRegistry: vi.fn().mockImplementation(() => registryMocks),
@@ -30,7 +36,7 @@ vi.mock('../registry.ts', () => ({
3036
vi.mock('../core.ts', () => ({
3137
buildXcodeToolsBridgeStatus: buildStatusMock,
3238
classifyBridgeError: vi.fn(() => 'XCODE_MCP_UNAVAILABLE'),
33-
getMcpBridgeAvailability: vi.fn(),
39+
getMcpBridgeAvailability: getMcpBridgeAvailabilityMock,
3440
serializeBridgeTool: vi.fn((tool) => tool),
3541
}));
3642

@@ -83,7 +89,11 @@ describe('XcodeToolsBridgeManager', () => {
8389
serviceMocks.getLastError.mockReset();
8490
serviceMocks.getLastError.mockReturnValue(null);
8591
serviceMocks.listTools.mockReset();
92+
serviceMocks.listTools.mockResolvedValue([]);
8693
serviceMocks.invokeTool.mockReset();
94+
95+
getMcpBridgeAvailabilityMock.mockReset();
96+
getMcpBridgeAvailabilityMock.mockResolvedValue({ available: true, path: '/usr/bin/mcpbridge' });
8797
});
8898

8999
it('does not resync on listChanged while a manual disconnect is in progress', async () => {
@@ -105,4 +115,27 @@ describe('XcodeToolsBridgeManager', () => {
105115
expect(registryMocks.clear).toHaveBeenCalledOnce();
106116
expect(server.sendToolListChanged).toHaveBeenCalledOnce();
107117
});
118+
119+
it('re-enables listChanged-driven syncs after a manual sync follows a disconnect', async () => {
120+
const server = {
121+
sendToolListChanged: vi.fn(),
122+
} as unknown as McpServer;
123+
124+
const tools: Tool[] = [{ name: 'remote.tool', inputSchema: { type: 'object' } } as Tool];
125+
serviceMocks.listTools.mockResolvedValue(tools);
126+
127+
const manager = new XcodeToolsBridgeManager(server);
128+
manager.setWorkflowEnabled(true);
129+
130+
await manager.disconnectTool();
131+
await manager.syncTools({ reason: 'manual' });
132+
133+
const syncSpy = vi.spyOn(manager, 'syncTools');
134+
135+
onToolCatalogInvalidatedRef.current?.();
136+
await Promise.resolve();
137+
await new Promise((resolve) => setTimeout(resolve, 0));
138+
139+
expect(syncSpy).toHaveBeenCalledWith({ reason: 'listChanged' });
140+
});
108141
});

0 commit comments

Comments
 (0)