Skip to content

Commit 7eba623

Browse files
committed
test: byte-prove workspace routing; refresh stale e2e; merge duplicate coverage
- New effective_features integration suite: a mapping yields the exact tools a session sees (resolver -> get_tools_for_grants), and an unbound root resolves to zero effective tools. - feature_set_resolver: two sessions on DIFFERENT roots resolve independently; two on the SAME root resolve to the same binding (session-keyed routing, distinguished only by session_id for notification). - Merge the duplicate "unbound root -> Deny" case (kept in the resolver decision-table; the events suite keeps the Deny -> WorkspaceBinding flip). - e2e: rewrite the stale workspaces spec to the current API (feature_set_ids, getDefaultSpace, workspace-entry-* cards) + manual-Apply create + duplicate validation (TC-WS-007); add a Built-in Servers nav smoke (TC-BS-001). Signed-off-by: Mohammod Al Amin Ashik <maa.ashik00@gmail.com>
1 parent 7e7255f commit 7eba623

7 files changed

Lines changed: 386 additions & 61 deletions

File tree

tests/e2e/helpers/tauri-api.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,8 @@ export async function listClients(): Promise<Client[]> {
9696
export interface FeatureSet {
9797
id: string;
9898
name: string;
99-
feature_set_type: 'default' | 'custom';
99+
// 'starter' is the current auto-seeded type; 'default' is the legacy alias.
100+
feature_set_type: 'starter' | 'default' | 'custom';
100101
server_id: string | null;
101102
is_builtin: boolean;
102103
}
@@ -262,15 +263,17 @@ export interface WorkspaceBinding {
262263
id: string;
263264
workspace_root: string;
264265
space_id: string;
265-
feature_set_id: string;
266+
/** A binding can map to one or more FeatureSets (order = render order). */
267+
feature_set_ids: string[];
266268
created_at: string;
267269
updated_at: string;
268270
}
269271

270272
export interface WorkspaceBindingInput {
271273
workspace_root: string;
272274
space_id: string;
273-
feature_set_id: string;
275+
/** Non-empty: at least one FeatureSet id. */
276+
feature_set_ids: string[];
274277
}
275278

276279
export async function listWorkspaceBindings(): Promise<WorkspaceBinding[]> {
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/**
2+
* E2E Tests: Built-in Servers page.
3+
*
4+
* The tab that houses McpMux's own bundled MCP servers. Today it surfaces one
5+
* concrete server — "Tool Optimization" (the mcpmux_* self-management tools) —
6+
* with a master enable switch, moved here out of Settings.
7+
*
8+
* Uses data-testid only (ADR-003).
9+
*/
10+
11+
import { byTestId, safeClick } from '../helpers/selectors';
12+
13+
describe('Built-in Servers - Page shell', () => {
14+
it('TC-BS-001: Navigate to Built-in Servers and see Tool Optimization', async () => {
15+
const nav = await byTestId('nav-builtin-servers');
16+
await safeClick(nav);
17+
await browser.pause(1200);
18+
19+
await browser.saveScreenshot('./tests/e2e/screenshots/bs-01-page.png');
20+
21+
const src = await browser.getPageSource();
22+
expect(src.includes('Built-in Servers')).toBe(true);
23+
expect(src.includes('Tool Optimization')).toBe(true);
24+
25+
// The Tool Optimization server card + its enable switch are present.
26+
const card = await byTestId('builtin-server-tool-optimization');
27+
expect(await card.isDisplayed()).toBe(true);
28+
const toggle = await byTestId('meta-tools-enabled-switch');
29+
expect(await toggle.isDisplayed()).toBe(true);
30+
});
31+
});

tests/e2e/specs/workspaces.wdio.ts

Lines changed: 74 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
/**
22
* E2E Tests: Workspaces page.
33
*
4-
* A WorkspaceBinding maps a normalized filesystem path to a concrete
5-
* (space_id, feature_set_id) pair. Roots are globally unique. These specs
6-
* cover the CRUD path plus the UI shell.
4+
* A WorkspaceBinding ("mapping") maps a normalized filesystem path to one or
5+
* more FeatureSets within a Space. Roots are globally unique. These specs
6+
* cover the CRUD path, the UI card render, the manual-Apply create form, and
7+
* duplicate-folder validation.
78
*
89
* Uses data-testid only (ADR-003).
910
*/
@@ -12,7 +13,7 @@ import { byTestId, safeClick, TIMEOUT } from '../helpers/selectors';
1213
import {
1314
createWorkspaceBinding,
1415
deleteWorkspaceBinding,
15-
getActiveSpace,
16+
getDefaultSpace,
1617
listFeatureSetsBySpace,
1718
listWorkspaceBindings,
1819
type WorkspaceBinding,
@@ -25,10 +26,19 @@ function uniqueRoot(): string {
2526
: `/tmp/mcpmux-e2e-${stamp}`;
2627
}
2728

29+
/** First auto-seeded ("starter"/legacy "default") FS in a space, else any. */
30+
async function pickFeatureSet(spaceId: string): Promise<string> {
31+
const fsList = await listFeatureSetsBySpace(spaceId);
32+
const seed =
33+
fsList.find((fs) => fs.feature_set_type === 'starter' || fs.feature_set_type === 'default') ??
34+
fsList[0];
35+
if (!seed) throw new Error('No FeatureSet in space — cannot set up test');
36+
return seed.id;
37+
}
38+
2839
describe('Workspaces - Page shell', () => {
2940
before(async () => {
30-
// Clean any leftover e2e bindings so the empty-state / populated-state
31-
// assertions are deterministic across reruns.
41+
// Clean any leftover e2e bindings so the state assertions are deterministic.
3242
const existing = await listWorkspaceBindings();
3343
for (const b of existing.filter((x) => x.workspace_root.includes('mcpmux-e2e'))) {
3444
await deleteWorkspaceBinding(b.id);
@@ -57,29 +67,26 @@ describe('Workspaces - Create, render, delete', () => {
5767
const root = uniqueRoot();
5868

5969
before(async () => {
60-
const active = await getActiveSpace();
61-
if (!active) throw new Error('No active space — cannot set up test');
62-
spaceId = active.id;
63-
const fsList = await listFeatureSetsBySpace(spaceId);
64-
const defaultFs = fsList.find((fs) => fs.feature_set_type === 'default');
65-
if (!defaultFs) throw new Error('No Default FS in active space');
66-
featureSetId = defaultFs.id;
70+
const space = await getDefaultSpace();
71+
if (!space) throw new Error('No default space — cannot set up test');
72+
spaceId = space.id;
73+
featureSetId = await pickFeatureSet(spaceId);
6774
});
6875

69-
it('TC-WS-002: Create binding pointing at the active space default FS', async () => {
76+
it('TC-WS-002: Create mapping pointing at the default space FS', async () => {
7077
const created: WorkspaceBinding = await createWorkspaceBinding({
7178
workspace_root: root,
7279
space_id: spaceId,
73-
feature_set_id: featureSetId,
80+
feature_set_ids: [featureSetId],
7481
});
7582
bindingId = created.id;
7683

7784
expect(created.workspace_root.toLowerCase().endsWith(root.toLowerCase())).toBe(true);
7885
expect(created.space_id).toBe(spaceId);
79-
expect(created.feature_set_id).toBe(featureSetId);
86+
expect(created.feature_set_ids).toContain(featureSetId);
8087
});
8188

82-
it('TC-WS-003: Binding row renders on the Workspaces page', async () => {
89+
it('TC-WS-003: Mapping card renders on the Workspaces page', async () => {
8390
const nav = await byTestId('nav-workspaces');
8491
await safeClick(nav);
8592
await browser.pause(1500);
@@ -94,22 +101,21 @@ describe('Workspaces - Create, render, delete', () => {
94101
await browser.saveScreenshot('./tests/e2e/screenshots/ws-02-populated.png');
95102

96103
if (bindingId) {
97-
const row = await $(`[data-testid="workspace-binding-row-${bindingId}"]`);
98-
await row.waitForDisplayed({ timeout: TIMEOUT.short });
99-
expect(await row.isDisplayed()).toBe(true);
104+
const card = await $(`[data-testid="workspace-entry-${bindingId}"]`);
105+
await card.waitForDisplayed({ timeout: TIMEOUT.short });
106+
expect(await card.isDisplayed()).toBe(true);
100107
}
101108
});
102109

103-
it('TC-WS-004: Binding row references the target Space + FS by name', async () => {
110+
it('TC-WS-004: Card references the target Space by name', async () => {
104111
const src = await browser.getPageSource();
105-
// The row's footer shows "Routes to <FS> in <Space>" — check the Space
106-
// name is present. FS is "Default" (builtin) which may also appear in
107-
// unrelated copy, so we only assert on the Space name for stability.
108-
const active = await getActiveSpace();
109-
expect(src.includes(active?.name ?? '__never__')).toBe(true);
112+
// The card footer shows "Serves <FS> from <Space>" — check the Space name
113+
// is present (FS names may collide with unrelated copy).
114+
const space = await getDefaultSpace();
115+
expect(src.includes(space?.name ?? '__never__')).toBe(true);
110116
});
111117

112-
it('TC-WS-005: Delete binding and row disappears', async () => {
118+
it('TC-WS-005: Delete mapping and card disappears', async () => {
113119
if (!bindingId) throw new Error('bindingId missing — TC-WS-002 must succeed first');
114120
await deleteWorkspaceBinding(bindingId);
115121

@@ -120,8 +126,8 @@ describe('Workspaces - Create, render, delete', () => {
120126
await safeClick(nav);
121127
await browser.pause(1500);
122128

123-
const rows = await $$(`[data-testid="workspace-binding-row-${bindingId}"]`);
124-
expect(rows.length).toBe(0);
129+
const cards = await $$(`[data-testid="workspace-entry-${bindingId}"]`);
130+
expect(cards.length).toBe(0);
125131
bindingId = null;
126132
});
127133

@@ -139,7 +145,7 @@ describe('Workspaces - Create, render, delete', () => {
139145
describe('Workspaces - Create form flow (UI)', () => {
140146
let bindingId: string | null = null;
141147

142-
it('TC-WS-006: Create binding through the form and see it listed', async () => {
148+
it('TC-WS-006: Create mapping through the form and see it listed', async () => {
143149
const nav = await byTestId('nav-workspaces');
144150
await safeClick(nav);
145151
await browser.pause(1000);
@@ -151,27 +157,58 @@ describe('Workspaces - Create form flow (UI)', () => {
151157
const rootInput = await byTestId('workspace-binding-root-input');
152158
const root = uniqueRoot();
153159
await rootInput.setValue(root);
160+
// Let the debounced root validation + default FS auto-select settle.
161+
await browser.pause(600);
154162

155-
// `space` and `fs` default to the active space + its Default FS, so we
156-
// can submit without touching the pickers.
163+
// Space defaults to the default space + its starter FS is auto-selected,
164+
// so the explicit Apply ("Create mapping") can be pressed without touching
165+
// the pickers.
157166
const submit = await byTestId('workspace-binding-submit');
167+
await submit.waitForEnabled({ timeout: TIMEOUT.short });
158168
await safeClick(submit);
159169
await browser.pause(800);
160170

161-
const created = (await listWorkspaceBindings()).find(
162-
(b) => b.workspace_root.toLowerCase().endsWith(root.toLowerCase())
171+
const created = (await listWorkspaceBindings()).find((b) =>
172+
b.workspace_root.toLowerCase().endsWith(root.toLowerCase())
163173
);
164174
expect(created).toBeTruthy();
165175
if (created) {
166176
bindingId = created.id;
167-
const row = await $(`[data-testid="workspace-binding-row-${created.id}"]`);
168-
await row.waitForDisplayed({ timeout: TIMEOUT.short });
169-
expect(await row.isDisplayed()).toBe(true);
177+
const card = await $(`[data-testid="workspace-entry-${created.id}"]`);
178+
await card.waitForDisplayed({ timeout: TIMEOUT.short });
179+
expect(await card.isDisplayed()).toBe(true);
170180
}
171181

172182
await browser.saveScreenshot('./tests/e2e/screenshots/ws-04-created-via-form.png');
173183
});
174184

185+
it('TC-WS-007: Mapping an already-mapped folder shows a duplicate error and blocks Apply', async () => {
186+
if (!bindingId) throw new Error('bindingId missing — TC-WS-006 must succeed first');
187+
const existing = (await listWorkspaceBindings()).find((b) => b.id === bindingId);
188+
if (!existing) throw new Error('expected the TC-WS-006 binding to still exist');
189+
190+
const nav = await byTestId('nav-workspaces');
191+
await safeClick(nav);
192+
await browser.pause(800);
193+
194+
const toggle = await byTestId('workspace-binding-create-toggle');
195+
await safeClick(toggle);
196+
await browser.pause(400);
197+
198+
const rootInput = await byTestId('workspace-binding-root-input');
199+
await rootInput.setValue(existing.workspace_root);
200+
await browser.pause(700); // debounced validation + duplicate check
201+
202+
const dupError = await byTestId('workspace-binding-duplicate-error');
203+
await dupError.waitForDisplayed({ timeout: TIMEOUT.short });
204+
expect(await dupError.isDisplayed()).toBe(true);
205+
206+
const submit = await byTestId('workspace-binding-submit');
207+
expect(await submit.isEnabled()).toBe(false);
208+
209+
await browser.saveScreenshot('./tests/e2e/screenshots/ws-05-duplicate.png');
210+
});
211+
175212
after(async () => {
176213
if (bindingId) {
177214
try {

0 commit comments

Comments
 (0)