feat(tui): 校验后原子保存 + 时间戳备份(第一轮第五块·地基)

三个配置编辑器共用的这块地基(ADR-0013)。顺序是刻意的:① 先校验(调用方给的 -Validate 回调);② **只有校验通过才备份与写盘** —— 校验没过还留下备份会让人以为"动过了";③ 原子替换走 Write-BaknretAtomicText。返回结果对象而不是抛异常:TUI 要把错误显示出来让人继续改,而不是把界面炸掉("TUI 异常不改退出码"这条同样适用)。

给 Write-BaknretAtomicText 加了一个可选 -Encoding(不传时沿用原行为,现有调用点不受影响)。原因:它原先固定用日志编码(无 BOM),而三个配置文件都是带 BOM 的 —— 丢掉 BOM 会让 5.1 把整个文件按 GBK 读,中文全成乱码。这类"编码悄悄变了"的错不会报错,只会让文件在某一个 PowerShell 版本上读出来是乱码。

判据(断言 10 条):校验不过时不写盘、不产生备份、错误原文能带回去;校验通过后新内容落盘、备份里是**原文**、且写回**保留 UTF-8 BOM**(逐字节验 EF BB BF)。

验收:test.ps1 9/9 全绿(5.1 与 7);真实清单只读冒烟 4/4。
This commit is contained in:
Shuery committed 2026-09-27 16:22:21 +08:00
1 parent 54972ab341
commit 59ec5a21d4
5 files changed
+91 -2

No files matched your search

+27
View File
@@ -1597,6 +1597,33 @@ Test-Case '清单行表与单行替换:只动那一行,注释与其它行逐
Assert-True $threw '行号越界必须抛错'
}
Test-Case '保存配置:校验不过什么都不动,过了才备份 + 原子替换,且保留 BOM' {
$dir = Join-Path $env:TEMP ("bnr-save-" + [guid]::NewGuid().ToString('N').Substring(0, 8))
New-Item -ItemType Directory -Path $dir -Force | Out-Null
$file = Join-Path $dir 'BackupList.txt'
$utf8Bom = New-Object System.Text.UTF8Encoding($true)
[System.IO.File]::WriteAllText($file, "Edge`n", $utf8Bom)
try {
# 1) 校验不过:文件不动、也不产生备份(留备份会让人以为"动过了")
$bad = Save-BakNRetConfigFile -Path $file -Text "坏行`n" -Validate { param($t) @('第 1 行有问题') } -BackupDirectory (Join-Path $dir 'bak')
Assert-Equal $false $bad.Saved '校验不过不能保存'
Assert-True ($bad.Error -like '*第 1 行有问题*') '错误原文要带回来(TUI 要显示它)'
Assert-Equal "Edge`n" ([System.IO.File]::ReadAllText($file)) '文件内容不动'
Assert-True (-not (Test-Path (Join-Path $dir 'bak'))) '校验不过不该产生备份'
# 2) 校验通过:写进去 + 备份里是原文 + 保留 BOM
$ok = Save-BakNRetConfigFile -Path $file -Text "+WindowsTerminal`n" -Validate { param($t) @() } -BackupDirectory (Join-Path $dir 'bak')
Assert-Equal $true $ok.Saved '校验通过应当保存'
Assert-Equal "+WindowsTerminal`n" ([System.IO.File]::ReadAllText($file)) '新内容写进去了'
Assert-True (Test-Path $ok.BackupPath) '应当产生时间戳备份'
Assert-Equal "Edge`n" ([System.IO.File]::ReadAllText($ok.BackupPath)) '备份里是原文'
$bytes = [System.IO.File]::ReadAllBytes($file)
Assert-True ($bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) '写回必须保留 UTF-8 BOM'
} finally {
Remove-Item -LiteralPath $dir -Recurse -Force -ErrorAction SilentlyContinue
}
}
# ============================================================================
Remove-Item -LiteralPath $sandbox -Recurse -Force -ErrorAction SilentlyContinue
Remove-Item -LiteralPath $itemSandbox -Recurse -Force -ErrorAction SilentlyContinue