chore: publish clean community baseline
This commit is contained in:
@@ -0,0 +1,476 @@
|
||||
const { test, expect } = require('@playwright/test');
|
||||
const { getCurrentWorkspace, login, localized } = require('./helpers');
|
||||
|
||||
test('wizard loads and protocol selection updates flow', async ({ page }) => {
|
||||
await login(page);
|
||||
await page.goto('/wizard/');
|
||||
await expect(page.locator('[data-step-counter]').first()).toContainText(localized('Step', 'Шаг'));
|
||||
await expect(page.locator('#step-panel-1 .step-panel-title')).toContainText(localized('Choose a protocol', 'Выберите протокол'));
|
||||
await page.locator('[data-testid="wizard-protocol-rest"]').click();
|
||||
await page.locator('#btn-continue').click();
|
||||
await expect(page.locator('#step-panel-2 .step-panel-title')).toContainText(localized('Select upstream', 'Выберите upstream'));
|
||||
});
|
||||
|
||||
test('community wizard hides premium protocols and explains edition scope', async ({ page }) => {
|
||||
await login(page);
|
||||
await page.goto('/wizard/');
|
||||
await expect(page.locator('[data-testid="wizard-protocol-rest"]')).toBeVisible();
|
||||
await expect(page.locator('[data-testid="wizard-protocol-graphql"]')).toBeHidden();
|
||||
await expect(page.locator('[data-testid="wizard-protocol-grpc"]')).toBeHidden();
|
||||
await expect(page.locator('[data-testid="wizard-protocol-websocket"]')).toBeHidden();
|
||||
await expect(page.locator('[data-testid="wizard-protocol-soap"]')).toBeHidden();
|
||||
await expect(page.locator('#wizard-edition-protocol-note')).toContainText(
|
||||
localized('Commercial editions unlock', 'коммерческих редакциях')
|
||||
);
|
||||
|
||||
await page.locator('[data-testid="wizard-protocol-rest"]').click();
|
||||
await page.locator('#btn-continue').click();
|
||||
await page.locator('#btn-continue').click();
|
||||
await page.locator('#btn-continue').click();
|
||||
await page.locator('#btn-continue').click();
|
||||
|
||||
await expect(page.locator('#wizard-streaming-config-card')).toBeHidden();
|
||||
await expect(page.locator('#wizard-streaming-capability-note')).toHaveCount(0);
|
||||
});
|
||||
|
||||
test('wizard edit mode hydrates fields from operation version snapshot', async ({ page }) => {
|
||||
await login(page);
|
||||
const workspace = await getCurrentWorkspace(page);
|
||||
const operationId = 'frankfurter_monthly_rates';
|
||||
const version = 7;
|
||||
|
||||
await page.route(
|
||||
`/api/admin/workspaces/${encodeURIComponent(workspace.id)}/operations/${operationId}`,
|
||||
async (route) => {
|
||||
await route.fulfill({
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
id: operationId,
|
||||
workspace_id: workspace.id,
|
||||
name: operationId,
|
||||
display_name: 'Frankfurter monthly rates',
|
||||
category: 'finance',
|
||||
protocol: 'rest',
|
||||
security_level: 'standard',
|
||||
status: 'draft',
|
||||
current_draft_version: version,
|
||||
latest_published_version: null,
|
||||
created_at: '2026-06-19T00:00:00Z',
|
||||
updated_at: '2026-06-19T00:00:00Z',
|
||||
published_at: null,
|
||||
draft_version_ref: { version, status: 'draft' },
|
||||
published_version_ref: null,
|
||||
agent_refs: [],
|
||||
}),
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
await page.route(
|
||||
`/api/admin/workspaces/${encodeURIComponent(workspace.id)}/operations/${operationId}/versions/${version}`,
|
||||
async (route) => {
|
||||
await route.fulfill({
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
operation_id: operationId,
|
||||
workspace_id: workspace.id,
|
||||
version,
|
||||
status: 'draft',
|
||||
change_note: null,
|
||||
created_at: '2026-06-19T00:00:00Z',
|
||||
created_by: null,
|
||||
snapshot: {
|
||||
id: operationId,
|
||||
name: operationId,
|
||||
display_name: 'Frankfurter monthly rates',
|
||||
category: 'finance',
|
||||
protocol: 'rest',
|
||||
security_level: 'standard',
|
||||
status: 'draft',
|
||||
version,
|
||||
target: {
|
||||
kind: 'rest',
|
||||
base_url: 'https://api.frankfurter.app',
|
||||
method: 'GET',
|
||||
path_template: '/2024-01..2024-03',
|
||||
static_headers: {
|
||||
Accept: 'application/json',
|
||||
'X-Demo': 'frankfurter',
|
||||
},
|
||||
},
|
||||
input_schema: {
|
||||
type: 'object',
|
||||
description: null,
|
||||
required: false,
|
||||
nullable: false,
|
||||
default_value: null,
|
||||
fields: {
|
||||
from: {
|
||||
type: 'string',
|
||||
description: 'Base currency',
|
||||
required: true,
|
||||
nullable: false,
|
||||
default_value: null,
|
||||
fields: {},
|
||||
items: null,
|
||||
enum_values: [],
|
||||
variants: [],
|
||||
},
|
||||
to: {
|
||||
type: 'string',
|
||||
description: 'Quote currency',
|
||||
required: false,
|
||||
nullable: false,
|
||||
default_value: 'EUR',
|
||||
fields: {},
|
||||
items: null,
|
||||
enum_values: [],
|
||||
variants: [],
|
||||
},
|
||||
},
|
||||
items: null,
|
||||
enum_values: [],
|
||||
variants: [],
|
||||
},
|
||||
output_schema: {
|
||||
type: 'object',
|
||||
description: null,
|
||||
required: false,
|
||||
nullable: false,
|
||||
default_value: null,
|
||||
fields: {
|
||||
rates: {
|
||||
type: 'object',
|
||||
description: 'Rates by date',
|
||||
required: true,
|
||||
nullable: false,
|
||||
default_value: null,
|
||||
fields: {},
|
||||
items: null,
|
||||
enum_values: [],
|
||||
variants: [],
|
||||
},
|
||||
},
|
||||
items: null,
|
||||
enum_values: [],
|
||||
variants: [],
|
||||
},
|
||||
input_mapping: {
|
||||
rules: [
|
||||
{ source: '$.mcp.from', target: '$.request.query.from' },
|
||||
],
|
||||
},
|
||||
output_mapping: {
|
||||
rules: [
|
||||
{ source: '$.response.body.rates', target: '$.output.rates' },
|
||||
],
|
||||
},
|
||||
execution_config: {
|
||||
timeout_ms: 9000,
|
||||
retry_policy: { max_attempts: 2 },
|
||||
auth_profile_ref: null,
|
||||
headers: {},
|
||||
protocol_options: null,
|
||||
streaming: null,
|
||||
},
|
||||
tool_description: {
|
||||
title: 'Получить историю курсов за месяц',
|
||||
description: 'Возвращает историю курсов валют Frankfurter за заданный период.',
|
||||
tags: [],
|
||||
examples: [
|
||||
{
|
||||
input: {
|
||||
from: 'USD',
|
||||
to: 'EUR',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
wizard_state: {
|
||||
input_sample: {
|
||||
from: 'USD',
|
||||
to: 'EUR',
|
||||
},
|
||||
output_sample: {
|
||||
rates: {
|
||||
'2024-01': {
|
||||
EUR: 0.91,
|
||||
},
|
||||
},
|
||||
},
|
||||
test_input: {
|
||||
from: 'GBP',
|
||||
to: 'USD',
|
||||
},
|
||||
},
|
||||
samples: [],
|
||||
generated_draft: null,
|
||||
config_export: null,
|
||||
},
|
||||
}),
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
await page.goto(`/wizard/?mode=edit&operationId=${operationId}`);
|
||||
|
||||
await expect(page.locator('#tool-name')).toHaveValue(operationId);
|
||||
await expect(page.locator('#tool-display-name')).toHaveValue('Frankfurter monthly rates');
|
||||
await expect(page.locator('#new-upstream-url')).toHaveValue('https://api.frankfurter.app');
|
||||
await expect(page.locator('#new-upstream-static-headers')).toHaveValue(/"Accept": "application\/json"/);
|
||||
await expect(page.locator('#new-upstream-static-headers')).toHaveValue(/"X-Demo": "frankfurter"/);
|
||||
await expect(page.locator('#endpoint-path')).toHaveValue('/2024-01..2024-03');
|
||||
await expect(page.locator('.method-card.active')).toHaveAttribute('data-method', 'GET');
|
||||
await expect(page.locator('#tool-title')).toHaveValue('Получить историю курсов за месяц');
|
||||
await expect(page.locator('#tool-description')).toHaveValue('Возвращает историю курсов валют Frankfurter за заданный период.');
|
||||
await expect(page.locator('#tool-input-schema')).toHaveValue(/"from"/);
|
||||
await expect(page.locator('#tool-input-schema')).toHaveValue(/"to"/);
|
||||
await expect(page.locator('#tool-input-schema')).not.toHaveValue(/first_name/);
|
||||
await expect(page.locator('#tool-output-schema')).toHaveValue(/"rates"/);
|
||||
await expect(page.locator('#tool-input-mapping')).toHaveValue(/query\.from/);
|
||||
await expect(page.locator('#tool-output-mapping')).toHaveValue(/rates: \$\.response\.body\.rates/);
|
||||
await expect(page.locator('#tool-exec-config')).toHaveValue(/9000/);
|
||||
await expect(page.locator('#tool-exec-config')).toHaveValue(/max_attempts: 2/);
|
||||
await expect(page.locator('#wizard-input-sample')).toHaveValue(/"from": "USD"/);
|
||||
await expect(page.locator('#wizard-input-sample')).toHaveValue(/"to": "EUR"/);
|
||||
await expect(page.locator('#wizard-input-sample')).not.toHaveValue(/Ada/);
|
||||
await expect(page.locator('#wizard-output-sample')).toHaveValue(/"rates":/);
|
||||
await expect(page.locator('#wizard-output-sample')).toHaveValue(/0\.91/);
|
||||
await expect(page.locator('#wizard-test-input')).toHaveValue(/"from": "GBP"/);
|
||||
await expect(page.locator('#wizard-test-input')).toHaveValue(/"to": "USD"/);
|
||||
await expect(page.locator('#wizard-test-input')).not.toHaveValue(/Ada/);
|
||||
});
|
||||
|
||||
test('wizard edit mode preserves explicit request mapping targets on save', async ({ page }) => {
|
||||
await login(page);
|
||||
const workspace = await getCurrentWorkspace(page);
|
||||
const operationId = 'frankfurter_latest_rate';
|
||||
const version = 3;
|
||||
let updatePayload = null;
|
||||
|
||||
await page.route(
|
||||
`/api/admin/workspaces/${encodeURIComponent(workspace.id)}/operations/${operationId}`,
|
||||
async (route) => {
|
||||
if (route.request().method() === 'PATCH') {
|
||||
updatePayload = route.request().postDataJSON();
|
||||
await route.fulfill({
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
operation_id: operationId,
|
||||
workspace_id: workspace.id,
|
||||
version,
|
||||
status: 'draft',
|
||||
updated_at: '2026-06-19T00:00:00Z',
|
||||
}),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await route.fulfill({
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
id: operationId,
|
||||
workspace_id: workspace.id,
|
||||
name: operationId,
|
||||
display_name: 'Frankfurter latest rate',
|
||||
category: 'finance',
|
||||
protocol: 'rest',
|
||||
security_level: 'standard',
|
||||
status: 'draft',
|
||||
current_draft_version: version,
|
||||
latest_published_version: null,
|
||||
created_at: '2026-06-19T00:00:00Z',
|
||||
updated_at: '2026-06-19T00:00:00Z',
|
||||
published_at: null,
|
||||
draft_version_ref: { version, status: 'draft' },
|
||||
published_version_ref: null,
|
||||
agent_refs: [],
|
||||
}),
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
await page.route(
|
||||
`/api/admin/workspaces/${encodeURIComponent(workspace.id)}/operations/${operationId}/versions/${version}`,
|
||||
async (route) => {
|
||||
await route.fulfill({
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
operation_id: operationId,
|
||||
workspace_id: workspace.id,
|
||||
version,
|
||||
status: 'draft',
|
||||
change_note: null,
|
||||
created_at: '2026-06-19T00:00:00Z',
|
||||
created_by: null,
|
||||
snapshot: {
|
||||
id: operationId,
|
||||
name: operationId,
|
||||
display_name: 'Frankfurter latest rate',
|
||||
category: 'finance',
|
||||
protocol: 'rest',
|
||||
security_level: 'standard',
|
||||
status: 'draft',
|
||||
version,
|
||||
target: {
|
||||
kind: 'rest',
|
||||
base_url: 'https://api.frankfurter.app',
|
||||
method: 'GET',
|
||||
path_template: '/latest',
|
||||
static_headers: {
|
||||
Accept: 'application/json',
|
||||
},
|
||||
},
|
||||
input_schema: {
|
||||
type: 'object',
|
||||
description: null,
|
||||
required: false,
|
||||
nullable: false,
|
||||
default_value: null,
|
||||
fields: {
|
||||
base: {
|
||||
type: 'string',
|
||||
description: null,
|
||||
required: true,
|
||||
nullable: false,
|
||||
default_value: null,
|
||||
fields: {},
|
||||
items: null,
|
||||
enum_values: [],
|
||||
variants: [],
|
||||
},
|
||||
symbols: {
|
||||
type: 'string',
|
||||
description: 'Quote symbols',
|
||||
required: false,
|
||||
nullable: false,
|
||||
default_value: 'EUR',
|
||||
fields: {},
|
||||
items: null,
|
||||
enum_values: [],
|
||||
variants: [],
|
||||
},
|
||||
},
|
||||
items: null,
|
||||
enum_values: [],
|
||||
variants: [],
|
||||
},
|
||||
output_schema: {
|
||||
type: 'object',
|
||||
description: null,
|
||||
required: false,
|
||||
nullable: false,
|
||||
default_value: null,
|
||||
fields: {},
|
||||
items: null,
|
||||
enum_values: [],
|
||||
variants: [],
|
||||
},
|
||||
input_mapping: {
|
||||
rules: [
|
||||
{ source: '$.mcp.base', target: '$.request.query.base' },
|
||||
{ source: '$.mcp.symbols', target: '$.request.query.symbols' },
|
||||
{ source: '$.mcp.date', target: '$.request.path.date' },
|
||||
],
|
||||
},
|
||||
output_mapping: {
|
||||
rules: [
|
||||
{ source: '$.response.body.base', target: '$.output.base' },
|
||||
],
|
||||
},
|
||||
execution_config: {
|
||||
timeout_ms: 9000,
|
||||
retry_policy: null,
|
||||
auth_profile_ref: null,
|
||||
headers: {},
|
||||
protocol_options: null,
|
||||
streaming: null,
|
||||
},
|
||||
tool_description: {
|
||||
title: 'Получить последний курс',
|
||||
description: 'Возвращает последний курс.',
|
||||
tags: ['finance'],
|
||||
examples: [{ input: { base: 'USD', symbols: 'EUR' } }],
|
||||
},
|
||||
wizard_state: {
|
||||
input_sample: {
|
||||
base: 'USD',
|
||||
symbols: 'EUR',
|
||||
},
|
||||
output_sample: {
|
||||
base: 'USD',
|
||||
rates: {
|
||||
EUR: 0.91,
|
||||
},
|
||||
},
|
||||
test_input: {
|
||||
base: 'CHF',
|
||||
symbols: 'JPY',
|
||||
},
|
||||
},
|
||||
samples: [],
|
||||
generated_draft: null,
|
||||
config_export: null,
|
||||
},
|
||||
}),
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
await page.goto(`/wizard/?mode=edit&operationId=${operationId}`);
|
||||
await expect(page.locator('#tool-input-mapping')).toHaveValue(/query\.base/);
|
||||
await expect(page.locator('#tool-input-mapping')).toHaveValue(/path\.date/);
|
||||
await page.locator('.btn-save-draft').click();
|
||||
await expect.poll(() => updatePayload).not.toBeNull();
|
||||
|
||||
expect(updatePayload.display_name).toBe('Frankfurter latest rate');
|
||||
expect(updatePayload.target).toEqual({
|
||||
kind: 'rest',
|
||||
base_url: 'https://api.frankfurter.app',
|
||||
method: 'GET',
|
||||
path_template: '/latest',
|
||||
static_headers: {
|
||||
Accept: 'application/json',
|
||||
},
|
||||
});
|
||||
expect(updatePayload.input_schema.fields).toHaveProperty('base');
|
||||
expect(updatePayload.input_schema.fields).toHaveProperty('symbols');
|
||||
expect(updatePayload.output_mapping.rules).toEqual([
|
||||
{ source: '$.response.body.base', target: '$.output.base' },
|
||||
]);
|
||||
expect(updatePayload.execution_config).toEqual({
|
||||
timeout_ms: 9000,
|
||||
retry_policy: null,
|
||||
auth_profile_ref: null,
|
||||
headers: {},
|
||||
protocol_options: null,
|
||||
streaming: null,
|
||||
});
|
||||
expect(updatePayload.tool_description).toEqual({
|
||||
title: 'Получить последний курс',
|
||||
description: 'Возвращает последний курс.',
|
||||
tags: ['finance'],
|
||||
examples: [{ input: { base: 'USD', symbols: 'EUR' } }],
|
||||
});
|
||||
expect(updatePayload.wizard_state).toEqual({
|
||||
input_sample: {
|
||||
base: 'USD',
|
||||
symbols: 'EUR',
|
||||
},
|
||||
output_sample: {
|
||||
base: 'USD',
|
||||
rates: {
|
||||
EUR: 0.91,
|
||||
},
|
||||
},
|
||||
test_input: {
|
||||
base: 'CHF',
|
||||
symbols: 'JPY',
|
||||
},
|
||||
});
|
||||
expect(updatePayload.input_mapping.rules).toEqual([
|
||||
{ source: '$.mcp.base', target: '$.request.query.base' },
|
||||
{ source: '$.mcp.symbols', target: '$.request.query.symbols' },
|
||||
{ source: '$.mcp.date', target: '$.request.path.date' },
|
||||
]);
|
||||
});
|
||||
Reference in New Issue
Block a user