Skip to content

Commit 4bd3a06

Browse files
authored
Merge pull request #1 from HusneShabbir/pr/maysunfaisal/3347-dcr-mcp-e2e-tests
test(lightspeed-e2e): add MCP DCR settings coverage
2 parents 5cbf768 + eb5b592 commit 4bd3a06

2 files changed

Lines changed: 316 additions & 0 deletions

File tree

workspaces/lightspeed/e2e-tests/lightspeed.mcp.test.ts

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import {
3838
clickMcpServersStatusColumn,
3939
clickMcpServersNameColumn,
4040
mcpServersTableBodyRows,
41+
mcpEditServerButton,
4142
} from './pages/LightspeedPage';
4243
import { McpConfigureTokenPage } from './pages/McpConfigureTokenPage';
4344
import {
@@ -55,6 +56,16 @@ import {
5556
bootstrapLightspeedE2ePage,
5657
LIGHTSPEED_E2E_DEFAULT_BOT_QUERY,
5758
} from './utils/lightspeedE2eSetup';
59+
import {
60+
DCR_SERVER_NAME,
61+
STATIC_SERVER_NAME,
62+
dcrDisplayModes,
63+
dcrOnlyScenario,
64+
dcrStatusLabel,
65+
expectDcrModalReadOnly,
66+
mixedDcrAndStaticScenario,
67+
openDcrConfigureModal,
68+
} from './utils/mcpDcrSupport';
5869

5970
test.describe('Intelligent assistant MCP', () => {
6071
let translations: LightspeedMessages;
@@ -293,6 +304,182 @@ test.describe('Intelligent assistant MCP', () => {
293304
await mcpToken.closeMcpPanel();
294305
});
295306
});
307+
308+
test.describe('DCR MCP servers', () => {
309+
test('shows Backstage-managed auth status in row', async () => {
310+
await mockMcpServers(sharedPage, dcrOnlyScenario);
311+
await openChatbot(sharedPage, translations);
312+
await openMcpSettingsPanel(sharedPage, translations);
313+
314+
const dcrRow = mcpServerRow(sharedPage, DCR_SERVER_NAME, translations);
315+
await expect(
316+
dcrRow.getByText(
317+
dcrStatusLabel(
318+
translations,
319+
dcrOnlyScenario.servers[0]?.toolCount ?? 0,
320+
),
321+
{ exact: true },
322+
),
323+
).toBeVisible();
324+
await expect(
325+
dcrRow.getByText(translations['mcp.settings.status.tokenRequired'], {
326+
exact: true,
327+
}),
328+
).not.toBeVisible();
329+
330+
await closeMcpSettingsPanel(sharedPage, translations);
331+
});
332+
333+
test('configure modal is read-only across all display modes', async () => {
334+
for (const mode of dcrDisplayModes) {
335+
await sharedPage.goto('/');
336+
await openDcrConfigureModal(sharedPage, translations, mode);
337+
await expectDcrModalReadOnly(sharedPage, translations);
338+
339+
await sharedPage
340+
.getByRole('dialog')
341+
.getByRole('button', {
342+
name: translations['common.cancel'],
343+
exact: true,
344+
})
345+
.click();
346+
await closeMcpSettingsPanel(sharedPage, translations);
347+
}
348+
});
349+
350+
test('mixed servers show managed and token-edit configure behavior', async () => {
351+
await mockMcpServers(sharedPage, mixedDcrAndStaticScenario);
352+
await openChatbot(sharedPage, translations);
353+
await openMcpSettingsPanel(sharedPage, translations);
354+
355+
const dcrRow = mcpServerRow(sharedPage, DCR_SERVER_NAME, translations);
356+
await expect(
357+
dcrRow.getByText(
358+
dcrStatusLabel(
359+
translations,
360+
mixedDcrAndStaticScenario.servers[0]?.toolCount ?? 0,
361+
),
362+
{ exact: true },
363+
),
364+
).toBeVisible();
365+
366+
const staticRow = mcpServerRow(
367+
sharedPage,
368+
STATIC_SERVER_NAME,
369+
translations,
370+
);
371+
await expect(
372+
staticRow.getByText(
373+
translations['mcp.settings.status.tokenRequired'],
374+
{
375+
exact: true,
376+
},
377+
),
378+
).toBeVisible();
379+
380+
await mcpEditServerButton(
381+
sharedPage,
382+
DCR_SERVER_NAME,
383+
translations,
384+
).click();
385+
await expectDcrModalReadOnly(sharedPage, translations);
386+
await sharedPage
387+
.getByRole('dialog')
388+
.getByRole('button', {
389+
name: translations['common.cancel'],
390+
exact: true,
391+
})
392+
.click();
393+
394+
await mcpEditServerButton(
395+
sharedPage,
396+
STATIC_SERVER_NAME,
397+
translations,
398+
).click();
399+
const staticModal = sharedPage.getByRole('dialog');
400+
await expect(staticModal.locator('#mcp-pat-input')).toBeVisible();
401+
await expect(
402+
staticModal.getByRole('button', { name: translations['modal.save'] }),
403+
).toBeVisible();
404+
await staticModal
405+
.getByRole('button', {
406+
name: translations['common.cancel'],
407+
exact: true,
408+
})
409+
.click();
410+
411+
await closeMcpSettingsPanel(sharedPage, translations);
412+
});
413+
414+
test('toggle off/on preserves disabled and managed status', async () => {
415+
await mockMcpServers(sharedPage, dcrOnlyScenario);
416+
await openChatbot(sharedPage, translations);
417+
await openMcpSettingsPanel(sharedPage, translations);
418+
419+
const dcrRow = mcpServerRow(sharedPage, DCR_SERVER_NAME, translations);
420+
await clickMcpServersStatusColumn(sharedPage, translations);
421+
await mcpServerToggle(
422+
sharedPage,
423+
DCR_SERVER_NAME,
424+
translations,
425+
).click();
426+
await expect(
427+
dcrRow.getByText(translations['mcp.settings.status.disabled'], {
428+
exact: true,
429+
}),
430+
).toBeVisible();
431+
432+
await mcpServerToggle(
433+
sharedPage,
434+
DCR_SERVER_NAME,
435+
translations,
436+
).click();
437+
await expect(
438+
dcrRow.getByText(
439+
dcrStatusLabel(
440+
translations,
441+
dcrOnlyScenario.servers[0]?.toolCount ?? 0,
442+
),
443+
{ exact: true },
444+
),
445+
).toBeVisible();
446+
447+
await closeMcpSettingsPanel(sharedPage, translations);
448+
});
449+
450+
test('validate failure surfaces failed status', async () => {
451+
await mockMcpServers(sharedPage, dcrOnlyScenario, {
452+
failServerValidateFor: DCR_SERVER_NAME,
453+
failServerValidateError:
454+
translations['mcp.settings.token.validationFailed'],
455+
});
456+
await openChatbot(sharedPage, translations);
457+
await openMcpSettingsPanel(sharedPage, translations);
458+
459+
const dcrRow = mcpServerRow(sharedPage, DCR_SERVER_NAME, translations);
460+
await expect(
461+
dcrRow.getByText(translations['mcp.settings.status.failed'], {
462+
exact: true,
463+
}),
464+
).toBeVisible();
465+
466+
await mcpEditServerButton(
467+
sharedPage,
468+
DCR_SERVER_NAME,
469+
translations,
470+
).click();
471+
await expectDcrModalReadOnly(sharedPage, translations);
472+
await sharedPage
473+
.getByRole('dialog')
474+
.getByRole('button', {
475+
name: translations['common.cancel'],
476+
exact: true,
477+
})
478+
.click();
479+
480+
await closeMcpSettingsPanel(sharedPage, translations);
481+
});
482+
});
296483
});
297484

