Files
gitmost/packages/mcp/test/unit/drawio-shapes.test.mjs
T
agent_coder 7cb3199d09 refactor(mcp)!: BREAKING — все имена MCP-тулов snake_case → camelCase (унификация с in-app) (#412)
Один логический тул жил под двумя именами: внешний MCP snake_case
(edit_page_text), in-app camelCase (editPageText) — дублирование доков, путаница
при переносе промптов/скиллов, помеха шарингу спек (#294). Решение владельца:
единый camelCase везде, включая внешний MCP. После этого mcpName === inAppKey.

- tool-specs.ts: mcpName ВЫВЕДЕН из ключа спеки (mcpName == inAppKey) для всех
  43 shared-спек — раньше divergent snake, теперь равен ключу (проверено: mcpName
  читается только структурно — цикл регистрации, генератор <tool_inventory>,
  TOOL_FAMILY). +5 inline-регистраций (tableGet/updateComment/deleteComment/
  docmostTransform; search без изменений). Рантайм: 47 тулов, все camelCase, ноль
  подчёркиваний.
- Контракт-конвенция ИНВЕРТИРОВАНА: shared-tool-specs.contract.spec
  `mcpName === toSnake(inAppKey)` → `mcpName === inAppKey`; tool-specs.test
  и tool-inventory.test обновлены.
- ROUTING_PROSE/TOOL_FAMILY/INLINE_MCP_INVENTORY (server-instructions.ts) →
  camelCase (105 замен). ai-chat.prompt/guard уже на in-app camelCase-ключах —
  без изменений (guard прошёл). comment-signal EXCLUDED_TOOLS схлопнут с
  дублей snake+camel до camelCase.
- Некоторое неочевидное: assertUnambiguousMatch(op: "patch_node"|"delete_node")
  в prosemirror-markdown/node-ops — op интерполируется в model-facing ошибку;
  литерал-юнион + call-sites → "patchNode"|"deleteNode".
- Все snake-имена в описаниях/error-строках/комментах/тестах/доках → camelCase
  (whole-token, longest-match-first). CHANGELOG: BREAKING-таблица 46 строк +
  миграция (allowlists mcp__gitmost-*__get_node→__getNode, промпты/скиллы,
  .mcp.json, метрики по tool-label); релизится вместе с #411.
  Внутренние имена методов (PageService.updatePageContent и т.п.) НЕ тронуты —
  переименованы только ИМЕНА ТУЛОВ.

Гейт: mcp node --test 677/677; tsc -p apps/server чисто; jest ai-chat-tools.
service + shared-tool-specs.contract + tool-tiers + ai-chat.prompt +
comment-signal-inapp → 323. Второй линк breaking-окна (#411→ЭТОТ→#413→#415).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 19:10:38 +03:00

98 lines
4.1 KiB
JavaScript

// Unit tests for the drawioShapes verified-stencil catalog (issue #424).
// Covers acceptance #1: a "lambda" query returns a valid mxgraph.aws4 icon with
// the right service/resource pattern + sizes; a blocklisted stencil query
// returns its working replacement.
import { test } from "node:test";
import assert from "node:assert/strict";
import {
searchShapes,
awsServiceStyle,
azureImageStyle,
loadShapeIndex,
AWS_CATEGORY_FILL,
} from "../../build/lib/drawio-shapes.js";
test("the bundled index loads and is the real ~10k-shape catalog", () => {
const idx = loadShapeIndex();
assert.ok(Array.isArray(idx));
assert.ok(idx.length > 10000, `expected >10000 shapes, got ${idx.length}`);
// Record shape { style, w, h, title, tags, type }.
for (const k of ["style", "w", "h", "title", "tags", "type"]) {
assert.ok(k in idx[0], `record missing key ${k}`);
}
});
test('drawioShapes("lambda") returns a valid mxgraph.aws4 service icon', () => {
const results = searchShapes("lambda", { limit: 5 });
assert.ok(results.length > 0);
// Acceptance #1: a valid aws4 service-level icon (resourceIcon + resIcon)
// for lambda, with sensible default sizes, is present.
const svc = results.find(
(r) =>
/shape=mxgraph\.aws4\.resourceIcon/.test(r.style) &&
/resIcon=mxgraph\.aws4\.lambda(_function)?\b/.test(r.style),
);
assert.ok(svc, `no aws4 lambda service icon in ${JSON.stringify(results.map((r) => r.style.slice(-40)))}`);
assert.ok(svc.w > 0 && svc.h > 0, "icon must carry default w/h");
// The current-generation aws4 icon must outrank the deprecated aws3 one.
assert.match(results[0].style, /mxgraph\.aws4/);
});
test("a blocklisted stencil query returns its replacement + a note", () => {
const results = searchShapes("dynamodb_table", { limit: 3 });
assert.ok(results.length > 0);
const rep = results[0];
// dynamodb_table (empty box) -> dynamodb.
assert.match(rep.style, /resIcon=mxgraph\.aws4\.dynamodb\b/);
assert.ok(rep.note && /dynamodb_table/.test(rep.note), "note must explain the replacement");
// The broken stencil name must NOT be returned as a usable style.
assert.ok(
!results.some((r) => /resIcon=mxgraph\.aws4\.dynamodb_table\b/.test(r.style)),
"the broken dynamodb_table stencil must not be returned",
);
});
test("an AWS rebranding query returns the real (renamed) resIcon", () => {
const os = searchShapes("opensearch", { limit: 3 });
assert.ok(
os.some((r) => /resIcon=mxgraph\.aws4\.elasticsearch_service\b/.test(r.style) && r.note),
"OpenSearch must map to elasticsearch_service with a note",
);
const msk = searchShapes("msk", { limit: 3 });
assert.ok(
msk.some((r) => /managed_streaming_for_kafka/.test(r.style)),
"MSK must map to managed_streaming_for_kafka",
);
});
test("category filter narrows results", () => {
const all = searchShapes("database", { limit: 20 });
const dbOnly = searchShapes("database", { category: "Database", limit: 20 });
assert.ok(dbOnly.length <= all.length);
});
test("limit is honoured and capped", () => {
assert.equal(searchShapes("aws", { limit: 3 }).length, 3);
assert.ok(searchShapes("aws", { limit: 999 }).length <= 50);
});
test("empty query returns nothing", () => {
assert.deepEqual(searchShapes(" "), []);
});
test("style builders match the appendix templates", () => {
const s = awsServiceStyle("lambda", "Compute");
assert.match(s, /strokeColor=#ffffff/); // mandatory for service-level
assert.match(s, new RegExp(`fillColor=${AWS_CATEGORY_FILL.Compute}`));
assert.match(s, /shape=mxgraph\.aws4\.resourceIcon;resIcon=mxgraph\.aws4\.lambda$/);
const az = azureImageStyle("databases/Azure_Cosmos_DB.svg");
assert.match(az, /image=img\/lib\/azure2\/databases\/Azure_Cosmos_DB\.svg/);
});
test("azure and group queries surface the curated overlay", () => {
const cosmos = searchShapes("cosmos", { limit: 5 });
assert.ok(cosmos.some((r) => /azure2\/databases\/Azure_Cosmos_DB\.svg/.test(r.style)));
const vpc = searchShapes("vpc group", { limit: 5 });
assert.ok(vpc.some((r) => /grIcon=mxgraph\.aws4\.group_vpc2/.test(r.style)));
});