diff --git a/.claude/skills/db-dump-xml/SKILL.md b/.claude/skills/db-dump-xml/SKILL.md index 42aa0630e..db1ad64be 100644 --- a/.claude/skills/db-dump-xml/SKILL.md +++ b/.claude/skills/db-dump-xml/SKILL.md @@ -66,13 +66,17 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/db-dump-xml.ps1" < | Режим | Описание | |-------|----------| -| `Full` | Полная выгрузка — все объекты конфигурации | -| `Changes` | Инкрементальная — только изменённые с последней выгрузки (использует ConfigDumpInfo.xml) | +| `Full` | Полная выгрузка — все объекты конфигурации. Пишет поверх каталога: файлы, которых нет в конфигурации, остаются | +| `Changes` | Инкрементальная — только изменённые с последней выгрузки (по `ConfigDumpInfo.xml`). Если выгрузки в каталоге ещё нет (пусто или только `.git`, README), выгружает полностью | | `Partial` | Частичная — выбранные объекты из параметра `-Objects` | | `UpdateInfo` | Обновить только ConfigDumpInfo.xml без выгрузки файлов | > Если пользователь просит выгрузить конкретные объекты — используй `-Mode Partial` с `-Objects`. +`Changes` отказывает, если в каталоге есть выгрузка без `ConfigDumpInfo.xml` или каталог выгружен +в другой версии формата (другой платформой). `-Mode Full` в таком случае перевыгрузит всё дерево, +и изменятся все файлы, поэтому решение о полной выгрузке оставь пользователю. + ## Примеры ```powershell diff --git a/.claude/skills/db-dump-xml/scripts/db-dump-xml.ps1 b/.claude/skills/db-dump-xml/scripts/db-dump-xml.ps1 index 9560cae62..df03f5e8a 100644 --- a/.claude/skills/db-dump-xml/scripts/db-dump-xml.ps1 +++ b/.claude/skills/db-dump-xml/scripts/db-dump-xml.ps1 @@ -1,4 +1,4 @@ -# db-dump-xml v1.22 — Dump 1C configuration to XML files +# db-dump-xml v1.23 — Dump 1C configuration to XML files # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # NB: *nix-раскладку платформы (/opt/1cv8//1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется. <# @@ -8,7 +8,7 @@ .DESCRIPTION Выполняет выгрузку конфигурации 1С в файлы в четырёх режимах: - Full: полная выгрузка всей конфигурации - - Changes: инкрементальная выгрузка изменённых объектов + - Changes: инкрементальная выгрузка изменённых объектов (в пустой каталог — полная) - Partial: выгрузка конкретных объектов из списка - UpdateInfo: обновление только ConfigDumpInfo.xml @@ -519,6 +519,32 @@ function Test-DirNonEmpty { return (Test-Path $Path -PathType Container) -and ([bool](Get-ChildItem -LiteralPath $Path -Force -ErrorAction SilentlyContinue | Select-Object -First 1)) } +function Copy-TreeOver { + # Скопировать дерево поверх каталога: файлы заменяются, лишнее в приёмнике остаётся — + # так пишет полную выгрузку конфигуратор (устаревшие объекты, .git и прочее не трогает). + # Через .NET: исключение на первой же ошибке (а не пропуск файла с продолжением), и без + # накладных расходов командлетов на десятках тысяч файлов. Пустые каталоги не переносятся. + param([string]$From, [string]$To) + $root = [System.IO.Path]::GetFullPath($From).TrimEnd('\') + $dest = (Resolve-Path -LiteralPath $To).ProviderPath + foreach ($f in [System.IO.Directory]::EnumerateFiles($root, '*', [System.IO.SearchOption]::AllDirectories)) { + $dst = [System.IO.Path]::Combine($dest, $f.Substring($root.Length + 1)) + [void][System.IO.Directory]::CreateDirectory([System.IO.Path]::GetDirectoryName($dst)) + [System.IO.File]::Copy($f, $dst, $true) + } +} + +function Write-FormatMismatchHint { + # Отказ инкрементальной выгрузки из-за версии формата: каталог выгружен другой платформой. + # Тексты: 1cv8 — «версия формата платформы отличается от версии формата выгрузки», + # ibcmd — «Версия формат выгрузки и платформы не совпадают». + param([string]$Text) + if ($Text -match 'верси\S* формат\S* .{0,40}(отличается|не совпадают)') { + Write-Host "[подсказка] Каталог выгружен в другой версии формата. -Mode Full перевыгрузит конфигурацию" -ForegroundColor Yellow + Write-Host " целиком в формате текущей платформы — изменятся все файлы каталога." -ForegroundColor Yellow + } +} + $engine = if ((Split-Path $V8Path -Leaf) -match '^ibcmd') { "ibcmd" } else { "1cv8" } # --- Resolve additional arguments for the selected engine --- @@ -567,6 +593,26 @@ if ($Mode -eq "Partial" -and -not $Objects) { exit 1 } +# --- Changes: инкремент ведётся от файла версий прошлой выгрузки --- +# Без ConfigDumpInfo.xml платформа инкремент отвергает. Выгрузки в каталоге ещё нет (пусто или +# только .git, README и т.п.) — первая выгрузка, она полная. Выгрузка есть, а файла версий нет — +# не трогаем: полная выгрузка перепишет её целиком, и это решение пользователя. У -AllExtensions +# файлы версий лежат в подкаталогах расширений — их проверяет платформа. +if ($Mode -eq "Changes") { + $firstDump = if ($AllExtensions) { -not (Test-DirNonEmpty $ConfigDir) } else { + -not (Test-Path -LiteralPath (Join-Path $ConfigDir "ConfigDumpInfo.xml")) -and + -not (Test-Path -LiteralPath (Join-Path $ConfigDir "Configuration.xml")) + } + if ($firstDump) { + Write-Host "[note] выгрузки в каталоге ещё нет — первая выгрузка выполняется полностью" -ForegroundColor Yellow + $Mode = "Full" + } elseif (-not $AllExtensions -and -not (Test-Path -LiteralPath (Join-Path $ConfigDir "ConfigDumpInfo.xml"))) { + Write-Host "Error: no ConfigDumpInfo.xml in $ConfigDir — incremental dump (Changes) is impossible" -ForegroundColor Red + Write-Host " -Mode Full выгрузит конфигурацию целиком поверх каталога." -ForegroundColor Yellow + exit 1 + } +} + # --- Create output dir if needed --- if (-not (Test-Path $ConfigDir)) { New-Item -ItemType Directory -Path $ConfigDir -Force | Out-Null @@ -584,11 +630,23 @@ try { Write-Host "Error: ibcmd config export supports hierarchical format only (use -Format Hierarchical or 1cv8)" -ForegroundColor Red exit 1 } - if ($AllExtensions) { - $arguments = @("infobase", "config", "export", "all-extensions", "$ConfigDir", "--db-path=$InfoBasePath") - } elseif ($Mode -eq "UpdateInfo") { + # Полный экспорт ibcmd в непустой каталог отказывает («Каталог не пуст»), а конфигуратор + # пишет поверх. Чтобы движки вели себя одинаково, такой экспорт идёт во временный каталог + # и копируется поверх ConfigDir. + if ($AllExtensions -and $Mode -eq "Changes") { + Write-Host "[note] ibcmd выгружает все расширения только полностью" -ForegroundColor Yellow + } + $overlay = ($Mode -ne "Partial") -and ($AllExtensions -or $Mode -eq "Full") -and (Test-DirNonEmpty $ConfigDir) + $outDir = if ($overlay) { Join-Path $tempDir "export" } else { $ConfigDir } + $dataDir = Join-Path $tempDir "data" + New-Item -ItemType Directory -Path $dataDir -Force | Out-Null + if ($Mode -eq "UpdateInfo") { + # Раньше развилки по -AllExtensions: иначе вместо обновления файла версий ушла бы + # полная выгрузка всех расширений. Write-Host "Error: ibcmd config export does not support Mode UpdateInfo; use 1cv8" -ForegroundColor Red exit 1 + } elseif ($AllExtensions) { + $arguments = @("infobase", "config", "export", "all-extensions", "$outDir", "--db-path=$InfoBasePath") } elseif ($Mode -eq "Partial") { $objList = @($Objects -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ }) $arguments = @("infobase", "config", "export", "objects") + $objList @@ -597,26 +655,36 @@ try { } else { $arguments = @("infobase", "config", "export", "--db-path=$InfoBasePath") if ($Extension) { $arguments += "--extension=$Extension" } - $arguments += "$ConfigDir" + # Инкремент — --sync; без --force: при другой версии формата ibcmd честно отказывает. + if ($Mode -eq "Changes") { $arguments += "--sync" } + $arguments += "$outDir" } if ($UserName) { $arguments += "--user=$UserName" } if ($Password) { $arguments += "--password=$Password" } - $arguments += "--data=$tempDir" + $arguments += "--data=$dataDir" $arguments += $extraArgs Write-Host "Running: ibcmd $(Protect-Secrets ((Format-ArgsForDisplay $arguments $engine) -join ' ') @($Password, $UserName))" $__ib = Invoke-PlatformProcess $V8Path $arguments $output = $__ib.Output $exitCode = $__ib.ExitCode - $outMissing = ($exitCode -eq 0) -and -not (Test-DirNonEmpty $ConfigDir) + $outMissing = ($exitCode -eq 0) -and -not (Test-DirNonEmpty $outDir) if ($outMissing) { $exitCode = 1 } + $copyError = $null + if ($exitCode -eq 0 -and $overlay) { + try { Copy-TreeOver $outDir $ConfigDir } catch { $copyError = $_.Exception.Message; $exitCode = 1 } + } if ($exitCode -eq 0) { Write-Host "Configuration exported successfully to: $ConfigDir" -ForegroundColor Green + } elseif ($copyError) { + Write-Host "Error: export succeeded, but copying it over $ConfigDir failed: $copyError" -ForegroundColor Red + Write-Host " Каталог мог остаться частично обновлённым — повторите выгрузку." -ForegroundColor Yellow } elseif ($outMissing) { Write-Host "Error: exit code 0 but no files under $ConfigDir — configuration was not exported" -ForegroundColor Red } else { Write-Host "Error exporting configuration (code: $exitCode)" -ForegroundColor Red } Write-PlatformOutput $output + if ($exitCode -ne 0) { Write-FormatMismatchHint $output } exit $exitCode } @@ -647,8 +715,8 @@ try { } "Changes" { Write-Host "Executing incremental configuration dump..." + # Без -force: с ним при другой версии формата платформа молча выгружает всё заново. $arguments += "-update" - $arguments += "-force" } "Partial" { Write-Host "Executing partial configuration dump..." @@ -699,6 +767,7 @@ try { Write-Host "Error dumping configuration (code: $exitCode)" -ForegroundColor Red } + $logContent = $null if (Test-Path $outFile) { $logContent = Get-Content $outFile -Raw -ErrorAction SilentlyContinue if ($logContent) { @@ -708,6 +777,7 @@ try { } } Write-PlatformOutput $__v8.Output + if ($exitCode -ne 0) { Write-FormatMismatchHint "$logContent`n$($__v8.Output)" } exit $exitCode diff --git a/.claude/skills/db-dump-xml/scripts/db-dump-xml.py b/.claude/skills/db-dump-xml/scripts/db-dump-xml.py index 1b45b8781..729fb80e7 100644 --- a/.claude/skills/db-dump-xml/scripts/db-dump-xml.py +++ b/.claude/skills/db-dump-xml/scripts/db-dump-xml.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# db-dump-xml v1.22 — Dump 1C configuration to XML files +# db-dump-xml v1.23 — Dump 1C configuration to XML files # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse @@ -494,6 +494,27 @@ def dir_nonempty(path): return os.path.isdir(path) and any(os.scandir(path)) +def copy_tree_over(src, dst): + """Скопировать дерево поверх каталога: файлы заменяются, лишнее в приёмнике остаётся — + так пишет полную выгрузку конфигуратор (устаревшие объекты, .git и прочее не трогает). + Исключение на первой же ошибке. Пустые каталоги не переносятся.""" + for base, _dirs, files in os.walk(src): + rel = os.path.relpath(base, src) + target = dst if rel == "." else os.path.join(dst, rel) + for name in files: + os.makedirs(target, exist_ok=True) + shutil.copy2(os.path.join(base, name), os.path.join(target, name)) + + +def print_format_mismatch_hint(text): + """Отказ инкрементальной выгрузки из-за версии формата: каталог выгружен другой платформой. + Тексты: 1cv8 — «версия формата платформы отличается от версии формата выгрузки», + ibcmd — «Версия формат выгрузки и платформы не совпадают».""" + if re.search(r"верси\S* формат\S* .{0,40}(отличается|не совпадают)", text or "", re.IGNORECASE): + print("[подсказка] Каталог выгружен в другой версии формата. -Mode Full перевыгрузит конфигурацию") + print(" целиком в формате текущей платформы — изменятся все файлы каталога.") + + def _redact(text, *secrets): """Redact literal secret values (password, user) from a display string — precise, never touches lookalike paths.""" @@ -604,6 +625,25 @@ def main(): print("Error: -Objects or -ObjectsFile required for Partial mode") sys.exit(1) + # --- Changes: инкремент ведётся от файла версий прошлой выгрузки --- + # Без ConfigDumpInfo.xml платформа инкремент отвергает. Пустой каталог — первая выгрузка, она + # полная. Непустой без файла версий не трогаем: полная выгрузка перепишет его целиком, и это + # решение пользователя. У -AllExtensions файлы версий лежат в подкаталогах расширений — их + # проверяет платформа. + if args.Mode == "Changes": + if args.AllExtensions: + first_dump = not dir_nonempty(args.ConfigDir) + else: + first_dump = (not os.path.exists(os.path.join(args.ConfigDir, "ConfigDumpInfo.xml")) + and not os.path.exists(os.path.join(args.ConfigDir, "Configuration.xml"))) + if first_dump: + print("[note] выгрузки в каталоге ещё нет — первая выгрузка выполняется полностью") + args.Mode = "Full" + elif not args.AllExtensions and not os.path.exists(os.path.join(args.ConfigDir, "ConfigDumpInfo.xml")): + print(f"Error: no ConfigDumpInfo.xml in {args.ConfigDir} — incremental dump (Changes) is impossible") + print(" -Mode Full выгрузит конфигурацию целиком поверх каталога.") + sys.exit(1) + # --- Create output dir if needed --- if not os.path.exists(args.ConfigDir): os.makedirs(args.ConfigDir, exist_ok=True) @@ -614,11 +654,25 @@ def main(): if args.Format == "Plain": print("Error: ibcmd config export supports hierarchical format only (use -Format Hierarchical or 1cv8)") sys.exit(1) - if args.AllExtensions: - arguments = ["infobase", "config", "export", "all-extensions", args.ConfigDir, f"--db-path={args.InfoBasePath}"] - elif args.Mode == "UpdateInfo": + # Полный экспорт ibcmd в непустой каталог отказывает («Каталог не пуст»), а конфигуратор + # пишет поверх. Чтобы движки вели себя одинаково, такой экспорт идёт во временный каталог + # и копируется поверх ConfigDir. + if args.AllExtensions and args.Mode == "Changes": + print("[note] ibcmd выгружает все расширения только полностью") + overlay = (args.Mode != "Partial" and (args.AllExtensions or args.Mode == "Full") + and dir_nonempty(args.ConfigDir)) + out_dir = args.ConfigDir + if overlay: + export_tmp = tempfile.mkdtemp(prefix="ibcmd_export_") + atexit.register(shutil.rmtree, export_tmp, ignore_errors=True) + out_dir = os.path.join(export_tmp, "export") + if args.Mode == "UpdateInfo": + # Раньше развилки по -AllExtensions: иначе вместо обновления файла версий ушла бы + # полная выгрузка всех расширений. print("Error: ibcmd config export does not support Mode UpdateInfo; use 1cv8") sys.exit(1) + elif args.AllExtensions: + arguments = ["infobase", "config", "export", "all-extensions", out_dir, f"--db-path={args.InfoBasePath}"] elif args.Mode == "Partial": obj_list = [o.strip() for o in args.Objects.split(",") if o.strip()] arguments = ["infobase", "config", "export", "objects"] + obj_list @@ -629,7 +683,10 @@ def main(): arguments = ["infobase", "config", "export", f"--db-path={args.InfoBasePath}"] if args.Extension: arguments.append(f"--extension={args.Extension}") - arguments.append(args.ConfigDir) + # Инкремент — --sync; без --force: при другой версии формата ibcmd честно отказывает. + if args.Mode == "Changes": + arguments.append("--sync") + arguments.append(out_dir) ib_data = tempfile.mkdtemp(prefix="ibcmd_data_") atexit.register(shutil.rmtree, ib_data, ignore_errors=True) if args.UserName: @@ -641,15 +698,28 @@ def main(): print(f"Running: ibcmd {_redact(' '.join(format_args_for_display(arguments, engine)), args.Password, args.UserName)}") result = run_ibcmd([v8path] + arguments, bool(args.UserName)) exit_code = result.returncode - out_missing = exit_code == 0 and not dir_nonempty(args.ConfigDir) + out_missing = exit_code == 0 and not dir_nonempty(out_dir) if out_missing: exit_code = 1 + copy_error = None + if exit_code == 0 and overlay: + try: + copy_tree_over(out_dir, args.ConfigDir) + except OSError as e: + copy_error = str(e) + exit_code = 1 if exit_code == 0: print(f"Configuration exported successfully to: {args.ConfigDir}") + elif copy_error: + print(f"Error: export succeeded, but copying it over {args.ConfigDir} failed: {copy_error}") + print(" Каталог мог остаться частично обновлённым — повторите выгрузку.") elif out_missing: print(f"Error: exit code 0 but no files under {args.ConfigDir} — configuration was not exported") else: print(f"Error exporting configuration (code: {exit_code})") + print_platform_output(result) + if exit_code != 0: + print_format_mismatch_hint((result.stdout or "") + (result.stderr or "")) sys.exit(exit_code) # --- Temp dir --- @@ -682,8 +752,8 @@ def main(): print("Executing full configuration dump...") elif args.Mode == "Changes": print("Executing incremental configuration dump...") + # Без -force: с ним при другой версии формата платформа молча выгружает всё заново. arguments.append("-update") - arguments.append("-force") elif args.Mode == "Partial": print("Executing partial configuration dump...") object_list = [obj.strip() for obj in args.Objects.split(",") if obj.strip()] @@ -730,6 +800,7 @@ def main(): else: print(f"Error dumping configuration (code: {exit_code})") + log_content = "" if os.path.isfile(out_file): try: with open(out_file, "r", encoding="utf-8-sig") as f: @@ -742,6 +813,8 @@ def main(): pass print_platform_output(result) + if exit_code != 0: + print_format_mismatch_hint(log_content + "\n" + (result.stdout or "") + (result.stderr or "")) sys.exit(exit_code) finally: diff --git a/tests/skills/cases/db-dump-xml/changes-empty-dir-full.json b/tests/skills/cases/db-dump-xml/changes-empty-dir-full.json new file mode 100644 index 000000000..8c95561db --- /dev/null +++ b/tests/skills/cases/db-dump-xml/changes-empty-dir-full.json @@ -0,0 +1,23 @@ +{ + "name": "Changes в пустой каталог: первая выгрузка выполняется полностью", + "fakePlatform": { + "log": "" + }, + "args_extra": [ + "-V8Path", + "{fakePlatform}", + "-InfoBasePath", + "{workDir}/ib", + "-ConfigDir", + "{workDir}/out" + ], + "expectError": true, + "expect": { + "stdoutContains": [ + "[note] выгрузки в каталоге ещё нет — первая выгрузка выполняется полностью", + "Executing full configuration dump..." + ], + "stdoutNotContains": "-update" + }, + "noSnapshot": "проверяется, какие ключи ушли платформе; фейк файлов не выгружает, поэтому постусловие навыка справедливо даёт отказ" +} diff --git a/tests/skills/cases/db-dump-xml/changes-git-only-dir-full.json b/tests/skills/cases/db-dump-xml/changes-git-only-dir-full.json new file mode 100644 index 000000000..f898160d0 --- /dev/null +++ b/tests/skills/cases/db-dump-xml/changes-git-only-dir-full.json @@ -0,0 +1,32 @@ +{ + "name": "Changes в каталог без выгрузки (только служебные файлы репозитория): первая выгрузка полная", + "fakePlatform": { + "log": "" + }, + "preRun": [ + { + "writeFile": { + "path": "out/.gitignore", + "content": "*.log\n" + } + } + ], + "args_extra": [ + "-V8Path", + "{fakePlatform}", + "-InfoBasePath", + "{workDir}/ib", + "-ConfigDir", + "{workDir}/out", + "-Mode", + "Changes" + ], + "expect": { + "stdoutContains": [ + "[note] выгрузки в каталоге ещё нет — первая выгрузка выполняется полностью", + "Executing full configuration dump..." + ], + "stdoutNotContains": "-update" + }, + "noSnapshot": "проверяется командная строка платформы; каталог выгрузки — фикстура кейса" +} diff --git a/tests/skills/cases/db-dump-xml/changes-update-without-force.json b/tests/skills/cases/db-dump-xml/changes-update-without-force.json new file mode 100644 index 000000000..66e10c1b2 --- /dev/null +++ b/tests/skills/cases/db-dump-xml/changes-update-without-force.json @@ -0,0 +1,32 @@ +{ + "name": "Changes по прошлой выгрузке: -update без -force", + "fakePlatform": { + "log": "" + }, + "preRun": [ + { + "writeFile": { + "path": "out/ConfigDumpInfo.xml", + "content": "" + } + } + ], + "args_extra": [ + "-V8Path", + "{fakePlatform}", + "-InfoBasePath", + "{workDir}/ib", + "-ConfigDir", + "{workDir}/out", + "-Mode", + "Changes" + ], + "expect": { + "stdoutContains": [ + "Executing incremental configuration dump...", + "-update" + ], + "stdoutNotContains": "-force" + }, + "noSnapshot": "проверяется командная строка платформы; каталог выгрузки — фикстура кейса" +} diff --git a/tests/skills/cases/db-dump-xml/error-changes-no-dumpinfo.json b/tests/skills/cases/db-dump-xml/error-changes-no-dumpinfo.json new file mode 100644 index 000000000..27c1ac227 --- /dev/null +++ b/tests/skills/cases/db-dump-xml/error-changes-no-dumpinfo.json @@ -0,0 +1,32 @@ +{ + "name": "Ошибка: Changes в непустой каталог без ConfigDumpInfo.xml", + "fakePlatform": { + "log": "" + }, + "preRun": [ + { + "writeFile": { + "path": "out/Configuration.xml", + "content": "" + } + } + ], + "args_extra": [ + "-V8Path", + "{fakePlatform}", + "-InfoBasePath", + "{workDir}/ib", + "-ConfigDir", + "{workDir}/out", + "-Mode", + "Changes" + ], + "expectError": true, + "expect": { + "stdoutContains": [ + "no ConfigDumpInfo.xml", + "-Mode Full выгрузит конфигурацию целиком поверх каталога." + ], + "stdoutNotContains": "Running:" + } +} diff --git a/tests/skills/cases/db-dump-xml/error-ibcmd-updateinfo-allext.json b/tests/skills/cases/db-dump-xml/error-ibcmd-updateinfo-allext.json new file mode 100644 index 000000000..dfcd2ba43 --- /dev/null +++ b/tests/skills/cases/db-dump-xml/error-ibcmd-updateinfo-allext.json @@ -0,0 +1,42 @@ +{ + "name": "Ошибка: ibcmd не умеет UpdateInfo — и с -AllExtensions не подменяет его полной выгрузкой (fake platform)", + "preRun": [ + { + "writeFile": { + "path": "ibcmd.cmd", + "content": "@echo off\r\necho ARGS: %*\r\nexit /b 0\r\n" + } + }, + { + "writeFile": { + "path": "ib/1Cv8.1CD", + "content": "stub" + } + }, + { + "writeFile": { + "path": "out/РасшА/ConfigDumpInfo.xml", + "content": "" + } + } + ], + "args_extra": [ + "-V8Path", + "{workDir}\\ibcmd.cmd", + "-InfoBasePath", + "{workDir}\\ib", + "-UserName", + "Admin", + "-ConfigDir", + "{workDir}\\out", + "-Mode", + "UpdateInfo", + "-AllExtensions" + ], + "expectError": true, + "expect": { + "stdoutContains": "ibcmd config export does not support Mode UpdateInfo", + "stdoutNotContains": "ARGS:" + }, + "osOnly": "win32" +} diff --git a/tests/skills/cases/db-dump-xml/format-mismatch-hint.json b/tests/skills/cases/db-dump-xml/format-mismatch-hint.json new file mode 100644 index 000000000..b0461e934 --- /dev/null +++ b/tests/skills/cases/db-dump-xml/format-mismatch-hint.json @@ -0,0 +1,32 @@ +{ + "name": "Отказ инкремента из-за версии формата: подсказка про полную перевыгрузку", + "fakePlatform": { + "log": "\ufeffОбновление XML выгрузки невозможно: версия формата платформы отличается от версии формата выгрузки.\r\nДля синхронизации необходимо выполнить полную выгрузку.\r\n", + "exit": 101 + }, + "preRun": [ + { + "writeFile": { + "path": "out/ConfigDumpInfo.xml", + "content": "" + } + } + ], + "args_extra": [ + "-V8Path", + "{fakePlatform}", + "-InfoBasePath", + "{workDir}/ib", + "-ConfigDir", + "{workDir}/out", + "-Mode", + "Changes" + ], + "expectError": true, + "expect": { + "stdoutContains": [ + "Error dumping configuration (code: 101)", + "[подсказка] Каталог выгружен в другой версии формата." + ] + } +} diff --git a/tests/skills/cases/db-dump-xml/ibcmd-changes-sync.json b/tests/skills/cases/db-dump-xml/ibcmd-changes-sync.json new file mode 100644 index 000000000..90a99a057 --- /dev/null +++ b/tests/skills/cases/db-dump-xml/ibcmd-changes-sync.json @@ -0,0 +1,44 @@ +{ + "name": "ibcmd: Changes — инкремент через --sync, без --force (fake platform)", + "preRun": [ + { + "writeFile": { + "path": "ibcmd.cmd", + "content": "@echo off\r\necho ARGS: %*\r\nexit /b 0\r\n" + } + }, + { + "writeFile": { + "path": "ib/1Cv8.1CD", + "content": "stub" + } + }, + { + "writeFile": { + "path": "out/ConfigDumpInfo.xml", + "content": "" + } + } + ], + "args_extra": [ + "-V8Path", + "{workDir}\\ibcmd.cmd", + "-InfoBasePath", + "{workDir}\\ib", + "-UserName", + "Admin", + "-ConfigDir", + "{workDir}\\out", + "-Mode", + "Changes" + ], + "expect": { + "stdoutContains": [ + "ARGS: infobase config export", + "--sync" + ], + "stdoutNotContains": "--force" + }, + "noSnapshot": "проверяется командная строка в stdout; каталог выгрузки — фикстура кейса", + "osOnly": "win32" +} diff --git a/tests/skills/integration/platform-dump-modes.test.mjs b/tests/skills/integration/platform-dump-modes.test.mjs index 2d5a52a80..2c01bd2b2 100644 --- a/tests/skills/integration/platform-dump-modes.test.mjs +++ b/tests/skills/integration/platform-dump-modes.test.mjs @@ -54,6 +54,17 @@ export const steps = [ script: 'db-dump-xml/scripts/db-dump-xml', args: { '-V8Path': '{v8path}', '-InfoBasePath': '{workDir}/testdb', '-ConfigDir': '{workDir}/dumpA', '-Mode': 'Changes' }, }, + // ── 3b. Changes into a FRESH dir → no ConfigDumpInfo yet, so the first dump must be full ── + { + name: 'db-dump-xml: Changes в пустой каталог (первая выгрузка)', + script: 'db-dump-xml/scripts/db-dump-xml', + args: { '-V8Path': '{v8path}', '-InfoBasePath': '{workDir}/testdb', '-ConfigDir': '{workDir}/dumpC', '-Mode': 'Changes' }, + }, + { + name: 'assert: первая выгрузка записала ConfigDumpInfo.xml', + assertContains: '{workDir}/dumpC/ConfigDumpInfo.xml', + expect: 'ConfigDumpInfo', + }, // ── 4. UpdateInfo into a FRESH dir → proves the mode alone yields non-empty output ── { name: 'db-dump-xml: UpdateInfo', diff --git a/tests/skills/integration/platform-dump-sync.test.mjs b/tests/skills/integration/platform-dump-sync.test.mjs new file mode 100644 index 000000000..4bca25884 --- /dev/null +++ b/tests/skills/integration/platform-dump-sync.test.mjs @@ -0,0 +1,101 @@ +// platform-dump-sync.test.mjs — db-dump-xml Changes/Full поверх существующей выгрузки, оба движка. +// Changes должен быть инкрементом от ConfigDumpInfo.xml (1cv8 -update, ibcmd --sync), а Full в +// непустой каталог — писать поверх и не трогать лишнее, как конфигуратор (ibcmd сам в непустой +// каталог не выгружает). Изменение доставляется в базу загрузкой частями и проверяется маркером. + +export const name = 'Выгрузка поверх существующей: Changes и Full (оба движка)'; +export const setup = 'none'; +export const requiresPlatform = true; +export const engines = ['1cv8', 'ibcmd']; + +export const steps = [ + // ── 1. Минимальная база ── + { + name: 'cf-init: пустая конфигурация', + script: 'cf-init/scripts/cf-init', + args: { '-Name': 'ВыгрузкаПоверх', '-OutputDir': '{workDir}/config' }, + }, + { + name: 'meta-compile: Справочник Товары', + script: 'meta-compile/scripts/meta-compile', + input: { type: 'Catalog', name: 'Товары', codeLength: 9, descriptionLength: 100 }, + args: { '-JsonPath': '{inputFile}', '-OutputDir': '{workDir}/config' }, + }, + { + name: 'cf-edit: регистрация справочника', + script: 'cf-edit/scripts/cf-edit', + input: [{ operation: 'add-childObject', value: 'Catalog.Товары' }], + args: { '-ConfigPath': '{workDir}/config', '-DefinitionFile': '{inputFile}' }, + }, + { + name: 'db-create: файловая ИБ', + script: 'db-create/scripts/db-create', + args: { '-V8Path': '{v8path}', '-InfoBasePath': '{workDir}/testdb' }, + }, + { + name: 'db-load-xml: загрузка конфигурации (Full)', + script: 'db-load-xml/scripts/db-load-xml', + args: { '-V8Path': '{v8path}', '-InfoBasePath': '{workDir}/testdb', '-ConfigDir': '{workDir}/config' }, + }, + + // ── 2. Первая выгрузка режимом по умолчанию (Changes) в несуществующий каталог → полная ── + { + name: 'db-dump-xml: первая выгрузка без -Mode', + script: 'db-dump-xml/scripts/db-dump-xml', + args: { '-V8Path': '{v8path}', '-InfoBasePath': '{workDir}/testdb', '-ConfigDir': '{workDir}/dump' }, + }, + { + name: 'assert: выгрузка содержит ConfigDumpInfo.xml', + assertContains: '{workDir}/dump/ConfigDumpInfo.xml', + expect: 'ConfigDumpInfo', + }, + + // ── 3. Изменение в базе → Changes поверх выгрузки ── + { + name: 'editFile: маркер в Comment справочника', + editFile: '{workDir}/config/Catalogs/Товары.xml', + replace: '', + with: 'syncMARK', + }, + { + name: 'db-load-xml: частичная загрузка Товары', + script: 'db-load-xml/scripts/db-load-xml', + args: { + '-V8Path': '{v8path}', '-InfoBasePath': '{workDir}/testdb', + '-ConfigDir': '{workDir}/config', '-Files': 'Catalogs/Товары.xml', + }, + }, + { + name: 'db-dump-xml: Changes поверх выгрузки', + script: 'db-dump-xml/scripts/db-dump-xml', + args: { '-V8Path': '{v8path}', '-InfoBasePath': '{workDir}/testdb', '-ConfigDir': '{workDir}/dump', '-Mode': 'Changes' }, + }, + { + name: 'assert: Changes выгрузил изменение', + assertContains: '{workDir}/dump/Catalogs/Товары.xml', + expect: 'syncMARK', + }, + + // ── 4. Full в непустой каталог: пишет поверх, лишнее остаётся ── + { + name: 'meta-compile: посторонний объект в каталоге выгрузки', + script: 'meta-compile/scripts/meta-compile', + input: { type: 'Catalog', name: 'Посторонний' }, + args: { '-JsonPath': '{inputFile}', '-OutputDir': '{workDir}/dump' }, + }, + { + name: 'db-dump-xml: Full в непустой каталог', + script: 'db-dump-xml/scripts/db-dump-xml', + args: { '-V8Path': '{v8path}', '-InfoBasePath': '{workDir}/testdb', '-ConfigDir': '{workDir}/dump', '-Mode': 'Full' }, + }, + { + name: 'assert: Full выгрузил конфигурацию', + assertContains: '{workDir}/dump/Catalogs/Товары.xml', + expect: 'syncMARK', + }, + { + name: 'assert: посторонний объект не удалён', + assertContains: '{workDir}/dump/Catalogs/Посторонний.xml', + expect: 'Посторонний', + }, +];