feat(db-repo): порог на длину вывода и функциональные тесты

Операция над всей конфигурацией перечисляет тысячи объектов, и весь список
уезжал в вывод модели. Списки обрезаются на двадцати позициях, полный уходит
в файл, путь к которому назван. Сырой лог платформы при отказе показывается
хвостом в 200 строк: итог и причину платформа пишет в конце.

Тесты на фейковой платформе — 12 кейсов, разбор лога проверяется без 1С на
логах, снятых со стенда. Ключевой кейс закрывает главное правило навыка:
платформа вернула 1, часть объектов захвачена, навык возвращает 0 с поимённым
предупреждением. Обрезка списка проверена на логе из тридцати объектов —
кейс требует и строку «и ещё 10», и отсутствие двадцать первого объекта
в выводе.

Проверено негативным контролем: намеренно сломанное ожидание роняет кейс,
то есть тесты действительно сверяют вывод, а не проходят вхолостую.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-08-23 13:25:11 +03:00
co-authored by Claude Opus 5
parent 67b8ad5b47
commit 4a49eea422
15 changed files with 598 additions and 28 deletions
+5
View File
@@ -0,0 +1,5 @@
{
"script": "db-repo/scripts/db-repo",
"setup": "none",
"args": []
}
@@ -0,0 +1,47 @@
{
"name": "Помещение без изменений: код 0, но версия не создана — и это сказано",
"osOnly": "win32",
"preRun": [
{
"writeFile": {
"path": "fake.cmd",
"content": "@echo off\r\nrem SELF запоминаем ДО цикла: shift сдвигает и %0, после него %~dp0 указывает не на скрипт\r\nset SELF=%~dp0\r\n:loop\r\nif \"%~1\"==\"\" goto done\r\nif /i \"%~1\"==\"/Out\" set OUT=%~2\r\nshift\r\ngoto loop\r\n:done\r\ncopy /y \"%SELF%log.txt\" \"%OUT%\" >nul\r\nexit /b 0\r\n"
}
},
{
"writeFile": {
"path": "log.txt",
"content": "---- Начало операции с хранилищем конфигурации ----\r\nОбъект не был изменен: Справочник.Номенклатура\r\n---- Операция с хранилищем конфигурации завершена ----\r\nПомещение изменений объектов в хранилище успешно завершено\r\n"
}
},
{
"writeFile": {
"path": "ib/1Cv8.1CD",
"content": "stub"
}
}
],
"args_extra": [
"-V8Path",
"{workDir}\\fake.cmd",
"-InfoBasePath",
"{workDir}\\ib",
"-RepositoryPath",
"{workDir}\\repo",
"-RepositoryUser",
"Admin",
"-Command",
"commit",
"-Objects",
"Справочник.Номенклатура",
"-Comment",
"x"
],
"expect": {
"stdoutContains": [
"Без изменений — не помещались (1):",
"Новая версия в хранилище НЕ создана"
]
},
"noSnapshot": "навык ничего не пишет в рабочий каталог — проверяется разбор лога платформы"
}
@@ -0,0 +1,29 @@
{
"name": "Ошибка: подкоманда не указана",
"osOnly": "win32",
"preRun": [
{
"writeFile": {
"path": "fake.cmd",
"content": "@echo off\r\nrem SELF запоминаем ДО цикла: shift сдвигает и %0, после него %~dp0 указывает не на скрипт\r\nset SELF=%~dp0\r\n:loop\r\nif \"%~1\"==\"\" goto done\r\nif /i \"%~1\"==\"/Out\" set OUT=%~2\r\nshift\r\ngoto loop\r\n:done\r\ncopy /y \"%SELF%log.txt\" \"%OUT%\" >nul\r\nexit /b 0\r\n"
}
},
{
"writeFile": {
"path": "ib/1Cv8.1CD",
"content": "stub"
}
}
],
"args_extra": [
"-V8Path",
"{workDir}\\fake.cmd",
"-InfoBasePath",
"{workDir}\\ib"
],
"expectError": true,
"expect": {
"stdoutContains": "-Command is required"
},
"noSnapshot": "разбор аргументов, платформа не запускается"
}
@@ -0,0 +1,33 @@
{
"name": "Ошибка: хранилище не объявлено в реестре и не задано явно",
"osOnly": "win32",
"preRun": [
{
"writeFile": {
"path": "fake.cmd",
"content": "@echo off\r\nrem SELF запоминаем ДО цикла: shift сдвигает и %0, после него %~dp0 указывает не на скрипт\r\nset SELF=%~dp0\r\n:loop\r\nif \"%~1\"==\"\" goto done\r\nif /i \"%~1\"==\"/Out\" set OUT=%~2\r\nshift\r\ngoto loop\r\n:done\r\ncopy /y \"%SELF%log.txt\" \"%OUT%\" >nul\r\nexit /b 0\r\n"
}
},
{
"writeFile": {
"path": "ib/1Cv8.1CD",
"content": "stub"
}
}
],
"args_extra": [
"-V8Path",
"{workDir}\\fake.cmd",
"-InfoBasePath",
"{workDir}\\ib",
"-Command",
"lock",
"-Objects",
"Справочник.Номенклатура"
],
"expectError": true,
"expect": {
"stdoutContains": "repository path is unknown"
},
"noSnapshot": "разбор аргументов, платформа не запускается"
}
@@ -0,0 +1,38 @@
{
"name": "Ошибка: корень конфигурации с -WithChildren — это вся конфигурация",
"osOnly": "win32",
"preRun": [
{
"writeFile": {
"path": "fake.cmd",
"content": "@echo off\r\nrem SELF запоминаем ДО цикла: shift сдвигает и %0, после него %~dp0 указывает не на скрипт\r\nset SELF=%~dp0\r\n:loop\r\nif \"%~1\"==\"\" goto done\r\nif /i \"%~1\"==\"/Out\" set OUT=%~2\r\nshift\r\ngoto loop\r\n:done\r\ncopy /y \"%SELF%log.txt\" \"%OUT%\" >nul\r\nexit /b 0\r\n"
}
},
{
"writeFile": {
"path": "ib/1Cv8.1CD",
"content": "stub"
}
}
],
"args_extra": [
"-V8Path",
"{workDir}\\fake.cmd",
"-InfoBasePath",
"{workDir}\\ib",
"-RepositoryPath",
"{workDir}\\repo",
"-RepositoryUser",
"Admin",
"-Command",
"lock",
"-Objects",
"Конфигурация",
"-WithChildren"
],
"expectError": true,
"expect": {
"stdoutContains": "means the WHOLE configuration"
},
"noSnapshot": "разбор аргументов, платформа не запускается"
}
@@ -0,0 +1,31 @@
{
"name": "Ошибка: unbind неоднозначен — unlock или disconnect",
"osOnly": "win32",
"preRun": [
{
"writeFile": {
"path": "fake.cmd",
"content": "@echo off\r\nrem SELF запоминаем ДО цикла: shift сдвигает и %0, после него %~dp0 указывает не на скрипт\r\nset SELF=%~dp0\r\n:loop\r\nif \"%~1\"==\"\" goto done\r\nif /i \"%~1\"==\"/Out\" set OUT=%~2\r\nshift\r\ngoto loop\r\n:done\r\ncopy /y \"%SELF%log.txt\" \"%OUT%\" >nul\r\nexit /b 0\r\n"
}
},
{
"writeFile": {
"path": "ib/1Cv8.1CD",
"content": "stub"
}
}
],
"args_extra": [
"-V8Path",
"{workDir}\\fake.cmd",
"-InfoBasePath",
"{workDir}\\ib",
"-Command",
"unbind"
],
"expectError": true,
"expect": {
"stdoutContains": "'unbind' is ambiguous"
},
"noSnapshot": "разбор аргументов, платформа не запускается"
}
@@ -0,0 +1,31 @@
{
"name": "Ошибка: неизвестная подкоманда",
"osOnly": "win32",
"preRun": [
{
"writeFile": {
"path": "fake.cmd",
"content": "@echo off\r\nrem SELF запоминаем ДО цикла: shift сдвигает и %0, после него %~dp0 указывает не на скрипт\r\nset SELF=%~dp0\r\n:loop\r\nif \"%~1\"==\"\" goto done\r\nif /i \"%~1\"==\"/Out\" set OUT=%~2\r\nshift\r\ngoto loop\r\n:done\r\ncopy /y \"%SELF%log.txt\" \"%OUT%\" >nul\r\nexit /b 0\r\n"
}
},
{
"writeFile": {
"path": "ib/1Cv8.1CD",
"content": "stub"
}
}
],
"args_extra": [
"-V8Path",
"{workDir}\\fake.cmd",
"-InfoBasePath",
"{workDir}\\ib",
"-Command",
"bogus"
],
"expectError": true,
"expect": {
"stdoutContains": "unknown command 'bogus'"
},
"noSnapshot": "разбор аргументов, платформа не запускается"
}
@@ -0,0 +1,38 @@
{
"name": "Ошибка: lock без -Objects затронул бы всю конфигурацию",
"osOnly": "win32",
"preRun": [
{
"writeFile": {
"path": "fake.cmd",
"content": "@echo off\r\nrem SELF запоминаем ДО цикла: shift сдвигает и %0, после него %~dp0 указывает не на скрипт\r\nset SELF=%~dp0\r\n:loop\r\nif \"%~1\"==\"\" goto done\r\nif /i \"%~1\"==\"/Out\" set OUT=%~2\r\nshift\r\ngoto loop\r\n:done\r\ncopy /y \"%SELF%log.txt\" \"%OUT%\" >nul\r\nexit /b 0\r\n"
}
},
{
"writeFile": {
"path": "ib/1Cv8.1CD",
"content": "stub"
}
}
],
"args_extra": [
"-V8Path",
"{workDir}\\fake.cmd",
"-InfoBasePath",
"{workDir}\\ib",
"-RepositoryPath",
"{workDir}\\repo",
"-RepositoryUser",
"Admin",
"-Command",
"lock"
],
"expectError": true,
"expect": {
"stdoutContains": [
"would affect the WHOLE configuration",
"подтвердите явно: -All"
]
},
"noSnapshot": "разбор аргументов, платформа не запускается"
}
@@ -0,0 +1,46 @@
{
"name": "Реквизит не объект хранилища: подставляется владелец, подмена проговаривается",
"osOnly": "win32",
"preRun": [
{
"writeFile": {
"path": "fake.cmd",
"content": "@echo off\r\nrem SELF запоминаем ДО цикла: shift сдвигает и %0, после него %~dp0 указывает не на скрипт\r\nset SELF=%~dp0\r\n:loop\r\nif \"%~1\"==\"\" goto done\r\nif /i \"%~1\"==\"/Out\" set OUT=%~2\r\nshift\r\ngoto loop\r\n:done\r\ncopy /y \"%SELF%log.txt\" \"%OUT%\" >nul\r\nexit /b 0\r\n"
}
},
{
"writeFile": {
"path": "log.txt",
"content": "---- Начало операции с хранилищем конфигурации ----\r\nОбъект захвачен для редактирования: Справочник.Контрагенты\r\n---- Операция с хранилищем конфигурации завершена ----\r\nЗахват объектов в хранилище успешно завершен\r\n"
}
},
{
"writeFile": {
"path": "ib/1Cv8.1CD",
"content": "stub"
}
}
],
"args_extra": [
"-V8Path",
"{workDir}\\fake.cmd",
"-InfoBasePath",
"{workDir}\\ib",
"-RepositoryPath",
"{workDir}\\repo",
"-RepositoryUser",
"Admin",
"-Command",
"lock",
"-Objects",
"Справочник.Контрагенты.Реквизит.Телефон,Справочник.Контрагенты.ТабличнаяЧасть.Контакты"
],
"expect": {
"stdoutContains": [
"части объекта отдельно не захватываются",
"Справочник.Контрагенты.Реквизит.Телефон -> Справочник.Контрагенты",
"Захвачено (1):"
]
},
"noSnapshot": "навык ничего не пишет в рабочий каталог — проверяется разбор лога платформы"
}
@@ -0,0 +1,47 @@
{
"name": "Длинный список объектов обрезается, полный уходит в файл",
"osOnly": "win32",
"preRun": [
{
"writeFile": {
"path": "fake.cmd",
"content": "@echo off\r\nrem SELF запоминаем ДО цикла: shift сдвигает и %0, после него %~dp0 указывает не на скрипт\r\nset SELF=%~dp0\r\n:loop\r\nif \"%~1\"==\"\" goto done\r\nif /i \"%~1\"==\"/Out\" set OUT=%~2\r\nshift\r\ngoto loop\r\n:done\r\ncopy /y \"%SELF%log.txt\" \"%OUT%\" >nul\r\nexit /b 0\r\n"
}
},
{
"writeFile": {
"path": "log.txt",
"content": "---- Начало операции с хранилищем конфигурации ----\r\nОбъект захвачен для редактирования: Справочник.Объект01\r\nОбъект захвачен для редактирования: Справочник.Объект02\r\nОбъект захвачен для редактирования: Справочник.Объект03\r\nОбъект захвачен для редактирования: Справочник.Объект04\r\nОбъект захвачен для редактирования: Справочник.Объект05\r\nОбъект захвачен для редактирования: Справочник.Объект06\r\nОбъект захвачен для редактирования: Справочник.Объект07\r\nОбъект захвачен для редактирования: Справочник.Объект08\r\nОбъект захвачен для редактирования: Справочник.Объект09\r\nОбъект захвачен для редактирования: Справочник.Объект10\r\nОбъект захвачен для редактирования: Справочник.Объект11\r\nОбъект захвачен для редактирования: Справочник.Объект12\r\nОбъект захвачен для редактирования: Справочник.Объект13\r\nОбъект захвачен для редактирования: Справочник.Объект14\r\nОбъект захвачен для редактирования: Справочник.Объект15\r\nОбъект захвачен для редактирования: Справочник.Объект16\r\nОбъект захвачен для редактирования: Справочник.Объект17\r\nОбъект захвачен для редактирования: Справочник.Объект18\r\nОбъект захвачен для редактирования: Справочник.Объект19\r\nОбъект захвачен для редактирования: Справочник.Объект20\r\nОбъект захвачен для редактирования: Справочник.Объект21\r\nОбъект захвачен для редактирования: Справочник.Объект22\r\nОбъект захвачен для редактирования: Справочник.Объект23\r\nОбъект захвачен для редактирования: Справочник.Объект24\r\nОбъект захвачен для редактирования: Справочник.Объект25\r\nОбъект захвачен для редактирования: Справочник.Объект26\r\nОбъект захвачен для редактирования: Справочник.Объект27\r\nОбъект захвачен для редактирования: Справочник.Объект28\r\nОбъект захвачен для редактирования: Справочник.Объект29\r\nОбъект захвачен для редактирования: Справочник.Объект30\r\n---- Операция с хранилищем конфигурации завершена ----\r\nЗахват объектов в хранилище успешно завершен\r\n"
}
},
{
"writeFile": {
"path": "ib/1Cv8.1CD",
"content": "stub"
}
}
],
"args_extra": [
"-V8Path",
"{workDir}\\fake.cmd",
"-InfoBasePath",
"{workDir}\\ib",
"-RepositoryPath",
"{workDir}\\repo",
"-RepositoryUser",
"Admin",
"-Command",
"lock",
"-All"
],
"expect": {
"stdoutContains": [
"Захвачено (30):",
"Справочник.Объект20",
"… и ещё 10",
"db-repo-locked.txt"
],
"stdoutNotContains": "Справочник.Объект21"
},
"noSnapshot": "навык ничего не пишет в рабочий каталог — проверяется разбор лога платформы"
}
@@ -0,0 +1,52 @@
{
"name": "Частичный захват на *nix: py-порт даёт тот же вердикт",
"osOnly": [
"darwin",
"linux"
],
"runtimeOnly": "python",
"preRun": [
{
"writeFile": {
"path": "fake.sh",
"content": "#!/bin/sh\n# Фейк платформы для *nix: вычитывает путь из /Out и кладёт туда готовый лог.\n# Значение /Out несёт кавычки ВНУТРИ токена (соглашение 1С, см. run_v8) — в batch их\n# снимает %~2, в sh их надо снять руками, иначе cp целится в имя с кавычками.\nSELF=$(dirname \"$0\")\nOUT=\"\"\nwhile [ $# -gt 0 ]; do\n if [ \"$1\" = \"/Out\" ]; then\n OUT=\"$2\"\n OUT=\"${OUT#\\\"}\"\n OUT=\"${OUT%\\\"}\"\n fi\n shift\ndone\ncp \"$SELF/log.txt\" \"$OUT\"\nexit 1\n",
"executable": true
}
},
{
"writeFile": {
"path": "log.txt",
"content": "---- Начало операции с хранилищем конфигурации ----\r\nОбъект захвачен для редактирования: Справочник.Номенклатура\r\nОбъект захвачен для редактирования другим пользователем: Справочник.Номенклатура.Форма.ФормаЭлемента (Ivanov) \r\n---- Операция с хранилищем конфигурации завершена ----\r\nОшибка захвата объектов в хранилище\r\n"
}
},
{
"writeFile": {
"path": "ib/1Cv8.1CD",
"content": "stub"
}
}
],
"args_extra": [
"-V8Path",
"{workDir}/fake.sh",
"-InfoBasePath",
"{workDir}/ib",
"-RepositoryPath",
"{workDir}/repo",
"-RepositoryUser",
"Admin",
"-Command",
"lock",
"-Objects",
"Справочник.Номенклатура",
"-WithChildren"
],
"expect": {
"stdoutContains": [
"Захвачено (1):",
"держит Ivanov",
"захват выполнен частично"
]
},
"noSnapshot": "навык ничего не пишет в рабочий каталог — проверяется разбор лога платформы"
}
@@ -0,0 +1,49 @@
{
"name": "Частичный захват: платформа вернула 1, навык — 0 с поимённым предупреждением",
"osOnly": "win32",
"preRun": [
{
"writeFile": {
"path": "fake.cmd",
"content": "@echo off\r\nrem SELF запоминаем ДО цикла: shift сдвигает и %0, после него %~dp0 указывает не на скрипт\r\nset SELF=%~dp0\r\n:loop\r\nif \"%~1\"==\"\" goto done\r\nif /i \"%~1\"==\"/Out\" set OUT=%~2\r\nshift\r\ngoto loop\r\n:done\r\ncopy /y \"%SELF%log.txt\" \"%OUT%\" >nul\r\nexit /b 1\r\n"
}
},
{
"writeFile": {
"path": "log.txt",
"content": "---- Начало операции с хранилищем конфигурации ----\r\nОбъект захвачен для редактирования: Справочник.Номенклатура\r\nОбъект захвачен для редактирования другим пользователем: Справочник.Номенклатура.Форма.ФормаЭлемента (Ivanov) \r\n---- Операция с хранилищем конфигурации завершена ----\r\nОшибка захвата объектов в хранилище\r\n"
}
},
{
"writeFile": {
"path": "ib/1Cv8.1CD",
"content": "stub"
}
}
],
"args_extra": [
"-V8Path",
"{workDir}\\fake.cmd",
"-InfoBasePath",
"{workDir}\\ib",
"-RepositoryPath",
"{workDir}\\repo",
"-RepositoryUser",
"Admin",
"-Command",
"lock",
"-Objects",
"Справочник.Номенклатура",
"-WithChildren"
],
"expect": {
"stdoutContains": [
"Захвачено (1):",
"Справочник.Номенклатура",
"Не удалось захватить (1):",
"держит Ivanov",
"захват выполнен частично"
]
},
"noSnapshot": "навык ничего не пишет в рабочий каталог — проверяется разбор лога платформы"
}
@@ -0,0 +1,44 @@
{
"name": "update: платформа отчиталась об успехе без блока операции — база не подключена",
"osOnly": "win32",
"preRun": [
{
"writeFile": {
"path": "fake.cmd",
"content": "@echo off\r\nrem SELF запоминаем ДО цикла: shift сдвигает и %0, после него %~dp0 указывает не на скрипт\r\nset SELF=%~dp0\r\n:loop\r\nif \"%~1\"==\"\" goto done\r\nif /i \"%~1\"==\"/Out\" set OUT=%~2\r\nshift\r\ngoto loop\r\n:done\r\ncopy /y \"%SELF%log.txt\" \"%OUT%\" >nul\r\nexit /b 0\r\n"
}
},
{
"writeFile": {
"path": "log.txt",
"content": "Обновление конфигурации из хранилища успешно завершено\r\n"
}
},
{
"writeFile": {
"path": "ib/1Cv8.1CD",
"content": "stub"
}
}
],
"args_extra": [
"-V8Path",
"{workDir}\\fake.cmd",
"-InfoBasePath",
"{workDir}\\ib",
"-RepositoryPath",
"{workDir}\\repo",
"-RepositoryUser",
"Admin",
"-Command",
"update"
],
"expect": {
"stdoutContains": [
"printed no repository operation block",
"НЕ подключена к хранилищу"
]
},
"noSnapshot": "навык ничего не пишет в рабочий каталог — проверяется разбор лога платформы",
"expectError": true
}