cf-edit add-childObject was a low-level XML-manipulation operation
with no file-existence validation — callers could register a reference
to any Type.Name in Configuration.xml's ChildObjects without the
underlying file existing on disk. Platform then refused to load:
"Файл объекта не существует".
The 4 failing tests (add-objects, remove-object, add-default-role,
set-default-roles) all used this operation with fake references in
either main input or preRun, and had no way to pass verify-snapshots
because the cf-init-ed config had no actual object files.
User observation: this is the tests being wrong, not the skill.
meta-compile/role-compile/subsystem-compile already auto-register
every new object in Configuration.xml as part of their normal flow
(meta-compile.ps1:2949-3068, role-compile.ps1:667-747,
subsystem-compile.ps1:430-506). Nobody should be calling cf-edit
add-childObject to create a new object — they should be calling the
profile skill. cf-edit add-childObject is only for rare recovery
scenarios: rolled-back Configuration.xml with intact object files,
re-import from DB dump that clobbered the root but left srcfiles.
Changes:
1. cf-edit.ps1/py: Do-AddChildObject now checks that the target file
exists at {ConfigDir}/{PluralDir}/{Name}.xml before registering.
On miss, exits 1 with a message that names the expected path and
points the user at the right skill (/meta-compile, /role-compile,
or /subsystem-compile depending on type). TYPE_TO_DIR mapping for
all 44 metadata types covers irregular plurals (FilterCriteria,
BusinessProcesses, ChartsOfAccounts, ChartsOfCharacteristicTypes,
ChartsOfCalculationTypes).
2. Tests: 4 existing cases rewritten to build realistic fixtures via
meta-compile/role-compile preRun (both skills auto-register, so
the resulting Configuration.xml already references the preRun
objects). add-objects now exercises the round-trip recovery
scenario: meta-compile creates Catalog.Товары and Document.ПриходТоваров
(auto-registered) → cf-edit remove-childObject un-registers both
(files remain) → main run re-registers via add-childObject. This
tests exactly the rollback-recovery use case the operation exists for.
3. New add-missing-errors case: negative test with expectError:
"Object file not found". Verifies the new hard-error path.
4. verify-snapshots.mjs: added symmetric expectError handling (runner.mjs
already had it at line 514). If caseData.expectError is set,
expect skill to fail; check stderr substring match; skip db-load
and mark passed. Without this, negative tests would go red in
verify-snapshots even though runner.mjs accepts them.
5. SKILL.md / reference.md: documented the new constraint and the
redirection to profile skills. Kept mention of legitimate use case
(rollback recovery).
Bumped cf-edit.ps1/py v1.0→v1.1.
Verification:
- runner --filter cf-edit (PS1): 2/6 → 7/7 (6 positive + 1 negative)
- runner --filter cf-edit --runtime python: 7/7 (dual-port clean)
- verify-snapshots --skill cf-edit: 2/6 → 7/7
With this landed P3 from debug/snapshot-verify/NEXT-STEPS.md is closed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Регресс-тесты навыков
Snapshot-тестирование скриптов навыков: навык получает вход → генерирует файлы → результат сравнивается с эталоном.
Быстрые, файловые, без зависимости от платформы 1С.
Запуск
node tests/skills/runner.mjs # все кейсы
node tests/skills/runner.mjs cases/meta-compile # один навык
node tests/skills/runner.mjs cases/meta-compile/catalog-basic # один кейс
node tests/skills/runner.mjs --verbose # подробный вывод (дерево)
node tests/skills/runner.mjs --update-snapshots # обновить эталоны
node tests/skills/runner.mjs --runtime python # запуск на PY-версиях
node tests/skills/runner.mjs --json report.json # JSON-отчёт
Exit code: 0 = все прошли, 1 = есть падения.
Что делать при падении
- Смотри case id в выводе — это путь к файлу кейса (можно перезапустить:
node runner.mjs <case-id>) - Открой
.jsonкейса — посмотри что на входе - Открой
snapshots/<кейс>/— посмотри эталон - Если изменение намеренное (доработка навыка) — обнови эталон:
node runner.mjs <case-id> --update-snapshots - Если баг — починить скрипт навыка и перезапустить тест
Как добавить навык
- Создать папку
tests/skills/cases/<имя-навыка>/ - Положить
_skill.json— описание навыка для раннера - Добавить кейсы — по одному
.jsonфайлу на кейс
Формат _skill.json
{
"script": "meta-compile/scripts/meta-compile",
"setup": "empty-config",
"args": [
{ "flag": "-JsonPath", "from": "inputFile" },
{ "flag": "-OutputDir", "from": "workDir" }
],
"snapshot": {
"root": "workDir",
"normalizeUuids": true
}
}
| Поле | Описание |
|---|---|
script |
Путь от .claude/skills/, без расширения. Раннер добавит .ps1 (по умолчанию) или .py |
setup |
Фикстура: "empty-config", "base-config", "none", "fixture:<name>" (из fixtures/ папки навыка), "external:<path>" (реальная выгрузка, read-only, skip если недоступна) |
args |
Маппинг параметров навыка (см. ниже) |
snapshot |
Настройки сравнения: root ("workDir" или "outputPath") и normalizeUuids |
Значения from в args
| Значение | Что подставляется |
|---|---|
"inputFile" |
Путь к temp-файлу с case.input (JSON) |
"workDir" |
Рабочая директория (копия фикстуры) |
"outputPath" |
workDir + case.outputPath |
"workPath" |
workDir + значение из params.<field>. Поле указывается в mapping.field (по умолчанию objectPath) |
"case.<field>" |
Значение из params.<field> (приоритет) или корня кейса |
"switch" |
Флаг без значения (напр. -Detailed) |
"literal" |
Фиксированное значение из mapping.value |
Как добавить кейс
Положить .json файл в папку навыка. Имя файла = имя кейса.
Позитивный кейс (минимальный)
{
"name": "Простой справочник",
"input": { "type": "Catalog", "name": "Валюты" }
}
Раннер проверит: exitCode=0 + выход совпадает со snapshot (если есть).
С параметрами навыка
{
"name": "Обзор справочника",
"params": { "objectPath": "Catalogs/Номенклатура" },
"expect": { "stdoutContains": "Номенклатура" }
}
params — параметры для навыка. Используются через case.<field> и workPath в _skill.json.
С дополнительными CLI-аргументами
{
"name": "Конфигурация с поставщиком",
"params": { "name": "Бухгалтерия" },
"args_extra": ["-Vendor", "Тест", "-Version", "2.0.1"]
}
args_extra — дополнительные аргументы, не описанные в _skill.json, передаются навыку как есть.
С предварительными шагами
{
"name": "Добавление реквизита к справочнику",
"preRun": [
{
"script": "meta-compile/scripts/meta-compile",
"input": { "type": "Catalog", "name": "Контрагенты" },
"args": { "-JsonPath": "{inputFile}", "-OutputDir": "{workDir}" }
}
],
"params": { "objectPath": "Catalogs/Контрагенты" },
"input": { "operations": [{ "op": "add-attribute", "name": "ИНН", "type": "String", "length": 12 }] }
}
preRun — шаги подготовки перед основным навыком. Каждый шаг: script (путь без расширения), input (JSON), args (маппинг с {workDir} и {inputFile} плейсхолдерами).
Кейс с реальной выгрузкой
{
"name": "Реальный справочник Номенклатура (БП)",
"setup": "external:C:/WS/tasks/cfsrc/acc_8.3.24",
"params": { "objectPath": "Catalogs/Номенклатура" },
"expect": { "stdoutContains": "Номенклатура" }
}
setup: "external:<path>" — использует реальную выгрузку конфигурации 1С как read-only рабочую директорию (без копирования). Если путь недоступен — тест пропускается (○ skipped), не падает. Подходит для info/validate навыков, которые не модифицируют файлы.
Негативный кейс
{
"name": "Ошибка: пустое имя",
"input": { "type": "Catalog", "name": "" },
"expectError": true
}
expectError: true — ожидается exitCode≠0. Строковое значение — проверит наличие в stderr.
Все поля кейса
| Поле | Обязательно | Описание |
|---|---|---|
name |
да | Название теста (отображается в отчёте) |
input |
нет | JSON-объект, передаётся навыку через temp-файл |
params |
нет | Параметры для case.<field> и workPath маппинга |
setup |
нет | Переопределение setup из _skill.json |
outputPath |
нет | Относительный путь для навыков с -OutputPath |
args_extra |
нет | Массив дополнительных CLI-аргументов |
preRun |
нет | Массив шагов подготовки (создание объектов и т.п.) |
expect |
нет | Дополнительные проверки: files, stdoutContains |
expectError |
нет | true или строка — ожидается ошибка |
Эталоны (snapshots)
Эталон — директория snapshots/<имя-кейса>/ внутри папки навыка. Содержит ожидаемый выход навыка после нормализации.
Создание / обновление эталонов
node tests/skills/runner.mjs --update-snapshots # все кейсы
node tests/skills/runner.mjs cases/meta-compile --update-snapshots # один навык
node tests/skills/runner.mjs cases/meta-compile/enum --update-snapshots # один кейс
Когда обновлять
- После намеренного изменения логики навыка (новый выход — новый эталон)
- После сертификации: загрузить результат в 1С (
db-load-xml), убедиться что платформа приняла, затем--update-snapshots - Не обновлять если падение — неожиданный побочный эффект (это баг)
Нормализация
Перед сравнением (и при сохранении) применяется:
- UUID →
UUID-001,UUID-002... (по порядку появления, ссылочная целостность сохраняется) - BOM (U+FEFF) — удаляется
- Line endings —
\r\n→\n
Структура
tests/skills/
runner.mjs # тест-раннер
README.md # этот файл
.cache/ # кэш фикстур (в .gitignore)
cases/
<навык>/
_skill.json # конфиг навыка
<кейс>.json # тестовый случай
snapshots/
<кейс>/ # эталон
fixtures/ # broken-фикстуры (для validate-навыков)
<имя>/ # сломанный XML, ссылка: "setup": "fixture:<имя>"