test: эталоны внешнего поведения перед перестройкой (#87)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
bac72c8bba
commit
808d7b710c
500
tests/fixtures/baseline/mcp_tools.expected.json
vendored
Normal file
500
tests/fixtures/baseline/mcp_tools.expected.json
vendored
Normal file
@ -0,0 +1,500 @@
|
||||
[
|
||||
{
|
||||
"description": "Задачи зеркала по фильтрам (спринт, исполнитель, вид работы, статус по факту, статус Jira, метка, эпик,\nподстрока темы/ключа, neighbor=1 — только соседние). Компактная таблица.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"assignee": {
|
||||
"default": "",
|
||||
"title": "Assignee",
|
||||
"type": "string"
|
||||
},
|
||||
"epic": {
|
||||
"default": "",
|
||||
"title": "Epic",
|
||||
"type": "string"
|
||||
},
|
||||
"fact_status": {
|
||||
"default": "",
|
||||
"title": "Fact Status",
|
||||
"type": "string"
|
||||
},
|
||||
"label": {
|
||||
"default": "",
|
||||
"title": "Label",
|
||||
"type": "string"
|
||||
},
|
||||
"limit": {
|
||||
"default": 50,
|
||||
"title": "Limit",
|
||||
"type": "integer"
|
||||
},
|
||||
"neighbor": {
|
||||
"default": "",
|
||||
"title": "Neighbor",
|
||||
"type": "string"
|
||||
},
|
||||
"q": {
|
||||
"default": "",
|
||||
"title": "Q",
|
||||
"type": "string"
|
||||
},
|
||||
"sprint": {
|
||||
"default": "",
|
||||
"title": "Sprint",
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"default": "",
|
||||
"title": "Status",
|
||||
"type": "string"
|
||||
},
|
||||
"work_kind": {
|
||||
"default": "",
|
||||
"title": "Work Kind",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"title": "issuesArguments",
|
||||
"type": "object"
|
||||
},
|
||||
"name": "issues"
|
||||
},
|
||||
{
|
||||
"description": "Задача целиком: поля, связи в обе стороны, служебные заметки, последние команды, описание. Комментарии — 5\nпоследних с номером, текст до 300 символов; полный текст — инструмент comment по номеру. Вложения — списком\n(номер, имя, размер, где упомянуты, скачано ли); содержимое — инструмент attachment.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"key": {
|
||||
"title": "Key",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"key"
|
||||
],
|
||||
"title": "issueArguments",
|
||||
"type": "object"
|
||||
},
|
||||
"name": "issue"
|
||||
},
|
||||
{
|
||||
"description": "Один комментарий задачи целиком: автор, время, полный текст. comment_id — номер из ответа issue («#123»).",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"comment_id": {
|
||||
"title": "Comment Id",
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
"title": "Key",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"key",
|
||||
"comment_id"
|
||||
],
|
||||
"title": "commentArguments",
|
||||
"type": "object"
|
||||
},
|
||||
"name": "comment"
|
||||
},
|
||||
{
|
||||
"description": "Скачать вложение задачи и вернуть путь к локальной копии (читайте файл по этому пути). attachment — номер из\nissue/comment («#31337» или «31337») либо точное имя файла; при нескольких одноимённых берётся самое новое.\nХаб просит агента скачать файл и сохраняет копию в downloads/ каталога данных; ожидание — до wait_seconds (1…600,\nпо умолчанию 120), не дождался — вызовите инструмент ещё раз: вторая команда не поставится. Файлы больше 10 МБ\nскачиваются только в Jira.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"attachment": {
|
||||
"title": "Attachment",
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
"title": "Key",
|
||||
"type": "string"
|
||||
},
|
||||
"wait_seconds": {
|
||||
"default": 120,
|
||||
"title": "Wait Seconds",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"key",
|
||||
"attachment"
|
||||
],
|
||||
"title": "attachmentArguments",
|
||||
"type": "object"
|
||||
},
|
||||
"name": "attachment"
|
||||
},
|
||||
{
|
||||
"description": "Лента изменений задач: смена статуса, правка описания, сроки, новые комментарии. По умолчанию — с начала\nсегодняшнего дня; hours — за последние N часов; since/until — ISO-8601 (без смещения — местное время); kinds — через запятую из\nstatus, description, dates, comment, other или all; key — одна задача (без привязки к проекту).",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"hours": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"title": "Hours"
|
||||
},
|
||||
"key": {
|
||||
"default": "",
|
||||
"title": "Key",
|
||||
"type": "string"
|
||||
},
|
||||
"kinds": {
|
||||
"default": "",
|
||||
"title": "Kinds",
|
||||
"type": "string"
|
||||
},
|
||||
"since": {
|
||||
"default": "",
|
||||
"title": "Since",
|
||||
"type": "string"
|
||||
},
|
||||
"until": {
|
||||
"default": "",
|
||||
"title": "Until",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"title": "changesArguments",
|
||||
"type": "object"
|
||||
},
|
||||
"name": "changes"
|
||||
},
|
||||
{
|
||||
"description": "Правка служебных заметок задачи (только переданные поля).",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"done_text": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"title": "Done Text"
|
||||
},
|
||||
"fact_status": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"title": "Fact Status"
|
||||
},
|
||||
"key": {
|
||||
"title": "Key",
|
||||
"type": "string"
|
||||
},
|
||||
"owner_note": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"title": "Owner Note"
|
||||
},
|
||||
"rest_text": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"title": "Rest Text"
|
||||
},
|
||||
"work_kind": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"title": "Work Kind"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"key"
|
||||
],
|
||||
"title": "notes_setArguments",
|
||||
"type": "object"
|
||||
},
|
||||
"name": "notes_set"
|
||||
},
|
||||
{
|
||||
"description": "Поставить пакет команд. Каждая команда — {\"type\": <тип>, \"params\": {...}}. Хвостовой refresh хаб добавит сам.\nПараметры по типам (необязательные — в [ ]):\n- ensure_issue: temp_key (new:<n>), issue_type (task|epic|bug), summary; [description, priority, duedate (YYYY-MM-DD), planned_start (YYYY-MM-DD), planned_end (YYYY-MM-DD), labels (список), epic_key (ключ или new:<n>), assignee (логин Jira)]\n- ensure_fields: key; [summary, description, priority, duedate (YYYY-MM-DD), planned_start (YYYY-MM-DD), planned_end (YYYY-MM-DD), epic_key (ключ или new:<n>), assignee (логин Jira), labels_add (список), labels_remove (список)] — хотя бы одно поле\n- ensure_comment: key, marker (метка в начале комментария), body\n- ensure_status: key, target (статус или список-путь)\n- ensure_link: source, target, link_type (логическое имя из link_types); [replace (true|false)]\n- ensure_sprint: key, sprint (имя спринта или backlog)\n- ensure_attachment: key, file (sha256 из POST /files; в MCP — path к локальному файлу), filename (имя вложения)\n- fetch_attachment: key, attachment_id (id вложения из issue (цифры))\n- refresh: —; [keys (список), jql] — keys или jql\nПроект: метку проекта сессии хаб дописывает в ensure_issue сам, метки других проектов запрещены, а команды над задачами вне проекта отклоняются; board_id для ensure_sprint выставляет хаб.\nensure_attachment из чата: вместо file передайте path — абсолютный путь к локальному файлу (до 10 МБ); MCP загрузит\nфайл в хаб и подставит file, filename по умолчанию — имя файла.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"commands": {
|
||||
"items": {
|
||||
"additionalProperties": true,
|
||||
"type": "object"
|
||||
},
|
||||
"title": "Commands",
|
||||
"type": "array"
|
||||
},
|
||||
"title": {
|
||||
"title": "Title",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"title",
|
||||
"commands"
|
||||
],
|
||||
"title": "batch_createArguments",
|
||||
"type": "object"
|
||||
},
|
||||
"name": "batch_create"
|
||||
},
|
||||
{
|
||||
"description": "Состояние пакета и его команд.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"title": "Id",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"title": "batch_statusArguments",
|
||||
"type": "object"
|
||||
},
|
||||
"name": "batch_status"
|
||||
},
|
||||
{
|
||||
"description": "Ждать завершения пакета (до 60 мин), вернуть итог по командам. Ожидание не блокирует event loop сервера.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"title": "Id",
|
||||
"type": "integer"
|
||||
},
|
||||
"timeout_minutes": {
|
||||
"default": 10,
|
||||
"title": "Timeout Minutes",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"title": "batch_waitArguments",
|
||||
"type": "object"
|
||||
},
|
||||
"name": "batch_wait"
|
||||
},
|
||||
{
|
||||
"description": "Последние пакеты со статусом.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"default": 10,
|
||||
"title": "Limit",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"title": "batchesArguments",
|
||||
"type": "object"
|
||||
},
|
||||
"name": "batches"
|
||||
},
|
||||
{
|
||||
"description": "Повторить команду — новый пакет с теми же параметрами.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"title": "Id",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"title": "command_retryArguments",
|
||||
"type": "object"
|
||||
},
|
||||
"name": "command_retry"
|
||||
},
|
||||
{
|
||||
"description": "Отменить пакет: команды в очереди отменяются; взятую агентом команду отмена не останавливает —\nв ответе подсказка (POST /commands/N/cancel с force).",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"title": "Id",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"title": "batch_cancelArguments",
|
||||
"type": "object"
|
||||
},
|
||||
"name": "batch_cancel"
|
||||
},
|
||||
{
|
||||
"description": "Обновить зеркало из Jira: по ключам или полностью (базовая JQL). По умолчанию только ставит пакет;\nwait=true — дождаться результата (до 30 мин).",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"full": {
|
||||
"default": false,
|
||||
"title": "Full",
|
||||
"type": "boolean"
|
||||
},
|
||||
"keys": {
|
||||
"anyOf": [
|
||||
{
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"title": "Keys"
|
||||
},
|
||||
"wait": {
|
||||
"default": false,
|
||||
"title": "Wait",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"title": "refreshArguments",
|
||||
"type": "object"
|
||||
},
|
||||
"name": "refresh"
|
||||
},
|
||||
{
|
||||
"description": "Агент: онлайн/офлайн, версия, текущая команда, 429 за сутки, команды без ответа, опубликованная версия,\nсостояние самообновления и запускатель.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"title": "agent_statusArguments",
|
||||
"type": "object"
|
||||
},
|
||||
"name": "agent_status"
|
||||
},
|
||||
{
|
||||
"description": "Журнал обновлений зеркала.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"limit": {
|
||||
"default": 10,
|
||||
"title": "Limit",
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"title": "sync_logArguments",
|
||||
"type": "object"
|
||||
},
|
||||
"name": "sync_log"
|
||||
},
|
||||
{
|
||||
"description": "Настройки хаба (все или одна по ключу).",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"key": {
|
||||
"default": "",
|
||||
"title": "Key",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"title": "settings_getArguments",
|
||||
"type": "object"
|
||||
},
|
||||
"name": "settings_get"
|
||||
},
|
||||
{
|
||||
"description": "Записать настройку: value — JSON. Поднимает settings_version.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"key": {
|
||||
"title": "Key",
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
"title": "Value",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"key",
|
||||
"value"
|
||||
],
|
||||
"title": "settings_setArguments",
|
||||
"type": "object"
|
||||
},
|
||||
"name": "settings_set"
|
||||
},
|
||||
{
|
||||
"description": "Проекты хаба: метка, название, доска, тип доски, число задач; текущий проект сессии отмечен.",
|
||||
"inputSchema": {
|
||||
"properties": {},
|
||||
"title": "projectsArguments",
|
||||
"type": "object"
|
||||
},
|
||||
"name": "projects"
|
||||
},
|
||||
{
|
||||
"description": "Завести проект (метка Jira, название, необязательно id доски). Хаб ставит пакет обновления; в ответе — фрагмент\n.mcp.json для репозитория проекта.",
|
||||
"inputSchema": {
|
||||
"properties": {
|
||||
"board_id": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"title": "Board Id"
|
||||
},
|
||||
"label": {
|
||||
"title": "Label",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"title": "Name",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"label",
|
||||
"name"
|
||||
],
|
||||
"title": "project_createArguments",
|
||||
"type": "object"
|
||||
},
|
||||
"name": "project_create"
|
||||
}
|
||||
]
|
||||
206
tests/fixtures/baseline/routes.expected.json
vendored
Normal file
206
tests/fixtures/baseline/routes.expected.json
vendored
Normal file
@ -0,0 +1,206 @@
|
||||
[
|
||||
[
|
||||
"GET",
|
||||
"^/(links)?$",
|
||||
null,
|
||||
"h_root"
|
||||
],
|
||||
[
|
||||
"GET",
|
||||
"^/agent$",
|
||||
"client",
|
||||
"h_agent_get"
|
||||
],
|
||||
[
|
||||
"POST",
|
||||
"^/agent/claim$",
|
||||
"agent",
|
||||
"h_claim"
|
||||
],
|
||||
[
|
||||
"GET",
|
||||
"^/agent/commands/(\\d+)$",
|
||||
"agent",
|
||||
"h_agent_command"
|
||||
],
|
||||
[
|
||||
"POST",
|
||||
"^/agent/commands/(\\d+)/result$",
|
||||
"agent",
|
||||
"h_agent_result"
|
||||
],
|
||||
[
|
||||
"POST",
|
||||
"^/agent/files$",
|
||||
"agent",
|
||||
"h_agent_files_post"
|
||||
],
|
||||
[
|
||||
"GET",
|
||||
"^/agent/files/([^/]+)$",
|
||||
"agent",
|
||||
"h_agent_file"
|
||||
],
|
||||
[
|
||||
"POST",
|
||||
"^/agent/history$",
|
||||
"agent",
|
||||
"h_agent_history"
|
||||
],
|
||||
[
|
||||
"GET",
|
||||
"^/agent/known-keys$",
|
||||
"agent",
|
||||
"h_known_keys"
|
||||
],
|
||||
[
|
||||
"GET",
|
||||
"^/agent/release$",
|
||||
"agent",
|
||||
"h_agent_release"
|
||||
],
|
||||
[
|
||||
"GET",
|
||||
"^/agent/settings$",
|
||||
"agent",
|
||||
"h_agent_settings"
|
||||
],
|
||||
[
|
||||
"POST",
|
||||
"^/agent/state$",
|
||||
"agent",
|
||||
"h_agent_state"
|
||||
],
|
||||
[
|
||||
"GET",
|
||||
"^/app(?:/(.*))?$",
|
||||
null,
|
||||
"h_app"
|
||||
],
|
||||
[
|
||||
"GET",
|
||||
"^/attachments/(\\d+)$",
|
||||
"client",
|
||||
"h_attachment_get"
|
||||
],
|
||||
[
|
||||
"GET",
|
||||
"^/attachments/(\\d+)/content$",
|
||||
"client",
|
||||
"h_attachment_content"
|
||||
],
|
||||
[
|
||||
"POST",
|
||||
"^/attachments/(\\d+)/fetch$",
|
||||
"client",
|
||||
"h_attachment_fetch"
|
||||
],
|
||||
[
|
||||
"GET",
|
||||
"^/batches$",
|
||||
"client",
|
||||
"h_batches_get"
|
||||
],
|
||||
[
|
||||
"POST",
|
||||
"^/batches$",
|
||||
"client",
|
||||
"h_batches_post"
|
||||
],
|
||||
[
|
||||
"GET",
|
||||
"^/batches/(\\d+)$",
|
||||
"client",
|
||||
"h_batch_get"
|
||||
],
|
||||
[
|
||||
"POST",
|
||||
"^/batches/(\\d+)/cancel$",
|
||||
"client",
|
||||
"h_batch_cancel"
|
||||
],
|
||||
[
|
||||
"GET",
|
||||
"^/board$",
|
||||
"client",
|
||||
"h_board"
|
||||
],
|
||||
[
|
||||
"GET",
|
||||
"^/changes$",
|
||||
"client",
|
||||
"h_changes"
|
||||
],
|
||||
[
|
||||
"POST",
|
||||
"^/commands/(\\d+)/cancel$",
|
||||
"client",
|
||||
"h_command_cancel"
|
||||
],
|
||||
[
|
||||
"POST",
|
||||
"^/commands/(\\d+)/retry$",
|
||||
"client",
|
||||
"h_command_retry"
|
||||
],
|
||||
[
|
||||
"POST",
|
||||
"^/files$",
|
||||
"client",
|
||||
"h_files_post"
|
||||
],
|
||||
[
|
||||
"GET",
|
||||
"^/issues$",
|
||||
"client",
|
||||
"h_issues"
|
||||
],
|
||||
[
|
||||
"GET",
|
||||
"^/issues/([^/]+)$",
|
||||
"client",
|
||||
"h_issue"
|
||||
],
|
||||
[
|
||||
"PUT",
|
||||
"^/issues/([^/]+)/note$",
|
||||
"client",
|
||||
"h_note_put"
|
||||
],
|
||||
[
|
||||
"GET",
|
||||
"^/projects$",
|
||||
"client",
|
||||
"h_projects_get"
|
||||
],
|
||||
[
|
||||
"POST",
|
||||
"^/projects$",
|
||||
"client",
|
||||
"h_projects_post"
|
||||
],
|
||||
[
|
||||
"POST",
|
||||
"^/refresh$",
|
||||
"client",
|
||||
"h_refresh"
|
||||
],
|
||||
[
|
||||
"GET",
|
||||
"^/settings$",
|
||||
"client",
|
||||
"h_settings_get"
|
||||
],
|
||||
[
|
||||
"PUT",
|
||||
"^/settings$",
|
||||
"client",
|
||||
"h_settings_put"
|
||||
],
|
||||
[
|
||||
"GET",
|
||||
"^/sync-runs$",
|
||||
"client",
|
||||
"h_sync_runs"
|
||||
]
|
||||
]
|
||||
95
tests/fixtures/baseline/schema.expected.json
vendored
Normal file
95
tests/fixtures/baseline/schema.expected.json
vendored
Normal file
@ -0,0 +1,95 @@
|
||||
{
|
||||
"objects": [
|
||||
[
|
||||
"index",
|
||||
"issue_attachment_key",
|
||||
"CREATE INDEX issue_attachment_key ON issue_attachment(key)"
|
||||
],
|
||||
[
|
||||
"index",
|
||||
"ix_command_status",
|
||||
"CREATE INDEX ix_command_status ON command(status, batch_id, seq)"
|
||||
],
|
||||
[
|
||||
"index",
|
||||
"ix_issue_change_at",
|
||||
"CREATE INDEX ix_issue_change_at ON issue_change(at)"
|
||||
],
|
||||
[
|
||||
"index",
|
||||
"ix_issue_change_key",
|
||||
"CREATE INDEX ix_issue_change_key ON issue_change(key, at)"
|
||||
],
|
||||
[
|
||||
"index",
|
||||
"ix_issue_link_target",
|
||||
"CREATE INDEX ix_issue_link_target ON issue_link(target_key)"
|
||||
],
|
||||
[
|
||||
"table",
|
||||
"agent_status",
|
||||
"CREATE TABLE agent_status (\n id INTEGER PRIMARY KEY CHECK (id = 1),\n last_seen_at TEXT,\n agent_version TEXT,\n settings_version INTEGER,\n current_command_id INTEGER,\n last_jira_call_at TEXT\n)"
|
||||
],
|
||||
[
|
||||
"table",
|
||||
"attachment_file",
|
||||
"CREATE TABLE attachment_file (\n attachment_id TEXT PRIMARY KEY,\n file TEXT NOT NULL,\n size INTEGER NOT NULL,\n fetched_at TEXT NOT NULL\n)"
|
||||
],
|
||||
[
|
||||
"table",
|
||||
"batch",
|
||||
"CREATE TABLE batch (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n title TEXT NOT NULL,\n author TEXT NOT NULL,\n created_at TEXT NOT NULL,\n cancelled_at TEXT,\n estimate_seconds INTEGER NOT NULL DEFAULT 0,\n project TEXT\n)"
|
||||
],
|
||||
[
|
||||
"table",
|
||||
"command",
|
||||
"CREATE TABLE command (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n batch_id INTEGER NOT NULL REFERENCES batch(id),\n seq INTEGER NOT NULL,\n type TEXT NOT NULL,\n params TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'queued',\n claimed_at TEXT,\n deliveries INTEGER NOT NULL DEFAULT 0,\n finished_at TEXT,\n result TEXT,\n error TEXT,\n jira_calls INTEGER NOT NULL DEFAULT 0,\n rate_limited INTEGER NOT NULL DEFAULT 0,\n pause_seconds INTEGER,\n depends_on INTEGER REFERENCES command(id)\n)"
|
||||
],
|
||||
[
|
||||
"table",
|
||||
"issue",
|
||||
"CREATE TABLE issue (\n key TEXT PRIMARY KEY,\n id TEXT,\n issue_type TEXT,\n summary TEXT,\n description TEXT,\n status TEXT,\n priority TEXT,\n assignee TEXT,\n reporter TEXT,\n duedate TEXT,\n planned_start TEXT,\n planned_end TEXT,\n epic_key TEXT,\n labels TEXT NOT NULL DEFAULT '[]',\n sprint TEXT,\n created TEXT,\n jira_updated TEXT,\n fetched_at TEXT NOT NULL,\n neighbor INTEGER NOT NULL DEFAULT 0\n)"
|
||||
],
|
||||
[
|
||||
"table",
|
||||
"issue_attachment",
|
||||
"CREATE TABLE issue_attachment (\n attachment_id TEXT PRIMARY KEY,\n key TEXT NOT NULL,\n filename TEXT NOT NULL,\n size INTEGER,\n mime_type TEXT,\n author TEXT,\n created TEXT,\n created_at TEXT\n)"
|
||||
],
|
||||
[
|
||||
"table",
|
||||
"issue_change",
|
||||
"CREATE TABLE issue_change (\n history_id TEXT NOT NULL,\n seq INTEGER NOT NULL,\n key TEXT NOT NULL,\n at TEXT,\n created TEXT NOT NULL,\n author TEXT,\n field TEXT NOT NULL,\n field_id TEXT,\n field_name TEXT NOT NULL,\n from_value TEXT,\n from_string TEXT,\n to_value TEXT,\n to_string TEXT,\n fetched_at TEXT NOT NULL,\n PRIMARY KEY (history_id, seq)\n)"
|
||||
],
|
||||
[
|
||||
"table",
|
||||
"issue_comment",
|
||||
"CREATE TABLE issue_comment (\n key TEXT NOT NULL,\n comment_id TEXT NOT NULL,\n author TEXT,\n created TEXT,\n updated TEXT,\n body TEXT NOT NULL DEFAULT '',\n created_at TEXT,\n PRIMARY KEY (key, comment_id)\n)"
|
||||
],
|
||||
[
|
||||
"table",
|
||||
"issue_link",
|
||||
"CREATE TABLE issue_link (\n source_key TEXT NOT NULL,\n target_key TEXT NOT NULL,\n link_type TEXT NOT NULL,\n PRIMARY KEY (source_key, target_key, link_type)\n)"
|
||||
],
|
||||
[
|
||||
"table",
|
||||
"issue_note",
|
||||
"CREATE TABLE issue_note (\n key TEXT PRIMARY KEY,\n fact_status TEXT,\n work_kind TEXT,\n done_text TEXT,\n rest_text TEXT,\n owner_note TEXT,\n updated_at TEXT,\n updated_by TEXT\n)"
|
||||
],
|
||||
[
|
||||
"table",
|
||||
"project",
|
||||
"CREATE TABLE project (\n label TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n board_id INTEGER,\n board_type TEXT,\n created_at TEXT NOT NULL\n)"
|
||||
],
|
||||
[
|
||||
"table",
|
||||
"setting",
|
||||
"CREATE TABLE setting (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL\n)"
|
||||
],
|
||||
[
|
||||
"table",
|
||||
"sync_run",
|
||||
"CREATE TABLE sync_run (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n started_at TEXT NOT NULL,\n finished_at TEXT,\n kind TEXT NOT NULL,\n query TEXT,\n issues_count INTEGER NOT NULL DEFAULT 0,\n links_count INTEGER NOT NULL DEFAULT 0,\n jira_calls INTEGER NOT NULL DEFAULT 0,\n rate_limited INTEGER NOT NULL DEFAULT 0,\n command_id INTEGER,\n history_at TEXT\n)"
|
||||
]
|
||||
],
|
||||
"schema_version": 6
|
||||
}
|
||||
63
tests/fixtures/baseline/settings.expected.json
vendored
Normal file
63
tests/fixtures/baseline/settings.expected.json
vendored
Normal file
@ -0,0 +1,63 @@
|
||||
{
|
||||
"agent_keys": [
|
||||
"project_key",
|
||||
"issue_types",
|
||||
"epic_name_field_id",
|
||||
"field_ids",
|
||||
"epic_link",
|
||||
"priority_map",
|
||||
"priority_value_key",
|
||||
"labels",
|
||||
"status_labels",
|
||||
"kind_labels",
|
||||
"comment_marker",
|
||||
"transitions_map",
|
||||
"link_types",
|
||||
"board_id",
|
||||
"pacing",
|
||||
"min_agent_version",
|
||||
"settings_version"
|
||||
],
|
||||
"defaults": {
|
||||
"auto_refresh_minutes": 60,
|
||||
"backup_keep": 30,
|
||||
"closed_statuses": [
|
||||
"Готово",
|
||||
"Снято"
|
||||
],
|
||||
"comment_marker": "",
|
||||
"epic_link": {},
|
||||
"epic_name_field_id": null,
|
||||
"field_ids": {},
|
||||
"initialized": false,
|
||||
"issue_types": {},
|
||||
"kind_labels": {},
|
||||
"labels": [],
|
||||
"link_types": {},
|
||||
"min_agent_version": 1,
|
||||
"pacing": {
|
||||
"ensure_attachment": 30,
|
||||
"ensure_comment": 30,
|
||||
"ensure_fields": 120,
|
||||
"ensure_issue": 120,
|
||||
"ensure_link": 30,
|
||||
"ensure_sprint": 30,
|
||||
"ensure_status": 30,
|
||||
"fetch_attachment": 2,
|
||||
"refresh": 2
|
||||
},
|
||||
"port": 8790,
|
||||
"priority_map": {},
|
||||
"priority_value_key": "name",
|
||||
"project_key": "AURORA",
|
||||
"relay_dir": null,
|
||||
"settings_version": 1,
|
||||
"status_labels": {},
|
||||
"transitions_map": {}
|
||||
},
|
||||
"protected": [
|
||||
"schema_version",
|
||||
"settings_version",
|
||||
"tokens"
|
||||
]
|
||||
}
|
||||
44
tests/test_baseline.py
Normal file
44
tests/test_baseline.py
Normal file
@ -0,0 +1,44 @@
|
||||
"""Эталоны внешнего поведения (golden): MCP-инструменты, маршруты API, настройки, схема базы.
|
||||
Перестройка на модули обязана их сохранить. Запись эталонов: BASELINE_WRITE=1 uv run pytest tests/test_baseline.py."""
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from jira_hub import api, db, mcp_server, settings
|
||||
|
||||
FIXTURES = Path(__file__).parent / "fixtures" / "baseline"
|
||||
|
||||
|
||||
def _check(name, snapshot):
|
||||
path = FIXTURES / f"{name}.expected.json"
|
||||
text = json.dumps(snapshot, ensure_ascii=False, indent=2, sort_keys=True) + "\n"
|
||||
if os.environ.get("BASELINE_WRITE") == "1":
|
||||
FIXTURES.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(text, encoding="utf-8")
|
||||
assert path.read_text(encoding="utf-8") == text
|
||||
|
||||
|
||||
def test_mcp_tools():
|
||||
tools = asyncio.run(mcp_server.mcp.list_tools())
|
||||
_check("mcp_tools", [{"name": t.name, "description": t.description, "inputSchema": t.inputSchema}
|
||||
for t in tools])
|
||||
|
||||
|
||||
def test_routes():
|
||||
rows = [[method, pattern, scope, fn.__name__] for method, pattern, fn, scope in api.ROUTES]
|
||||
_check("routes", sorted(rows, key=lambda r: (r[1], r[0])))
|
||||
|
||||
|
||||
def test_settings():
|
||||
_check("settings", {"defaults": settings.DEFAULTS, "agent_keys": settings.AGENT_KEYS,
|
||||
"protected": sorted(settings.PROTECTED)})
|
||||
|
||||
|
||||
def test_schema(tmp_path):
|
||||
conn = db.connect(tmp_path / "hub.sqlite")
|
||||
rows = conn.execute("SELECT type, name, sql FROM sqlite_master WHERE name NOT LIKE 'sqlite_%' "
|
||||
"ORDER BY type, name").fetchall()
|
||||
version = db._schema_version(conn)
|
||||
conn.close()
|
||||
_check("schema", {"schema_version": version, "objects": [[r[0], r[1], r[2]] for r in rows]})
|
||||
Loading…
x
Reference in New Issue
Block a user