mirror of
https://github.com/Kong/insomnia.git
synced 2026-11-04 20:13:48 -05:00
refactor: Introduce McpPrimitiveType and McpListAction enums for better type safety
- Added McpPrimitiveType enum to categorize sidebar row types. - Introduced McpListAction enum to standardize action names in capability menus. - Updated references in tests and flows to use new enums instead of string literals for improved maintainability and clarity. - Refactored related tests to ensure compatibility with the new enum structure.
This commit is contained in:
20 files changed
+110
-78
No files matched your search
@@ -17,7 +17,7 @@ Every test injects only the `user` fixture (`async ({ user }) => {...}`) — nev
|
||||
| WebSocket request | `webSocketRequestFlow` | `flows/web-socket-request.flow.ts` | `pages/web-socket-request.page.ts` | `models/websocket-request.ts` |
|
||||
| Socket.IO request | `socketIoRequestFlow` | `flows/socket-io-request.flow.ts` | `pages/socket-io-request.page.ts` | `models/socket-io-request.ts` |
|
||||
| Event Stream (SSE) request | `eventStreamRequestFlow` | `flows/event-stream-request.flow.ts` | `pages/event-stream-request.page.ts` | `models/event-stream-request.ts` |
|
||||
| MCP client | `mcpClientFlow` | `flows/mcp-client.flow.ts` | `pages/mcp-client.page.ts` | `models/mcp-client.ts` |
|
||||
| MCP client | `mcpClientFlow` | `flows/mcp-client.flow.ts` | `pages/mcp-client.page.ts` | `models/mcp-client.ts`; `enums/mcp-capability.ts` (`McpPrimitiveType`, `McpListAction`) |
|
||||
| Mock Server (self-hosted only, `tests/mock-server/`) | `mockServerFlow` | `flows/mock-server.flow.ts` | `pages/mock-server.page.ts` | `models/mock-server.ts` (`MockServer`, positional ctor), `models/mock-route.ts` (`MockRoute` literal, `MockRouteHistoryEntry`); `enums/mock-body-type.ts` (`MockBodyType`) |
|
||||
| Environment | `environmentFlow` | `flows/environment.flow.ts` | `pages/environment.page.ts` | `models/environment.ts` |
|
||||
| Cookie | `cookieFlow` | `flows/cookie.flow.ts` | `pages/cookie.page.ts` | `models/cookie.ts` |
|
||||
@@ -557,11 +557,11 @@ getListError(client: McpClient, type: McpCapabilityType): Promise<McpCapabilityE
|
||||
toggleCapability(client: McpClient, type: McpCapabilityType): Promise<void> // collapse/expand the capability item
|
||||
refresh(client: McpClient, type: McpCapabilityType): Promise<void> // actions menu -> Refresh (re-runs the list request, re-derives entries + error)
|
||||
loadMore(client: McpClient, type: McpCapabilityType): Promise<void> // actions menu -> Load More (only offered while the last page carried a nextCursor)
|
||||
getListAction(client: McpClient, type: McpCapabilityType): Promise<string> // "Refresh" or "Load More" — what the actions menu currently offers
|
||||
getListAction(client: McpClient, type: McpCapabilityType): Promise<McpListAction> // McpListAction.Refresh or .LoadMore — what the actions menu currently offers
|
||||
filterCapabilities(client: McpClient, text: string): Promise<void> // opens the client, types into the sidebar capability filter (narrows primitives by name) — wraps McpClientPage.setCapabilityFilter
|
||||
```
|
||||
|
||||
`McpClientPage` (`pageManager.mcpClientPage`) additionally exposes plain-value readers for the same sidebar, none of which have a Flow wrapper: `getPrimitives(type: McpPrimitiveType): Promise<string[]>` (row names of one kind; `McpPrimitiveType` adds `"resourceTemplates"`; **does not wait**, so an empty list is a valid answer — poll it with `expect.poll`, unlike `getTools()` which waits for >0), `getCapabilityErrorCount(type): Promise<number>` (0 or 1 — use `expect.poll(...).toBe(0|1)` for "panel hidden/shown"), `hasCapability(type): Promise<boolean>`, `hasCaret(type): Promise<boolean>` (no caret on an empty capability item), `getCapabilityBounds(type)` / `getCapabilityErrorBounds(type)` (`{ y, height }`, for "panel does not overlap the next row"), `isCapabilityErrorScrollable(type): Promise<boolean>`.
|
||||
`McpClientPage` (`pageManager.mcpClientPage`) additionally exposes plain-value readers for the same sidebar, none of which have a Flow wrapper: `getPrimitives(type: McpPrimitiveType): Promise<string[]>` (row names of one kind; `McpPrimitiveType` (`enums/mcp-capability.ts`) adds `ResourceTemplates`; every `type` argument here and in the Flow methods above takes the enum (`McpPrimitiveType.Tools`), never a `"tools"` string literal, and list-action assertions use `McpListAction.LoadMore`/`.Refresh`; **does not wait**, so an empty list is a valid answer — poll it with `expect.poll`, unlike `getTools()` which waits for >0), `getCapabilityErrorCount(type): Promise<number>` (0 or 1 — use `expect.poll(...).toBe(0|1)` for "panel hidden/shown"), `hasCapability(type): Promise<boolean>`, `hasCaret(type): Promise<boolean>` (no caret on an empty capability item), `getCapabilityBounds(type)` / `getCapabilityErrorBounds(type)` (`{ y, height }`, for "panel does not overlap the next row"), `isCapabilityErrorScrollable(type): Promise<boolean>`.
|
||||
|
||||
**Scripting what the MCP server answers (`misc/mcp-list-server.ts`).** `misc/mcp-server.ts`'s regular `/mcp` endpoint only serves valid tools. For malformed / partial / error / paginated list responses, every test gets its own endpoint: `const url = await scriptMcpListServer(key, config)` (key unique per test, e.g. `faker.string.alphanumeric(12)`; returns the URL to give `new McpClient(name, url)`). `config` maps a list method (`"tools/list" | "resources/list" | "resources/templates/list" | "prompts/list"`) to `{ result: <any JSON, sent verbatim> }`, `{ error: { code, message } }` (a JSON-RPC error), or `{ pages: { "": <first page>, "<cursor>": <page for params.cursor> } }`. Methods left out answer with a valid empty list. `config` also takes the flag `serverRequestReusingToolsListId: true`, which makes the `tools/list` reply an SSE stream: the result, then a server-initiated `roots/list` request reusing the `tools/list` request's JSON-RPC id (for testing id-collision handling). The `McpListMethod` type names the four list methods. Requests without an `id` **or without a `method`** get a bare 202. Call `scriptMcpListServer` again with the **same key** to switch behavior mid-test (e.g. between two Refresh clicks). Real examples: `tests/mcp-client/error-response/` (one spec per INS-3647 test-plan case; `helpers.ts` there holds `connectTo(user, config)` = script + create client + connect, plus `validTool` / `wholeFailureTitle` / `skippedTitle`).
|
||||
|
||||
@@ -1579,6 +1579,8 @@ ContextMenuItem: Collection |
|
||||
CollaboratorRole: Owner | Admin | Member; // enums/collaborator-role.ts — InvitePage's per-row role-menu selector, OrganizationFlow.invite()'s Collaborator.role field
|
||||
DeleteMode: Local | Full; // enums/delete-mode.ts
|
||||
SyncStatus: Synced | Unsynced; // enums/sync-status.ts
|
||||
McpPrimitiveType: Tools | Resources | Prompts | ResourceTemplates; // enums/mcp-capability.ts — values "tools"/"resources"/"prompts"/"resourceTemplates"; `McpCapabilityType` (same file, a type alias) = every member except ResourceTemplates
|
||||
McpListAction: Refresh | LoadMore; // enums/mcp-capability.ts — "Refresh" / "Load More", the list action a capability item's menu offers
|
||||
ExportFormat: InsomniaV5 | Har; // enums/export-format.ts
|
||||
ApiSpecExportFormat: Yaml | Json; // enums/spec-format.ts or export-format.ts — see ExportPage
|
||||
ImportSource: File | Url | Curl | Clipboard | Mcp;
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
/** The primitive kinds a sidebar row can belong to (the prefix of its `data-key`). */
|
||||
export enum McpPrimitiveType {
|
||||
Tools = "tools",
|
||||
Resources = "resources",
|
||||
Prompts = "prompts",
|
||||
ResourceTemplates = "resourceTemplates",
|
||||
}
|
||||
|
||||
/** The capability items of the MCP sidebar; Resource Templates are listed under `resources`. */
|
||||
export type McpCapabilityType = Exclude<McpPrimitiveType, McpPrimitiveType.ResourceTemplates>;
|
||||
|
||||
/** The list action a capability item's menu offers. */
|
||||
export enum McpListAction {
|
||||
Refresh = "Refresh",
|
||||
LoadMore = "Load More",
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
import { expect } from "@playwright/test";
|
||||
|
||||
import { type McpCapabilityType, McpListAction } from "../enums/mcp-capability";
|
||||
import { DEFAULT_TIMEOUT } from "../misc/constants";
|
||||
import { McpClient } from "../models/mcp-client";
|
||||
import type { Project } from "../models/project";
|
||||
import type { ResponseView } from "../models/response";
|
||||
import type { OAuth2Tokens } from "../pages/auth-tab.page";
|
||||
import type { McpCapabilityError, McpCapabilityType } from "../pages/mcp-client.page";
|
||||
import type { McpCapabilityError } from "../pages/mcp-client.page";
|
||||
import type { PageManager } from "../pages/page-manager";
|
||||
import { BaseFlow } from "./base.flow";
|
||||
import type { FlowContext } from "./flow-context";
|
||||
@@ -223,7 +224,7 @@ export class McpClientFlow extends BaseFlow {
|
||||
*/
|
||||
async refresh(client: McpClient, type: McpCapabilityType): Promise<void> {
|
||||
await this.open(client);
|
||||
await this.pageManager.mcpClientPage.clickCapabilityAction(type, "Refresh");
|
||||
await this.pageManager.mcpClientPage.clickCapabilityAction(type, McpListAction.Refresh);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -235,7 +236,7 @@ export class McpClientFlow extends BaseFlow {
|
||||
*/
|
||||
async loadMore(client: McpClient, type: McpCapabilityType): Promise<void> {
|
||||
await this.open(client);
|
||||
await this.pageManager.mcpClientPage.clickCapabilityAction(type, "Load More");
|
||||
await this.pageManager.mcpClientPage.clickCapabilityAction(type, McpListAction.LoadMore);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -245,7 +246,7 @@ export class McpClientFlow extends BaseFlow {
|
||||
* @param type - Which capability item's actions menu to read
|
||||
* @returns `Refresh`, or `Load More` while a `nextCursor` is pending
|
||||
*/
|
||||
async getListAction(client: McpClient, type: McpCapabilityType): Promise<string> {
|
||||
async getListAction(client: McpClient, type: McpCapabilityType): Promise<McpListAction> {
|
||||
await this.open(client);
|
||||
return this.pageManager.mcpClientPage.getCapabilityActionName(type);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import { expect, type Locator } from '@playwright/test';
|
||||
|
||||
import type { McpListAction} from '../enums/mcp-capability';
|
||||
import { type McpCapabilityType, type McpPrimitiveType } from '../enums/mcp-capability';
|
||||
import { ACTION_TIMEOUT, DEFAULT_TIMEOUT } from '../misc/constants';
|
||||
import { RequestPage } from './request.page';
|
||||
|
||||
/** The capability items of the MCP sidebar; Resource Templates are listed under `resources`. */
|
||||
export type McpCapabilityType = 'tools' | 'resources' | 'prompts';
|
||||
/** The primitive kinds a sidebar row can belong to (the prefix of its `data-key`). */
|
||||
export type McpPrimitiveType = McpCapabilityType | 'resourceTemplates';
|
||||
|
||||
/** The error panel embedded in a capability item's row. */
|
||||
export interface McpCapabilityError {
|
||||
title: string;
|
||||
@@ -245,7 +242,7 @@ export class McpClientPage extends RequestPage {
|
||||
* @param type - Which capability item's menu to open
|
||||
* @param action - `Refresh`, or `Load More` once the server returned a `nextCursor`
|
||||
*/
|
||||
async clickCapabilityAction(type: McpCapabilityType, action: 'Refresh' | 'Load More'): Promise<void> {
|
||||
async clickCapabilityAction(type: McpCapabilityType, action: McpListAction): Promise<void> {
|
||||
const capabilityItem = this.getCapabilityItem(type);
|
||||
await capabilityItem.hover({ position: { x: 60, y: 10 } });
|
||||
await capabilityItem.locator(`[data-testid="Dropdown-${type}"]`).click();
|
||||
@@ -258,12 +255,12 @@ export class McpClientPage extends RequestPage {
|
||||
* Opens the menu to read it and closes it again.
|
||||
* @param type - Which capability item's menu to read
|
||||
*/
|
||||
async getCapabilityActionName(type: McpCapabilityType): Promise<string> {
|
||||
async getCapabilityActionName(type: McpCapabilityType): Promise<McpListAction> {
|
||||
const capabilityItem = this.getCapabilityItem(type);
|
||||
await capabilityItem.hover({ position: { x: 60, y: 10 } });
|
||||
await capabilityItem.locator(`[data-testid="Dropdown-${type}"]`).click();
|
||||
const item = this.page.locator(this.ACTIONS_MENU).locator('[role^="menuitem"]').first();
|
||||
const name = (await item.innerText()).trim();
|
||||
const name = (await item.innerText()).trim() as McpListAction;
|
||||
await this.page.keyboard.press('Escape');
|
||||
await expect(item).toBeHidden();
|
||||
return name;
|
||||
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
|
||||
import { McpPrimitiveType } from '../../../enums/mcp-capability';
|
||||
import { ProjectType } from '../../../enums/project-types';
|
||||
import { expect, test } from '../../../misc/fixtures';
|
||||
import { scriptMcpListServer } from '../../../misc/mcp-list-server';
|
||||
@@ -19,6 +20,6 @@ test('Verify Invalid Entries With the Same Name and Issue Are All Listed', async
|
||||
const client = await mcpClientFlow.create(project, new McpClient(faker.string.alphanumeric(10), url));
|
||||
await mcpClientFlow.connect(client);
|
||||
|
||||
const error = await mcpClientFlow.getListError(client, 'tools');
|
||||
const error = await mcpClientFlow.getListError(client, McpPrimitiveType.Tools);
|
||||
expect(error.entries.map(entry => entry.label)).toEqual(['dup', 'dup']);
|
||||
});
|
||||
+6
-5
@@ -1,5 +1,6 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
|
||||
import { McpPrimitiveType } from '../../../enums/mcp-capability';
|
||||
import { ProjectType } from '../../../enums/project-types';
|
||||
import { expect, test } from '../../../misc/fixtures';
|
||||
import { scriptMcpListServer } from '../../../misc/mcp-list-server';
|
||||
@@ -20,7 +21,7 @@ test('Verify an Entry Without a Usable Name Is Labeled by Its Position and Shows
|
||||
const client = await mcpClientFlow.create(project, new McpClient(faker.string.alphanumeric(10), url));
|
||||
await mcpClientFlow.connect(client);
|
||||
|
||||
const error = await mcpClientFlow.getListError(client, 'tools');
|
||||
const error = await mcpClientFlow.getListError(client, McpPrimitiveType.Tools);
|
||||
expect(error.entries.map(entry => entry.label)).toEqual(['index 1', 'index 2', 'index 3', 'index 4']);
|
||||
expect(error.entries[3].reason).toMatch(/\(And \d+ more\)$/);
|
||||
});
|
||||
@@ -37,11 +38,11 @@ test('Verify a Long Error Panel Scrolls Inside Its Own Area Without Overlapping
|
||||
const client = await mcpClientFlow.create(project, new McpClient(faker.string.alphanumeric(10), url));
|
||||
await mcpClientFlow.connect(client);
|
||||
|
||||
const error = await mcpClientFlow.getListError(client, 'tools');
|
||||
const error = await mcpClientFlow.getListError(client, McpPrimitiveType.Tools);
|
||||
expect(error.entries).toHaveLength(12);
|
||||
expect(await mcpClientPage.isCapabilityErrorScrollable('tools')).toBe(true);
|
||||
expect(await mcpClientPage.isCapabilityErrorScrollable(McpPrimitiveType.Tools)).toBe(true);
|
||||
|
||||
const panelBox = await mcpClientPage.getCapabilityErrorBounds('tools');
|
||||
const nextCapabilityBox = await mcpClientPage.getCapabilityBounds('prompts');
|
||||
const panelBox = await mcpClientPage.getCapabilityErrorBounds(McpPrimitiveType.Tools);
|
||||
const nextCapabilityBox = await mcpClientPage.getCapabilityBounds(McpPrimitiveType.Prompts);
|
||||
expect(panelBox.y + panelBox.height).toBeLessThanOrEqual(nextCapabilityBox.y + 1);
|
||||
});
|
||||
+13
-12
@@ -1,5 +1,6 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
|
||||
import { McpPrimitiveType } from '../../../enums/mcp-capability';
|
||||
import { ProjectType } from '../../../enums/project-types';
|
||||
import { expect, test } from '../../../misc/fixtures';
|
||||
import { scriptMcpListServer } from '../../../misc/mcp-list-server';
|
||||
@@ -19,18 +20,18 @@ test('Verify the Error Panel of a Partially Invalid List Follows Expand and Coll
|
||||
const project = await workspaceFlow.create(new Project(faker.string.alphanumeric(10), ProjectType.Local));
|
||||
const client = await mcpClientFlow.create(project, new McpClient(faker.string.alphanumeric(10), url));
|
||||
await mcpClientFlow.connect(client);
|
||||
await mcpClientFlow.getListError(client, 'tools');
|
||||
await expect.poll(() => mcpClientPage.hasCapability('prompts')).toBe(true);
|
||||
await mcpClientFlow.getListError(client, McpPrimitiveType.Tools);
|
||||
await expect.poll(() => mcpClientPage.hasCapability(McpPrimitiveType.Prompts)).toBe(true);
|
||||
|
||||
const panelBox = await mcpClientPage.getCapabilityErrorBounds('tools');
|
||||
const nextCapabilityBox = await mcpClientPage.getCapabilityBounds('prompts');
|
||||
const panelBox = await mcpClientPage.getCapabilityErrorBounds(McpPrimitiveType.Tools);
|
||||
const nextCapabilityBox = await mcpClientPage.getCapabilityBounds(McpPrimitiveType.Prompts);
|
||||
expect(panelBox.y + panelBox.height).toBeLessThanOrEqual(nextCapabilityBox.y + 1);
|
||||
|
||||
await mcpClientFlow.toggleCapability(client, 'tools');
|
||||
await expect.poll(() => mcpClientPage.getCapabilityErrorCount('tools')).toBe(0);
|
||||
await mcpClientFlow.toggleCapability(client, McpPrimitiveType.Tools);
|
||||
await expect.poll(() => mcpClientPage.getCapabilityErrorCount(McpPrimitiveType.Tools)).toBe(0);
|
||||
|
||||
await mcpClientFlow.toggleCapability(client, 'tools');
|
||||
await expect.poll(() => mcpClientPage.getCapabilityErrorCount('tools')).toBe(1);
|
||||
await mcpClientFlow.toggleCapability(client, McpPrimitiveType.Tools);
|
||||
await expect.poll(() => mcpClientPage.getCapabilityErrorCount(McpPrimitiveType.Tools)).toBe(1);
|
||||
});
|
||||
|
||||
test('Verify the Error Panel of an Empty List Stays Visible Without a Collapse Caret', async ({ user }) => {
|
||||
@@ -42,9 +43,9 @@ test('Verify the Error Panel of an Empty List Stays Visible Without a Collapse C
|
||||
const project = await workspaceFlow.create(new Project(faker.string.alphanumeric(10), ProjectType.Local));
|
||||
const client = await mcpClientFlow.create(project, new McpClient(faker.string.alphanumeric(10), url));
|
||||
await mcpClientFlow.connect(client);
|
||||
await mcpClientFlow.getListError(client, 'tools');
|
||||
await mcpClientFlow.getListError(client, McpPrimitiveType.Tools);
|
||||
|
||||
expect(await mcpClientPage.hasCaret('tools')).toBe(false);
|
||||
await mcpClientFlow.toggleCapability(client, 'tools');
|
||||
await expect.poll(() => mcpClientPage.getCapabilityErrorCount('tools')).toBe(1);
|
||||
expect(await mcpClientPage.hasCaret(McpPrimitiveType.Tools)).toBe(false);
|
||||
await mcpClientFlow.toggleCapability(client, McpPrimitiveType.Tools);
|
||||
await expect.poll(() => mcpClientPage.getCapabilityErrorCount(McpPrimitiveType.Tools)).toBe(1);
|
||||
});
|
||||
+2
-1
@@ -1,5 +1,6 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
|
||||
import { McpPrimitiveType } from '../../../enums/mcp-capability';
|
||||
import { ProjectType } from '../../../enums/project-types';
|
||||
import { expect, test } from '../../../misc/fixtures';
|
||||
import { scriptMcpListServer } from '../../../misc/mcp-list-server';
|
||||
@@ -15,7 +16,7 @@ test('Verify a JSON-RPC Error Whose Message Is a JSON String Is Shown Readably',
|
||||
const client = await mcpClientFlow.create(project, new McpClient(faker.string.alphanumeric(10), url));
|
||||
await mcpClientFlow.connect(client);
|
||||
|
||||
const error = await user.flowManager.mcpClientFlow.getListError(client, 'tools');
|
||||
const error = await user.flowManager.mcpClientFlow.getListError(client, McpPrimitiveType.Tools);
|
||||
|
||||
expect(error.title).toContain('boom');
|
||||
expect(error.title).not.toContain('[object Object]');
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
|
||||
import { McpPrimitiveType } from '../../../enums/mcp-capability';
|
||||
import { ProjectType } from '../../../enums/project-types';
|
||||
import { expect, test } from '../../../misc/fixtures';
|
||||
import { scriptMcpListServer } from '../../../misc/mcp-list-server';
|
||||
@@ -21,7 +22,7 @@ for (const rpcError of jsonRpcErrors) {
|
||||
const client = await mcpClientFlow.create(project, new McpClient(faker.string.alphanumeric(10), url));
|
||||
await mcpClientFlow.connect(client);
|
||||
|
||||
const error = await user.flowManager.mcpClientFlow.getListError(client, 'tools');
|
||||
const error = await user.flowManager.mcpClientFlow.getListError(client, McpPrimitiveType.Tools);
|
||||
expect(error.title).toContain(rpcError.message);
|
||||
expect(error.entries).toEqual([]);
|
||||
});
|
||||
|
||||
+5
-4
@@ -1,5 +1,6 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
|
||||
import { McpPrimitiveType } from '../../../enums/mcp-capability';
|
||||
import { ProjectType } from '../../../enums/project-types';
|
||||
import { DEFAULT_TIMEOUT, expect, test } from '../../../misc/fixtures';
|
||||
import { scriptMcpListServer } from '../../../misc/mcp-list-server';
|
||||
@@ -32,13 +33,13 @@ test('Verify Load More Whose Next Page Has an Invalid Entry Lists the Skipped En
|
||||
const project = await workspaceFlow.create(new Project(faker.string.alphanumeric(10), ProjectType.Local));
|
||||
const client = await mcpClientFlow.create(project, new McpClient(faker.string.alphanumeric(10), url));
|
||||
await mcpClientFlow.connect(client);
|
||||
await expect.poll(() => mcpClientPage.getPrimitives('resources'), { timeout: DEFAULT_TIMEOUT }).toEqual(['item-1']);
|
||||
await expect.poll(() => mcpClientPage.getPrimitives(McpPrimitiveType.Resources), { timeout: DEFAULT_TIMEOUT }).toEqual(['item-1']);
|
||||
|
||||
await mcpClientFlow.loadMore(client, 'resources');
|
||||
await mcpClientFlow.loadMore(client, McpPrimitiveType.Resources);
|
||||
await expect
|
||||
.poll(() => mcpClientPage.getPrimitives('resources'), { timeout: DEFAULT_TIMEOUT })
|
||||
.poll(() => mcpClientPage.getPrimitives(McpPrimitiveType.Resources), { timeout: DEFAULT_TIMEOUT })
|
||||
.toEqual(['item-1', 'item-2']);
|
||||
const error = await mcpClientFlow.getListError(client, 'resources');
|
||||
const error = await mcpClientFlow.getListError(client, McpPrimitiveType.Resources);
|
||||
|
||||
expect(error.entries.map(entry => entry.label)).toEqual(['bad-resource']);
|
||||
});
|
||||
@@ -1,5 +1,6 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
|
||||
import { McpListAction, McpPrimitiveType } from '../../../enums/mcp-capability';
|
||||
import { ProjectType } from '../../../enums/project-types';
|
||||
import { DEFAULT_TIMEOUT, expect, test } from '../../../misc/fixtures';
|
||||
import { scriptMcpListServer } from '../../../misc/mcp-list-server';
|
||||
@@ -8,19 +9,19 @@ import { Project } from '../../../models/project';
|
||||
|
||||
const loadMoreVariants = [
|
||||
{
|
||||
type: 'tools',
|
||||
type: McpPrimitiveType.Tools,
|
||||
method: 'tools/list',
|
||||
entry: (name: string) => ({ name, inputSchema: { type: 'object' } }),
|
||||
badEntry: { name: 'bad-tool' },
|
||||
},
|
||||
{
|
||||
type: 'prompts',
|
||||
type: McpPrimitiveType.Prompts,
|
||||
method: 'prompts/list',
|
||||
entry: (name: string) => ({ name }),
|
||||
badEntry: { name: 'bad-prompt', arguments: 'x' },
|
||||
},
|
||||
{
|
||||
type: 'resources',
|
||||
type: McpPrimitiveType.Resources,
|
||||
method: 'resources/list',
|
||||
entry: (name: string) => ({ name, uri: `file:///${name}.txt` }),
|
||||
badEntry: { name: 'bad-resource' },
|
||||
@@ -30,8 +31,8 @@ const loadMoreVariants = [
|
||||
// Open app bugs: listTools() / listPrompts() do not forward the cursor, so Load More re-requests the first page.
|
||||
// Resources forwards it and passes.
|
||||
const knownBugs: Partial<Record<(typeof loadMoreVariants)[number]['type'], string>> = {
|
||||
tools: 'INS-4060',
|
||||
prompts: 'INS-4061',
|
||||
[McpPrimitiveType.Tools]: 'INS-4060',
|
||||
[McpPrimitiveType.Prompts]: 'INS-4061',
|
||||
};
|
||||
|
||||
for (const { type, method, entry, badEntry } of loadMoreVariants) {
|
||||
@@ -51,12 +52,12 @@ for (const { type, method, entry, badEntry } of loadMoreVariants) {
|
||||
const client = await mcpClientFlow.create(project, new McpClient(faker.string.alphanumeric(10), url));
|
||||
await mcpClientFlow.connect(client);
|
||||
await expect.poll(() => mcpClientPage.getPrimitives(type), { timeout: DEFAULT_TIMEOUT }).toEqual(['item-1']);
|
||||
expect(await mcpClientFlow.getListAction(client, type)).toBe('Load More');
|
||||
expect(await mcpClientFlow.getListAction(client, type)).toBe(McpListAction.LoadMore);
|
||||
|
||||
await mcpClientFlow.loadMore(client, type);
|
||||
await expect
|
||||
.poll(() => mcpClientPage.getPrimitives(type), { timeout: DEFAULT_TIMEOUT })
|
||||
.toEqual(['item-1', 'item-2']);
|
||||
expect(await mcpClientFlow.getListAction(client, type)).toBe('Refresh');
|
||||
expect(await mcpClientFlow.getListAction(client, type)).toBe(McpListAction.Refresh);
|
||||
});
|
||||
}
|
||||
+3
-2
@@ -1,5 +1,6 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
|
||||
import { McpPrimitiveType } from '../../../enums/mcp-capability';
|
||||
import { ProjectType } from '../../../enums/project-types';
|
||||
import { DEFAULT_TIMEOUT, expect, test } from '../../../misc/fixtures';
|
||||
import { scriptMcpListServer } from '../../../misc/mcp-list-server';
|
||||
@@ -31,14 +32,14 @@ for (const [variant, { tools, skipped }] of Object.entries(partialInvalidTools))
|
||||
const client = await mcpClientFlow.create(project, new McpClient(faker.string.alphanumeric(10), url));
|
||||
await mcpClientFlow.connect(client);
|
||||
|
||||
const error = await mcpClientFlow.getListError(client, 'tools');
|
||||
const error = await mcpClientFlow.getListError(client, McpPrimitiveType.Tools);
|
||||
expect(error.title).toMatch(skippedTitle(2));
|
||||
expect(error.entries.map(entry => entry.label)).toEqual(skipped);
|
||||
for (const entry of error.entries) {
|
||||
expect(entry.reason).not.toBe('');
|
||||
}
|
||||
await expect
|
||||
.poll(() => mcpClientPage.getPrimitives('tools'), {
|
||||
.poll(() => mcpClientPage.getPrimitives(McpPrimitiveType.Tools), {
|
||||
timeout: DEFAULT_TIMEOUT,
|
||||
})
|
||||
.toEqual(['valid-tool']);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
|
||||
import { McpPrimitiveType } from '../../../enums/mcp-capability';
|
||||
import { ProjectType } from '../../../enums/project-types';
|
||||
import { DEFAULT_TIMEOUT, expect, test } from '../../../misc/fixtures';
|
||||
import { type McpListConfig, scriptMcpListServer } from '../../../misc/mcp-list-server';
|
||||
@@ -19,14 +20,14 @@ test('Verify Refresh Clears the Error Once the Server Returns Valid Data', async
|
||||
);
|
||||
const client = await mcpClientFlow.create(project, new McpClient(faker.string.alphanumeric(10), url));
|
||||
await mcpClientFlow.connect(client);
|
||||
await mcpClientFlow.getListError(client, 'tools');
|
||||
await mcpClientFlow.getListError(client, McpPrimitiveType.Tools);
|
||||
|
||||
await scriptMcpListServer(key, valid);
|
||||
await mcpClientFlow.refresh(client, 'tools');
|
||||
await expect.poll(() => mcpClientPage.getCapabilityErrorCount('tools')).toBe(0);
|
||||
await expect.poll(() => mcpClientPage.getPrimitives('tools'), { timeout: DEFAULT_TIMEOUT }).toEqual(['valid-tool']);
|
||||
await mcpClientFlow.refresh(client, McpPrimitiveType.Tools);
|
||||
await expect.poll(() => mcpClientPage.getCapabilityErrorCount(McpPrimitiveType.Tools)).toBe(0);
|
||||
await expect.poll(() => mcpClientPage.getPrimitives(McpPrimitiveType.Tools), { timeout: DEFAULT_TIMEOUT }).toEqual(['valid-tool']);
|
||||
|
||||
await scriptMcpListServer(key, invalid);
|
||||
await mcpClientFlow.refresh(client, 'tools');
|
||||
expect((await mcpClientFlow.getListError(client, 'tools')).title).toBe(wholeFailureTitle('tools/list'));
|
||||
await mcpClientFlow.refresh(client, McpPrimitiveType.Tools);
|
||||
expect((await mcpClientFlow.getListError(client, McpPrimitiveType.Tools)).title).toBe(wholeFailureTitle('tools/list'));
|
||||
});
|
||||
+4
-3
@@ -1,5 +1,6 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
|
||||
import { McpPrimitiveType } from '../../../enums/mcp-capability';
|
||||
import { ProjectType } from '../../../enums/project-types';
|
||||
import { DEFAULT_TIMEOUT, expect, test } from '../../../misc/fixtures';
|
||||
import { scriptMcpListServer } from '../../../misc/mcp-list-server';
|
||||
@@ -23,7 +24,7 @@ test('Verify Refresh on Resources Clears a Resource Templates Error Once the Ser
|
||||
const project = await workspaceFlow.create(new Project(faker.string.alphanumeric(10), ProjectType.Local));
|
||||
const client = await mcpClientFlow.create(project, new McpClient(faker.string.alphanumeric(10), url));
|
||||
await mcpClientFlow.connect(client);
|
||||
await mcpClientFlow.getListError(client, 'resources');
|
||||
await mcpClientFlow.getListError(client, McpPrimitiveType.Resources);
|
||||
|
||||
await scriptMcpListServer(key, {
|
||||
'resources/list': resources,
|
||||
@@ -31,7 +32,7 @@ test('Verify Refresh on Resources Clears a Resource Templates Error Once the Ser
|
||||
result: { resourceTemplates: [{ uriTemplate: 'file:///{path}', name: 'valid-template' }] },
|
||||
},
|
||||
});
|
||||
await mcpClientFlow.refresh(client, 'resources');
|
||||
await mcpClientFlow.refresh(client, McpPrimitiveType.Resources);
|
||||
|
||||
await expect.poll(() => mcpClientPage.getCapabilityErrorCount('resources'), { timeout: DEFAULT_TIMEOUT }).toBe(0);
|
||||
await expect.poll(() => mcpClientPage.getCapabilityErrorCount(McpPrimitiveType.Resources), { timeout: DEFAULT_TIMEOUT }).toBe(0);
|
||||
});
|
||||
+8
-7
@@ -1,5 +1,6 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
|
||||
import { McpPrimitiveType } from '../../../enums/mcp-capability';
|
||||
import { ProjectType } from '../../../enums/project-types';
|
||||
import { DEFAULT_TIMEOUT, expect, test } from '../../../misc/fixtures';
|
||||
import { scriptMcpListServer } from '../../../misc/mcp-list-server';
|
||||
@@ -26,20 +27,20 @@ test('Verify Invalid resources/list and prompts/list Are Reported Like tools/lis
|
||||
const client = await mcpClientFlow.create(project, new McpClient(faker.string.alphanumeric(10), url));
|
||||
await mcpClientFlow.connect(client);
|
||||
|
||||
const resourcesError = await mcpClientFlow.getListError(client, 'resources');
|
||||
const resourcesError = await mcpClientFlow.getListError(client, McpPrimitiveType.Resources);
|
||||
expect(resourcesError.title).toMatch(skippedTitle(2));
|
||||
expect(resourcesError.entries.map(entry => entry.label)).toEqual(['no-uri-resource', 'numeric-uri-resource']);
|
||||
await expect
|
||||
.poll(() => mcpClientPage.getPrimitives('resources'), {
|
||||
.poll(() => mcpClientPage.getPrimitives(McpPrimitiveType.Resources), {
|
||||
timeout: DEFAULT_TIMEOUT,
|
||||
})
|
||||
.toEqual(['valid-resource']);
|
||||
|
||||
expect(await mcpClientFlow.getListError(client, 'prompts')).toEqual({
|
||||
expect(await mcpClientFlow.getListError(client, McpPrimitiveType.Prompts)).toEqual({
|
||||
title: wholeFailureTitle('prompts/list'),
|
||||
entries: [],
|
||||
});
|
||||
expect(await mcpClientPage.getPrimitives('prompts')).toEqual([]);
|
||||
expect(await mcpClientPage.getPrimitives(McpPrimitiveType.Prompts)).toEqual([]);
|
||||
});
|
||||
|
||||
test('Verify a prompts/list Whose prompts Is an Object Is a Whole-Response Error', async ({ user }) => {
|
||||
@@ -51,7 +52,7 @@ test('Verify a prompts/list Whose prompts Is an Object Is a Whole-Response Error
|
||||
const client = await mcpClientFlow.create(project, new McpClient(faker.string.alphanumeric(10), url));
|
||||
await mcpClientFlow.connect(client);
|
||||
|
||||
expect(await user.flowManager.mcpClientFlow.getListError(client, 'prompts')).toEqual({
|
||||
expect(await user.flowManager.mcpClientFlow.getListError(client, McpPrimitiveType.Prompts)).toEqual({
|
||||
title: wholeFailureTitle('prompts/list'),
|
||||
entries: [],
|
||||
});
|
||||
@@ -70,11 +71,11 @@ test('Verify a Prompt with Bad arguments Is Skipped While Valid Prompts Are List
|
||||
const client = await mcpClientFlow.create(project, new McpClient(faker.string.alphanumeric(10), url));
|
||||
await mcpClientFlow.connect(client);
|
||||
|
||||
const error = await mcpClientFlow.getListError(client, 'prompts');
|
||||
const error = await mcpClientFlow.getListError(client, McpPrimitiveType.Prompts);
|
||||
expect(error.title).toMatch(skippedTitle(1));
|
||||
expect(error.entries.map(entry => entry.label)).toEqual(['bad-args-prompt']);
|
||||
await expect
|
||||
.poll(() => user.pageManager.mcpClientPage.getPrimitives('prompts'), {
|
||||
.poll(() => user.pageManager.mcpClientPage.getPrimitives(McpPrimitiveType.Prompts), {
|
||||
timeout: DEFAULT_TIMEOUT,
|
||||
})
|
||||
.toEqual(['valid-prompt']);
|
||||
|
||||
+3
-2
@@ -1,5 +1,6 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
|
||||
import { McpPrimitiveType } from '../../../enums/mcp-capability';
|
||||
import { ProjectType } from '../../../enums/project-types';
|
||||
import { DEFAULT_TIMEOUT, expect, test } from '../../../misc/fixtures';
|
||||
import { scriptMcpListServer } from '../../../misc/mcp-list-server';
|
||||
@@ -21,6 +22,6 @@ test('Verify a Server-Initiated Request Reusing the tools/list Request Id Does N
|
||||
const client = await mcpClientFlow.create(project, new McpClient(faker.string.alphanumeric(10), url));
|
||||
await mcpClientFlow.connect(client);
|
||||
|
||||
await expect.poll(() => mcpClientPage.getPrimitives('tools'), { timeout: DEFAULT_TIMEOUT }).toEqual(['valid-tool']);
|
||||
expect(await mcpClientPage.getCapabilityErrorCount('tools')).toBe(0);
|
||||
await expect.poll(() => mcpClientPage.getPrimitives(McpPrimitiveType.Tools), { timeout: DEFAULT_TIMEOUT }).toEqual(['valid-tool']);
|
||||
expect(await mcpClientPage.getCapabilityErrorCount(McpPrimitiveType.Tools)).toBe(0);
|
||||
});
|
||||
+4
-3
@@ -1,5 +1,6 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
|
||||
import { McpPrimitiveType } from '../../../enums/mcp-capability';
|
||||
import { ProjectType } from '../../../enums/project-types';
|
||||
import { DEFAULT_TIMEOUT, expect, test } from '../../../misc/fixtures';
|
||||
import { scriptMcpListServer } from '../../../misc/mcp-list-server';
|
||||
@@ -22,13 +23,13 @@ test('Verify Resources and Resource Templates Errors Share One Capability Item P
|
||||
const client = await mcpClientFlow.create(project, new McpClient(faker.string.alphanumeric(10), url));
|
||||
await mcpClientFlow.connect(client);
|
||||
|
||||
const error = await mcpClientFlow.getListError(client, 'resources');
|
||||
await expect.poll(() => mcpClientPage.getCapabilityErrorCount('resources')).toBe(1);
|
||||
const error = await mcpClientFlow.getListError(client, McpPrimitiveType.Resources);
|
||||
await expect.poll(() => mcpClientPage.getCapabilityErrorCount(McpPrimitiveType.Resources)).toBe(1);
|
||||
expect(error.title).toContain(wholeFailureTitle('resources/list'));
|
||||
expect(error.title).toContain('skipped because they do not match the MCP schema');
|
||||
expect(error.entries.map(entry => entry.label)).toEqual(['no-uri-template']);
|
||||
await expect
|
||||
.poll(() => mcpClientPage.getPrimitives('resourceTemplates'), {
|
||||
.poll(() => mcpClientPage.getPrimitives(McpPrimitiveType.ResourceTemplates), {
|
||||
timeout: DEFAULT_TIMEOUT,
|
||||
})
|
||||
.toEqual(['valid-template']);
|
||||
|
||||
+5
-4
@@ -1,5 +1,6 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
|
||||
import { McpPrimitiveType } from '../../../enums/mcp-capability';
|
||||
import { ProjectType } from '../../../enums/project-types';
|
||||
import { expect, test } from '../../../misc/fixtures';
|
||||
import { scriptMcpListServer } from '../../../misc/mcp-list-server';
|
||||
@@ -21,11 +22,11 @@ for (const [variant, result] of Object.entries(wholeInvalidTools)) {
|
||||
const client = await mcpClientFlow.create(project, new McpClient(faker.string.alphanumeric(10), url));
|
||||
await mcpClientFlow.connect(client);
|
||||
|
||||
expect(await mcpClientFlow.getListError(client, 'tools')).toEqual({
|
||||
expect(await mcpClientFlow.getListError(client, McpPrimitiveType.Tools)).toEqual({
|
||||
title: wholeFailureTitle('tools/list'),
|
||||
entries: [],
|
||||
});
|
||||
expect(await user.pageManager.mcpClientPage.getPrimitives('tools')).toEqual([]);
|
||||
expect(await user.pageManager.mcpClientPage.getPrimitives(McpPrimitiveType.Tools)).toEqual([]);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -39,8 +40,8 @@ test('Verify a tools/list Result That Is Not an Object Shows an Error and Lists
|
||||
// The MCP SDK rejects a non-object `result` before the pane's own
|
||||
// validation runs, so the title is the SDK's ZodError, not the agreed
|
||||
// "does not match the MCP schema" wording.
|
||||
const error = await user.flowManager.mcpClientFlow.getListError(client, 'tools');
|
||||
const error = await user.flowManager.mcpClientFlow.getListError(client, McpPrimitiveType.Tools);
|
||||
expect(error.title).not.toBe('');
|
||||
expect(error.entries).toEqual([]);
|
||||
expect(await user.pageManager.mcpClientPage.getPrimitives('tools')).toEqual([]);
|
||||
expect(await user.pageManager.mcpClientPage.getPrimitives(McpPrimitiveType.Tools)).toEqual([]);
|
||||
});
|
||||
@@ -1,5 +1,6 @@
|
||||
import { faker } from "@faker-js/faker";
|
||||
|
||||
import { McpPrimitiveType } from "../../enums/mcp-capability";
|
||||
import { ProjectType } from "../../enums/project-types";
|
||||
import { DEFAULT_TIMEOUT,expect, MCP_SERVER, test } from "../../misc/fixtures";
|
||||
import { McpClient } from "../../models/mcp-client";
|
||||
@@ -38,7 +39,7 @@ test("Verify Create MCP Client", async ({ user }) => {
|
||||
);
|
||||
await mcpClientFlow.connect(mcpClient);
|
||||
const tools = await mcpClientFlow.getTools(mcpClient);
|
||||
expect(await user.pageManager.mcpClientPage.getCapabilityErrorCount("tools")).toBe(0);
|
||||
expect(await user.pageManager.mcpClientPage.getCapabilityErrorCount(McpPrimitiveType.Tools)).toBe(0);
|
||||
expect(tools).toEqual(
|
||||
expect.arrayContaining([
|
||||
"read_wiki_structure",
|
||||
|
||||
+5
-4
@@ -1,5 +1,6 @@
|
||||
import { faker } from "@faker-js/faker";
|
||||
|
||||
import { McpPrimitiveType } from "../../enums/mcp-capability";
|
||||
import { ProjectType } from "../../enums/project-types";
|
||||
import { DEFAULT_TIMEOUT, expect, MCP_SERVER, test } from "../../misc/fixtures";
|
||||
import { scriptMcpListServer } from "../../misc/mcp-list-server";
|
||||
@@ -39,9 +40,9 @@ test("Verify Loaded Pages Are Kept After Switching to Another MCP Client and Bac
|
||||
new McpClient(faker.string.alphanumeric(10), url),
|
||||
);
|
||||
await mcpClientFlow.connect(client);
|
||||
await mcpClientFlow.loadMore(client, "resources");
|
||||
await mcpClientFlow.loadMore(client, McpPrimitiveType.Resources);
|
||||
await expect
|
||||
.poll(() => mcpClientPage.getPrimitives("resources"), {
|
||||
.poll(() => mcpClientPage.getPrimitives(McpPrimitiveType.Resources), {
|
||||
timeout: DEFAULT_TIMEOUT,
|
||||
})
|
||||
.toEqual(["item-1", "item-2"]);
|
||||
@@ -50,10 +51,10 @@ test("Verify Loaded Pages Are Kept After Switching to Another MCP Client and Bac
|
||||
project,
|
||||
new McpClient(faker.string.alphanumeric(10), MCP_SERVER),
|
||||
);
|
||||
await mcpClientFlow.getListAction(client, "resources");
|
||||
await mcpClientFlow.getListAction(client, McpPrimitiveType.Resources);
|
||||
|
||||
await expect
|
||||
.poll(() => mcpClientPage.getPrimitives("resources"), {
|
||||
.poll(() => mcpClientPage.getPrimitives(McpPrimitiveType.Resources), {
|
||||
timeout: DEFAULT_TIMEOUT,
|
||||
})
|
||||
.toEqual(["item-1", "item-2"]);
|
||||
|
||||
Reference in new issue
Block a user