298485
test('MCP tool calling renders in UI', async () => {
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/*
2+
* Copyright Red Hat, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { expect, type Page } from '@playwright/test';
18+
import { type McpServersListMock } from '../fixtures/responses';
19+
import {
20+
mcpEditServerButton,
21+
openChatbot,
22+
openMcpSettingsPanel,
23+
selectDisplayMode,
24+
type DisplayMode,
25+
} from '../pages/LightspeedPage';
26+
import { mockMcpServers } from './devMode';
27+
import {
28+
formatMcpToolCountStatus,
29+
type LightspeedMessages,
30+
} from './translations';
31+
32+
type McpServerMockWithAuth = McpServersListMock['servers'][number] & {
33+
auth?: 'dcr';
34+
};
35+
36+
type McpServersListMockWithAuth = {
37+
servers: McpServerMockWithAuth[];
38+
};
39+
40+
const withAuth = (mock: McpServersListMockWithAuth): McpServersListMock =>
41+
mock as unknown as McpServersListMock;
42+
43+
export const DCR_SERVER_NAME = 'mcp-integration-tools';
44+
export const STATIC_SERVER_NAME = 'test-mcp-server';
45+
46+
export const dcrOnlyScenario = withAuth({
47+
servers: [
48+
{
49+
name: DCR_SERVER_NAME,
50+
url: 'http://localhost:7007/api/mcp-actions/v1',
51+
status: 'connected',
52+
toolCount: 5,
53+
enabled: true,
54+
hasToken: true,
55+
hasUserToken: false,
56+
auth: 'dcr',
57+
},
58+
],
59+
});
60+
61+
export const mixedDcrAndStaticScenario = withAuth({
62+
servers: [
63+
{
64+
name: DCR_SERVER_NAME,
65+
url: 'http://localhost:7007/api/mcp-actions/v1',
66+
status: 'connected',
67+
toolCount: 5,
68+
enabled: true,
69+
hasToken: true,
70+
hasUserToken: false,
71+
auth: 'dcr',
72+
},
73+
{
74+
name: STATIC_SERVER_NAME,
75+
url: 'http://localhost:8888/mcp',
76+
status: 'unknown',
77+
toolCount: 0,
78+
enabled: true,
79+
hasToken: false,
80+
hasUserToken: false,
81+
},
82+
],
83+
});
84+
85+
export const dcrDisplayModes: DisplayMode[] = [
86+
'Overlay',
87+
'Dock to window',
88+
'Fullscreen',
89+
];
90+
91+
export function dcrStatusLabel(
92+
t: LightspeedMessages,
93+
toolCount: number,
94+
): string {
95+
const autoManaged = (t as Record<string, string>)[
96+
'mcp.settings.status.autoManaged'
97+
];
98+
return autoManaged ?? formatMcpToolCountStatus(t, toolCount);
99+
}
100+
101+
export async function openDcrConfigureModal(
102+
page: Page,
103+
t: LightspeedMessages,
104+
mode: DisplayMode,
105+
): Promise<void> {
106+
await mockMcpServers(page, dcrOnlyScenario);
107+
await openChatbot(page, t);
108+
if (mode !== 'Overlay') {
109+
await selectDisplayMode(page, t, mode);
110+
}
111+
await openMcpSettingsPanel(page, t);
112+
await mcpEditServerButton(page, DCR_SERVER_NAME, t).click();
113+
}
114+
115+
export async function expectDcrModalReadOnly(
116+
page: Page,
117+
t: LightspeedMessages,
118+
): Promise<void> {
119+
const modal = page.getByRole('dialog');
120+
await expect(
121+
modal.getByText(t['mcp.settings.modalDescriptionDcr'], {
122+
exact: true,
123+
}),
124+
).toBeVisible();
125+
await expect(modal.locator('#mcp-pat-input')).toHaveCount(0);
126+
await expect(
127+
modal.getByRole('button', { name: t['modal.save'] }),
128+
).toHaveCount(0);
129+
}

0 commit comments

Comments
 (0)