chore: 记录改造前基线
改造开始前的完整状态,作为可回退的基点。此提交之后:Pester 175 项、零依赖套件 101 项全绿;PowerShell 5.1 尚不可用(源文件无 BOM)。 包含此前未提交的在制品:安全描述符套件、Hyper-V 实验环境(tools/lab)、agent 约定(AGENTS.md 与 docs/agents)。 .gitignore 增加 *.key / *.pfx:BackupConfig.psd1 的 PasswordFile 此前默认指向仓库内的 baknret.key,一次 git add -A 就会把口令提交进版本库。默认值在后续提交中改为空。
This commit is contained in:
1 parent
7173e8ae10
commit
2937eb6652
32 files changed
+8775
-1691
No files matched your search
+353
-175
@@ -1,15 +1,18 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
清单"两种写法 + 追加/排除"的 Pester 测试:软件名、手写路径,:+/:- 两者都要生效。
|
||||
清单与名录的"格式契约"Pester 测试:软件名 / 手写路径两种写法,
|
||||
Slot 形状的 SoftwareCatalog.psd1,以及 `::` / `:-` / `:+` / `:encrypt` / `@ Key='Value'`。
|
||||
|
||||
.DESCRIPTION
|
||||
这里覆盖的是清单/名录的**输入格式**契约:
|
||||
这里覆盖的是清单/名录的**输入格式与归档布局**契约(重构后的新契约):
|
||||
* 写法一:直接写 SoftwareCatalog.psd1 里的软件名;
|
||||
* 写法二:用户手写目录(含 \ / 或 % 就按路径处理);
|
||||
* 两种写法都要支持 `:+` 追加与 `:-` 排除;
|
||||
* 名录里一个软件可以挂**对象数组**(每个目录带 Description),运行时会逐条介绍;
|
||||
* 同一条目里出现两个同名目录时,必须在归档前就明确报错(Blocking),
|
||||
而不是把两棵树悄悄混在一起。
|
||||
* 软件名条目 -> 一个归档,归档内是 `<Slot>\<内容>`;Path 是文件时归档内是名为
|
||||
`<Slot>` 的文件(没有扩展名);
|
||||
* 手写路径条目 -> 历史布局 `<末级名>\...`,现有清单不需要改写;
|
||||
* `::` 覆盖 Path(不再是 `:-` 的别名),排除一律写 `:-`;
|
||||
* `:+` / `@ Include=` 是 `<归档内相对路径>:<宿主机绝对路径>`;
|
||||
* 同一条目里两个归档项抢同一个包内位置时,必须在归档前就明确报错(Blocking)。
|
||||
|
||||
跟 BakNRet.Tests.ps1 一样,脚本调用统一走**子进程**:Backup.ps1 / Restore.ps1 结尾会
|
||||
`exit`,同进程 `&` 调用会把 Pester 宿主一起带走。
|
||||
@@ -32,6 +35,8 @@ BeforeAll {
|
||||
$script:Sandbox = Join-Path $env:TEMP ('baknret-formats-' + [guid]::NewGuid().ToString('N').Substring(0, 8))
|
||||
New-Item -ItemType Directory -Path $script:Sandbox -Force | Out-Null
|
||||
|
||||
# 见 BakNRet.Tests.ps1 里的说明:本机沙箱禁止 PowerShell 为捕获原生子进程输出建管道,
|
||||
# 所以走"临时 .cmd + 文件重定向 + Invoke-ExternalCommand(继承 stdio)"这条路。
|
||||
function Invoke-BaknretScript {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$Script,
|
||||
@@ -49,9 +54,24 @@ BeforeAll {
|
||||
if ($value -is [array]) { $arguments += $value } else { $arguments += [string]$value }
|
||||
}
|
||||
|
||||
$lines = & pwsh @arguments 2>&1
|
||||
$outFile = Join-Path ([System.IO.Path]::GetTempPath()) ("bnr-out-" + [guid]::NewGuid().ToString('N') + '.txt')
|
||||
$cmdFile = Join-Path ([System.IO.Path]::GetTempPath()) ("bnr-cmd-" + [guid]::NewGuid().ToString('N') + '.cmd')
|
||||
$argString = (@($arguments | ForEach-Object { '"' + ($_ -replace '"', '\"') + '"' }) -join ' ')
|
||||
$batch = "@echo off`r`n" + 'pwsh ' + $argString + ' > "' + $outFile + '" 2>&1' + "`r`nexit /b %ERRORLEVEL%`r`n"
|
||||
[System.IO.File]::WriteAllText($cmdFile, $batch, [System.Text.UTF8Encoding]::new($false))
|
||||
|
||||
$exitCode = $null
|
||||
$lines = @()
|
||||
try {
|
||||
$exitCode = Invoke-ExternalCommand -FilePath 'cmd.exe' -ArgumentList @('/c', $cmdFile)
|
||||
$lines = @(Get-Content -LiteralPath $outFile -Encoding UTF8 -ErrorAction SilentlyContinue)
|
||||
} finally {
|
||||
Remove-Item -LiteralPath $outFile -Force -ErrorAction SilentlyContinue
|
||||
Remove-Item -LiteralPath $cmdFile -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
return [pscustomobject]@{
|
||||
ExitCode = $LASTEXITCODE
|
||||
ExitCode = $exitCode
|
||||
Lines = @($lines | ForEach-Object { [string]$_ })
|
||||
Output = (($lines | Out-String))
|
||||
}
|
||||
@@ -71,7 +91,7 @@ AfterAll {
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
Describe '软件名录:对象数组写法' {
|
||||
Describe '软件名录:Slot 形状(新契约)' {
|
||||
# ============================================================================
|
||||
|
||||
BeforeAll {
|
||||
@@ -84,249 +104,406 @@ Describe '软件名录:对象数组写法' {
|
||||
New-Item -ItemType Directory -Path $directory -Force | Out-Null
|
||||
Set-Content -LiteralPath (Join-Path $directory 'keep.txt') "keep-$directory"
|
||||
}
|
||||
New-Item -ItemType Directory -Path (Join-Path $script:DirA 'Cache') -Force | Out-Null
|
||||
Set-Content -LiteralPath (Join-Path $script:DirA 'Cache\c.bin') 'cache'
|
||||
$script:CfgFile = Join-Path $script:FormatRoot 'settings.json'
|
||||
Set-Content -LiteralPath $script:CfgFile '{"a":1}'
|
||||
|
||||
# 两个不同父目录下各有一个**同名**子目录 —— 用来看"归档内同名"有没有被拦住
|
||||
# 两个不同父目录下各有一个**同名**子目录 —— 供"归档内同名"冲突测试用
|
||||
$script:CollideRoot = Join-Path $script:FormatRoot 'collide'
|
||||
foreach ($parent in 'p1', 'p2') {
|
||||
New-Item -ItemType Directory -Path (Join-Path $script:CollideRoot "$parent\dupdir") -Force | Out-Null
|
||||
Set-Content -LiteralPath (Join-Path $script:CollideRoot "$parent\dupdir\x.txt") $parent
|
||||
}
|
||||
|
||||
$dirAPath = $script:DirA
|
||||
$dirBPath = $script:DirB
|
||||
$collideP1 = Join-Path $script:CollideRoot 'p1\dupdir'
|
||||
$collideP2 = Join-Path $script:CollideRoot 'p2\dupdir'
|
||||
$script:MissingDir = Join-Path $script:FormatRoot 'not-here'
|
||||
|
||||
$script:FormatCatalog = Write-ListFile -Path (Join-Path $script:FormatRoot 'cat.psd1') -Content @"
|
||||
@{
|
||||
'pair' = @(
|
||||
@{ Path = '$dirAPath'; Description = '第一个目录' }
|
||||
@{ Path = '$dirBPath'; Description = '第二个目录' }
|
||||
)
|
||||
'collide' = @(
|
||||
@{ Path = '$collideP1'; Description = 'p1 里的' }
|
||||
@{ Path = '$collideP2'; Description = 'p2 里的' }
|
||||
)
|
||||
}
|
||||
"@
|
||||
|
||||
$script:MissingDir = Join-Path $script:FormatRoot 'not-here'
|
||||
$missingPath = $script:MissingDir
|
||||
$script:PartialCatalog = Write-ListFile -Path (Join-Path $script:FormatRoot 'cat-partial.psd1') -Content @"
|
||||
@{
|
||||
'pair' = @(
|
||||
@{ Path = '$dirAPath'; Description = '存在' }
|
||||
@{ Path = '$missingPath'; Description = '不存在' }
|
||||
)
|
||||
'pair' = @{
|
||||
A = @{ Path = '$script:DirA'; Description = '第一个 Slot' }
|
||||
B = @{ Path = '$script:DirB'; Description = '第二个 Slot' }
|
||||
}
|
||||
'solo' = @{ Only = @{ Path = '$script:DirA' } }
|
||||
'partial' = @{ Ok = @{ Path = '$script:DirA' }; Gone = @{ Path = '$script:MissingDir' } }
|
||||
'slotex' = @{ Data = @{ Path = '$script:DirA'; Exclude = '!*Cache,logs\' } }
|
||||
'fileapp' = @{ Cfg = @{ Path = '$script:CfgFile'; Encrypt = `$true } }
|
||||
'conflict' = @{ Data = @{ Path = '$script:DirA' } }
|
||||
'legacyarr' = @('$script:DirA', '$script:DirB')
|
||||
'legacydirs' = @{ Dirs = @('$script:DirA', '$script:DirB') }
|
||||
'legacystr' = '$script:DirA'
|
||||
}
|
||||
"@
|
||||
}
|
||||
|
||||
It '一个软件多个目录:顺序与说明都被保留' {
|
||||
$catalog = Get-SoftwareCatalog -Path $script:FormatCatalog -MaxDepth 3
|
||||
It '一个软件多个 Slot:Kind=Multi,Slot 按名排序且说明被保留' {
|
||||
$catalog = Get-SoftwareCatalog -Path $script:FormatCatalog -MaxDepth 3 -NoCache
|
||||
$catalog['pair'].Kind | Should -Be 'Multi'
|
||||
@($catalog['pair'].Items).Count | Should -Be 2
|
||||
$catalog['pair'].Items[0].Resolved | Should -Be $script:DirA
|
||||
$catalog['pair'].Items[0].Description | Should -Be '第一个目录'
|
||||
$catalog['pair'].Items[1].Description | Should -Be '第二个目录'
|
||||
@($catalog['pair'].Slots).Count | Should -Be 2
|
||||
(@($catalog['pair'].Slots | ForEach-Object { $_.Name }) -join ',') | Should -Be 'A,B'
|
||||
$catalog['pair'].Slots[0].Description | Should -Be '第一个 Slot'
|
||||
$catalog['pair'].Slots[1].Description | Should -Be '第二个 Slot'
|
||||
$catalog['pair'].Slots[0].Resolved | Should -Be $script:DirA
|
||||
$catalog['pair'].Slots[1].Resolved | Should -Be $script:DirB
|
||||
}
|
||||
|
||||
It '解析成多个源,每个源带着自己的说明' {
|
||||
It '每个 Slot 都是一个独立的归档项来源(Kind=slot / Origin=catalog)' {
|
||||
$entry = ConvertFrom-BackupListLine -Line 'pair'
|
||||
$resolved = Resolve-BackupEntry -Entry $entry -CatalogPath $script:FormatCatalog -MaxDepth 3
|
||||
@($resolved.Sources).Count | Should -Be 2
|
||||
$resolved.Sources[0].SourcePath | Should -Be $script:DirA
|
||||
$resolved.Sources[0].Description | Should -Be '第一个目录'
|
||||
$resolved.Sources[1].Description | Should -Be '第二个目录'
|
||||
@($resolved.Sources | ForEach-Object { $_.Origin }) | Should -Be @('catalog', 'catalog')
|
||||
@($resolved.Items).Count | Should -Be 2
|
||||
(@($resolved.Items | ForEach-Object { $_.ArchivePath }) -join ',') | Should -Be 'A,B'
|
||||
(@($resolved.Items | ForEach-Object { $_.Kind }) -join ',') | Should -Be 'slot,slot'
|
||||
(@($resolved.Items | ForEach-Object { $_.Origin }) -join ',') | Should -Be 'catalog,catalog'
|
||||
$resolved.Items[0].RealPath | Should -Be $script:DirA
|
||||
$resolved.Items[0].Description | Should -Be '第一个 Slot'
|
||||
$resolved.Items[1].Description | Should -Be '第二个 Slot'
|
||||
}
|
||||
|
||||
It '数组里"当前不存在"的目录仍然产出源(恢复要靠它还原回原位)' {
|
||||
$entry = ConvertFrom-BackupListLine -Line 'pair'
|
||||
$resolved = Resolve-BackupEntry -Entry $entry -CatalogPath $script:PartialCatalog -MaxDepth 3
|
||||
@($resolved.Sources).Count | Should -Be 2
|
||||
@($resolved.Sources | ForEach-Object { $_.SourcePath }) | Should -Contain $script:MissingDir
|
||||
$resolved.Error | Should -Not -BeNullOrEmpty # 有提示
|
||||
$resolved.Blocking | Should -BeNullOrEmpty # 但不算致命
|
||||
It 'Slot 级排除写在 Slot 自己身上(相对本 Slot 的归档根)' {
|
||||
$catalog = Get-SoftwareCatalog -Path $script:FormatCatalog -MaxDepth 3 -NoCache
|
||||
(@($catalog['slotex'].Slots[0].Exclude) -join '|') | Should -Be '!*Cache|logs\'
|
||||
|
||||
$entry = ConvertFrom-BackupListLine -Line 'slotex'
|
||||
$resolved = Resolve-BackupEntry -Entry $entry -CatalogPath $script:FormatCatalog -MaxDepth 3
|
||||
(@($resolved.Items[0].Exclude) -join '|') | Should -Be '!*Cache|logs\'
|
||||
$resolved.HasExcludeOverride | Should -BeFalse
|
||||
}
|
||||
|
||||
It '纯字符串数组写法继续可用' {
|
||||
$plain = Write-ListFile -Path (Join-Path $script:FormatRoot 'cat-plain.psd1') -Content "@{ 'pair2' = @('$script:DirA', '$script:DirB') }"
|
||||
$catalog = Get-SoftwareCatalog -Path $plain -MaxDepth 3
|
||||
$catalog['pair2'].Kind | Should -Be 'Multi'
|
||||
@($catalog['pair2'].Dirs).Count | Should -Be 2
|
||||
It '数组里"当前不存在"的 Slot 仍然产出归档项(恢复要靠它还原回原位)' {
|
||||
$catalog = Get-SoftwareCatalog -Path $script:FormatCatalog -MaxDepth 3 -NoCache
|
||||
$catalog['partial'].Kind | Should -Be 'Partial'
|
||||
@($catalog['partial'].Missing) | Should -Contain $script:MissingDir
|
||||
|
||||
$entry = ConvertFrom-BackupListLine -Line 'partial'
|
||||
$resolved = Resolve-BackupEntry -Entry $entry -CatalogPath $script:FormatCatalog -MaxDepth 3
|
||||
@($resolved.Items).Count | Should -Be 2
|
||||
@($resolved.Items | ForEach-Object { $_.RealPath }) | Should -Contain $script:MissingDir
|
||||
$resolved.Blocking | Should -BeNullOrEmpty # 源不存在不是致命错误
|
||||
}
|
||||
|
||||
It '旧的 @{ Dirs = @(...) } 写法继续可用' {
|
||||
$legacy = Write-ListFile -Path (Join-Path $script:FormatRoot 'cat-legacy.psd1') -Content "@{ 'pair3' = @{ Dirs = @('$script:DirA', '$script:DirB') } }"
|
||||
$catalog = Get-SoftwareCatalog -Path $legacy -MaxDepth 3
|
||||
$catalog['pair3'].Kind | Should -Be 'Multi'
|
||||
@($catalog['pair3'].Dirs).Count | Should -Be 2
|
||||
It '文件 Slot:归档项是文件项(归档里就是名为 Slot 的文件)' {
|
||||
$entry = ConvertFrom-BackupListLine -Line 'fileapp'
|
||||
$resolved = Resolve-BackupEntry -Entry $entry -CatalogPath $script:FormatCatalog -MaxDepth 3
|
||||
@($resolved.Items).Count | Should -Be 1
|
||||
$resolved.Items[0].IsFile | Should -BeTrue
|
||||
$resolved.Items[0].ArchivePath | Should -Be 'Cfg'
|
||||
$resolved.Items[0].RealPath | Should -Be $script:CfgFile
|
||||
$resolved.Encrypt | Should -BeTrue
|
||||
}
|
||||
|
||||
It '数组形式的名录条目在清单里仍然按软件名命名归档' {
|
||||
It '旧的裸字符串 / 字符串数组写法被拒绝(ERROR + 跳过)' {
|
||||
$catalog = Get-SoftwareCatalog -Path $script:FormatCatalog -MaxDepth 3 -NoCache
|
||||
$catalog.ContainsKey('legacystr') | Should -BeFalse
|
||||
$catalog.ContainsKey('legacyarr') | Should -BeFalse
|
||||
}
|
||||
|
||||
It '旧的 @{ Dirs = @(...) } 写法不再展开,留下 Invalid 与原因' {
|
||||
$catalog = Get-SoftwareCatalog -Path $script:FormatCatalog -MaxDepth 3 -NoCache
|
||||
$catalog.ContainsKey('legacydirs') | Should -BeTrue
|
||||
$catalog['legacydirs'].Kind | Should -Be 'Invalid'
|
||||
$catalog['legacydirs'].Error | Should -Not -BeNullOrEmpty
|
||||
@($catalog['legacydirs'].Slots).Count | Should -Be 0
|
||||
}
|
||||
|
||||
It '多 Slot 的名录条目在清单里仍然按软件名命名归档' {
|
||||
$entry = ConvertFrom-BackupListLine -Line 'pair'
|
||||
(Get-ItemArchiveName -Entry $entry -CatalogPath $script:FormatCatalog -MaxDepth 3) | Should -Be 'pair'
|
||||
}
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
Describe '两种写法都要支持 :+ 追加与 :- 排除' {
|
||||
Describe '清单修饰符:新契约格式' {
|
||||
# ============================================================================
|
||||
|
||||
BeforeAll {
|
||||
$script:FormatRoot = Join-Path $script:Sandbox 'format'
|
||||
$script:FormatCatalog = Join-Path $script:FormatRoot 'cat.psd1'
|
||||
$script:DirA = Join-Path $script:FormatRoot 'dirA'
|
||||
$script:DirB = Join-Path $script:FormatRoot 'dirB'
|
||||
$script:FormatCatalog = Join-Path $script:FormatRoot 'cat.psd1'
|
||||
$script:CollideRoot = Join-Path $script:FormatRoot 'collide'
|
||||
}
|
||||
|
||||
It '软件名写法::+ 追加一个目录' {
|
||||
$entry = ConvertFrom-BackupListLine -Line "pair :+ $script:CollideRoot"
|
||||
It ':: 覆盖 Path:单 Slot 条目直接生效' {
|
||||
$entry = ConvertFrom-BackupListLine -Line "solo :: $script:DirB"
|
||||
$entry.Overrides.ContainsKey('Path') | Should -BeTrue
|
||||
$resolved = Resolve-BackupEntry -Entry $entry -CatalogPath $script:FormatCatalog -MaxDepth 3
|
||||
@($resolved.Sources).Count | Should -Be 3
|
||||
@($resolved.Sources | ForEach-Object { $_.Origin }) | Should -Contain 'append-path'
|
||||
}
|
||||
|
||||
It '软件名写法::+ 追加"另一个软件名"会按名录展开成它的全部目录' {
|
||||
$entry = ConvertFrom-BackupListLine -Line 'pair :+ pair'
|
||||
$resolved = Resolve-BackupEntry -Entry $entry -CatalogPath $script:FormatCatalog -MaxDepth 3
|
||||
@($resolved.Sources).Count | Should -Be 4
|
||||
@($resolved.Sources | ForEach-Object { $_.Origin }) | Should -Contain 'append-catalog'
|
||||
}
|
||||
|
||||
# ---- 回归:手写路径的 :+ 以前会被整段丢掉 ----
|
||||
It '[回归] 手写路径写法::+ 追加一个目录' {
|
||||
$entry = ConvertFrom-BackupListLine -Line "$script:DirA :+ $script:DirB"
|
||||
$resolved = Resolve-BackupEntry -Entry $entry -CatalogPath $script:FormatCatalog -MaxDepth 3
|
||||
@($resolved.Sources).Count | Should -Be 2
|
||||
@($resolved.Sources | ForEach-Object { $_.SourcePath }) | Should -Contain $script:DirB
|
||||
@($resolved.Sources | ForEach-Object { $_.Origin }) | Should -Contain 'append-path'
|
||||
}
|
||||
|
||||
It '手写路径写法::- 排除与 :+ 追加并存' {
|
||||
$entry = ConvertFrom-BackupListLine -Line "$script:DirA :+ $script:DirB :- skip.log,!*Cache"
|
||||
$entry.ExcludePatterns.Count | Should -Be 2
|
||||
$resolved = Resolve-BackupEntry -Entry $entry -CatalogPath $script:FormatCatalog -MaxDepth 3
|
||||
@($resolved.Sources).Count | Should -Be 2
|
||||
@($resolved.Items).Count | Should -Be 1
|
||||
$resolved.Items[0].ArchivePath | Should -Be 'Only'
|
||||
$resolved.Items[0].RealPath | Should -Be $script:DirB
|
||||
$resolved.Blocking | Should -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It '软件名与手写路径混在一行也认得(主目录是软件名,追加是路径)' {
|
||||
$entry = ConvertFrom-BackupListLine -Line "pair :+ $script:CollideRoot :- logs\"
|
||||
It ':: 覆盖遇到多 Slot 条目 -> Blocking(不知道给哪一个,绝不猜)' {
|
||||
$entry = ConvertFrom-BackupListLine -Line "pair :: $script:DirB"
|
||||
$resolved = Resolve-BackupEntry -Entry $entry -CatalogPath $script:FormatCatalog -MaxDepth 3
|
||||
$resolved.IsName | Should -BeTrue
|
||||
@($resolved.Sources).Count | Should -Be 3
|
||||
$entry.ExcludePatterns | Should -Be @('logs\')
|
||||
@($resolved.Items).Count | Should -Be 0
|
||||
$resolved.Blocking | Should -Match '不能用一个'
|
||||
}
|
||||
|
||||
It '同一条目里出现两个同名目录 -> Blocking(明确报错,不静默混成一棵树)' {
|
||||
$entry = ConvertFrom-BackupListLine -Line 'collide'
|
||||
It ':- 排除与 :+ 包含并存,顺序任意' {
|
||||
$entry = ConvertFrom-BackupListLine -Line "pair :- logs\,!*Cache :+ Mods:$script:DirB"
|
||||
(@($entry.ExcludePatterns) -join '|') | Should -Be 'logs\|!*Cache'
|
||||
(@($entry.Includes) -join '|') | Should -Be "Mods:$script:DirB"
|
||||
|
||||
$resolved = Resolve-BackupEntry -Entry $entry -CatalogPath $script:FormatCatalog -MaxDepth 3
|
||||
@($resolved.Sources).Count | Should -Be 2
|
||||
$resolved.Blocking | Should -Match '顶层同名'
|
||||
$resolved.HasExcludeOverride | Should -BeTrue
|
||||
$resolved.HasIncludeOverride | Should -BeTrue
|
||||
@($resolved.Items | ForEach-Object { $_.ArchivePath }) | Should -Contain 'Mods'
|
||||
$resolved.Blocking | Should -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It '@ Exclude / @ Include / @ Path 与记号写法等价' {
|
||||
$marks = ConvertFrom-BackupListLine -Line "pair :- logs\ :+ Mods:$script:DirB"
|
||||
$ats = ConvertFrom-BackupListLine -Line "pair @ Exclude='logs\' @ Include='Mods:$script:DirB'"
|
||||
(@($marks.ExcludePatterns) -join '|') | Should -Be (@($ats.ExcludePatterns) -join '|')
|
||||
(@($marks.Includes) -join '|') | Should -Be (@($ats.Includes) -join '|')
|
||||
}
|
||||
|
||||
It ':encrypt / :!encrypt 覆盖名录里的加密默认值' {
|
||||
$base = Resolve-BackupEntry -Entry (ConvertFrom-BackupListLine -Line 'fileapp') -CatalogPath $script:FormatCatalog -MaxDepth 3
|
||||
$base.Encrypt | Should -BeTrue # 名录里 Cfg Slot 标了 Encrypt
|
||||
|
||||
$off = Resolve-BackupEntry -Entry (ConvertFrom-BackupListLine -Line 'fileapp :!encrypt') -CatalogPath $script:FormatCatalog -MaxDepth 3
|
||||
$off.Encrypt | Should -BeFalse
|
||||
|
||||
$on = Resolve-BackupEntry -Entry (ConvertFrom-BackupListLine -Line 'pair :encrypt') -CatalogPath $script:FormatCatalog -MaxDepth 3
|
||||
$on.Encrypt | Should -BeTrue
|
||||
}
|
||||
|
||||
It '遗留写法 @encrypt / @pathname / @root= 仍可解析' {
|
||||
(ConvertFrom-BackupListLine -Line 'pair @encrypt').Overrides['Encrypt'] | Should -BeTrue
|
||||
(ConvertFrom-BackupListLine -Line 'pair @pathname').Flags | Should -Contain 'pathname'
|
||||
(ConvertFrom-BackupListLine -Line 'pair @root=Bar').Flags | Should -Contain 'root=Bar'
|
||||
|
||||
# @pathname 对软件名条目也会改用真实路径命名
|
||||
$entry = ConvertFrom-BackupListLine -Line 'pair @pathname'
|
||||
$expected = Get-BackupBaseName -RawPath $script:DirA
|
||||
(Get-ItemArchiveName -Entry $entry -CatalogPath $script:FormatCatalog -MaxDepth 3) | Should -Be $expected
|
||||
}
|
||||
|
||||
It '同一行里重复写同类记号会累积(不静默丢掉前一条规则)' {
|
||||
# `:+ a :+ b` 与 `:+ a,b` 等价:两条规则都生效。
|
||||
# 静默丢掉前一条排除/追加规则是这工具最不该犯的错,所以这里是"累加"语义。
|
||||
$entry = ConvertFrom-BackupListLine -Line "pair :+ Mods:$script:DirB,More:$script:DirA"
|
||||
(@($entry.Includes) -join '|') | Should -Be "Mods:$script:DirB|More:$script:DirA"
|
||||
|
||||
$repeated = ConvertFrom-BackupListLine -Line "pair :+ Mods:$script:DirB :+ More:$script:DirA"
|
||||
(@($repeated.Includes) -join '|') | Should -Be "Mods:$script:DirB|More:$script:DirA"
|
||||
|
||||
$excludes = ConvertFrom-BackupListLine -Line 'pair :- logs\ :- !*Cache :- temp\'
|
||||
(@($excludes.ExcludePatterns) -join '|') | Should -Be 'logs\|!*Cache|temp\'
|
||||
|
||||
# @ Exclude= 与 :- 也是累加关系
|
||||
$mixed = ConvertFrom-BackupListLine -Line "pair @ Exclude='a' :- b"
|
||||
(@($mixed.ExcludePatterns) -join '|') | Should -Be 'a|b'
|
||||
}
|
||||
|
||||
It '行尾说明与缺少目标的行' {
|
||||
$entry = ConvertFrom-BackupListLine -Line 'pair :- logs\ # 日志可再生'
|
||||
$entry.Comment | Should -Be '日志可再生'
|
||||
(@($entry.ExcludePatterns) -join '|') | Should -Be 'logs\'
|
||||
|
||||
ConvertFrom-BackupListLine -Line ':- logs\' | Should -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
Describe '清单行尾的 `# 说明`' {
|
||||
# ============================================================================
|
||||
|
||||
It '会作为这条目的说明解析出来' {
|
||||
$entry = ConvertFrom-BackupListLine -Line 'Edge :- !*Cache # 缓存可再生'
|
||||
$entry.Path | Should -Be 'Edge'
|
||||
$entry.ExcludePatterns | Should -Be @('!*Cache')
|
||||
$entry.Comment | Should -Be '缓存可再生'
|
||||
}
|
||||
|
||||
It '路径里紧贴的 # 不会被当成注释' {
|
||||
$entry = ConvertFrom-BackupListLine -Line 'C:\a#b\c'
|
||||
$entry.Path | Should -Be 'C:\a#b\c'
|
||||
$entry.Comment | Should -BeNullOrEmpty
|
||||
}
|
||||
|
||||
It '没有说明时 Comment 为空' {
|
||||
ConvertFrom-BackupListLine -Line 'legendary' | Select-Object -ExpandProperty Comment | Should -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
Describe '集成:手写路径 + :+ 追加 的打包与恢复' -Skip:(-not ($script:HasSevenZip -and $script:HasPwsh)) {
|
||||
Describe '集成:Slot 布局的打包与恢复' -Skip:(-not ($script:HasSevenZip -and $script:HasPwsh)) {
|
||||
# ============================================================================
|
||||
|
||||
BeforeAll {
|
||||
$script:AppendRoot = Join-Path $script:Sandbox 'append-e2e'
|
||||
# 刻意放在**两个不同的父目录**下:只有这样才能验证
|
||||
# "恢复时不会把兄弟目录也复制过去"
|
||||
$script:AppendA = Join-Path $script:AppendRoot 'srcA\dirA'
|
||||
$script:AppendB = Join-Path $script:AppendRoot 'srcB\dirB'
|
||||
foreach ($directory in $script:AppendA, $script:AppendB) {
|
||||
New-Item -ItemType Directory -Path $directory -Force | Out-Null
|
||||
}
|
||||
Set-Content -LiteralPath (Join-Path $script:AppendA 'a.txt') 'A'
|
||||
Set-Content -LiteralPath (Join-Path $script:AppendB 'b.txt') 'B'
|
||||
Set-Content -LiteralPath (Join-Path $script:AppendA 'skip.log') 'S'
|
||||
$script:SlotRoot = Join-Path $script:Sandbox 'slots-e2e'
|
||||
$script:SlotAppOne = Join-Path $script:SlotRoot 'apps\AppOne'
|
||||
$script:SlotAppTwo = Join-Path $script:SlotRoot 'apps\AppTwo'
|
||||
$script:SlotCfgDir = Join-Path $script:SlotRoot 'apps\AppCfg'
|
||||
$script:SlotInclude = Join-Path $script:SlotRoot 'psmodules'
|
||||
|
||||
$script:AppendList = Write-ListFile -Path (Join-Path $script:AppendRoot 'list.txt') `
|
||||
-Content "$script:AppendA :+ $script:AppendB :- skip.log`n"
|
||||
$script:AppendBackupDir = Join-Path $script:AppendRoot 'Backups'
|
||||
New-Item -ItemType Directory -Path (Join-Path $script:SlotAppOne 'Cache') -Force | Out-Null
|
||||
New-Item -ItemType Directory -Path (Join-Path $script:SlotAppOne 'sub') -Force | Out-Null
|
||||
New-Item -ItemType Directory -Path $script:SlotAppTwo -Force | Out-Null
|
||||
New-Item -ItemType Directory -Path $script:SlotCfgDir -Force | Out-Null
|
||||
New-Item -ItemType Directory -Path $script:SlotInclude -Force | Out-Null
|
||||
|
||||
$script:AppendBackupRun = Invoke-BaknretScript -Script $script:BackupScript -Parameters @{
|
||||
BackupListPath = $script:AppendList
|
||||
BackupDir = $script:AppendBackupDir
|
||||
Set-Content -LiteralPath (Join-Path $script:SlotAppOne 'one.txt') 'one'
|
||||
Set-Content -LiteralPath (Join-Path $script:SlotAppOne 'sub\deep.txt') 'deep'
|
||||
Set-Content -LiteralPath (Join-Path $script:SlotAppOne 'Cache\c.bin') 'cache'
|
||||
Set-Content -LiteralPath (Join-Path $script:SlotAppTwo 'two.txt') 'two'
|
||||
Set-Content -LiteralPath (Join-Path $script:SlotCfgDir 'settings.json') '{"a":1}'
|
||||
Set-Content -LiteralPath (Join-Path $script:SlotInclude 'mod.txt') 'mod'
|
||||
|
||||
$appOne = $script:SlotAppOne
|
||||
$appTwo = $script:SlotAppTwo
|
||||
$cfgFile = Join-Path $script:SlotCfgDir 'settings.json'
|
||||
$includeDir = $script:SlotInclude
|
||||
|
||||
$script:SlotCatalog = Write-ListFile -Path (Join-Path $script:SlotRoot 'cat.psd1') -Content @"
|
||||
@{
|
||||
'appkit' = @{
|
||||
Cfg = @{ Path = '$cfgFile' }
|
||||
Data = @{ Path = '$appOne'; Exclude = '!*Cache' }
|
||||
Extra = @{ Path = '$appTwo'; Include = 'Modules:$includeDir' }
|
||||
}
|
||||
}
|
||||
"@
|
||||
$script:SlotConfig = Write-ListFile -Path (Join-Path $script:SlotRoot 'config.psd1') -Content "@{ SoftwareCatalog = '$script:SlotCatalog' }"
|
||||
$script:SlotList = Write-ListFile -Path (Join-Path $script:SlotRoot 'list.txt') -Content "appkit`n"
|
||||
$script:SlotBackupDir = Join-Path $script:SlotRoot 'Backups'
|
||||
|
||||
$script:SlotBackupRun = Invoke-BaknretScript -Script $script:BackupScript -Parameters @{
|
||||
BackupListPath = $script:SlotList
|
||||
BackupDir = $script:SlotBackupDir
|
||||
ConfigPath = $script:SlotConfig
|
||||
Force = $true
|
||||
QuietTool = $true
|
||||
}
|
||||
$script:SlotManifest = Read-BaknretManifest -Path (Join-Path $script:SlotBackupDir 'manifest.json')
|
||||
$script:SlotArchive = @(Get-ChildItem -LiteralPath $script:SlotBackupDir -File -Filter *.7z)[0]
|
||||
}
|
||||
|
||||
It '备份前会打印空间预估与"够不够"的结论' {
|
||||
$script:AppendBackupRun.Output | Should -Match '备份前空间预估'
|
||||
$script:AppendBackupRun.Output | Should -Match '要重打'
|
||||
$script:AppendBackupRun.Output | Should -Match '结论:'
|
||||
It '备份退出码 0,归档名就是软件名' {
|
||||
$script:SlotBackupRun.ExitCode | Should -Be 0
|
||||
$script:SlotArchive.BaseName | Should -Be 'appkit'
|
||||
}
|
||||
|
||||
It '备份成功,manifest.roots 记录两棵子树' {
|
||||
$script:AppendBackupRun.ExitCode | Should -Be 0
|
||||
$archive = @(Get-ChildItem -LiteralPath $script:AppendBackupDir -File -Filter *.7z)[0]
|
||||
$record = (Read-BaknretManifest -Path (Join-Path $script:AppendBackupDir 'manifest.json')).items[$archive.BaseName]
|
||||
$record.roots | Should -Contain 'dirA'
|
||||
$record.roots | Should -Contain 'dirB'
|
||||
It '归档顶层就是各个 Slot 名(目录 Slot + 文件 Slot + Include 项)' {
|
||||
$top = @(Get-ArchiveTopLevelNames -ArchivePath $script:SlotArchive.FullName -SevenZip $script:SevenZip)
|
||||
(@($top | Sort-Object) -join ',') | Should -Be 'Cfg,Data,Extra,Modules'
|
||||
}
|
||||
|
||||
It '归档里两棵树都在,且 :- 排除生效' {
|
||||
$verify = Join-Path $script:AppendRoot 'verify'
|
||||
It 'manifest.layouts 记下每个归档项是目录还是文件' {
|
||||
$record = $script:SlotManifest.items['appkit']
|
||||
$record.action | Should -Be 'backed-up'
|
||||
$record.roots | Should -Contain 'Data'
|
||||
(@($record.layouts | ForEach-Object { $_.name + ':' + $_.kind }) -join ',') | Should -Be 'Cfg:file,Data:dir,Extra:dir,Modules:dir'
|
||||
}
|
||||
|
||||
It '归档内容:<Slot>\<内容> 布局,文件 Slot 是名为 Slot 的文件,Slot 排除生效' {
|
||||
$verify = Join-Path $script:SlotRoot 'verify'
|
||||
New-Item -ItemType Directory -Path $verify -Force | Out-Null
|
||||
$archive = @(Get-ChildItem -LiteralPath $script:AppendBackupDir -File -Filter *.7z)[0]
|
||||
(Invoke-ExternalCommand -FilePath $script:SevenZip -ArgumentList @('x', '-bso0', '-bsp0', '-y', "-o$verify", $archive.FullName)) | Should -Be 0
|
||||
(Invoke-ExternalCommand -FilePath $script:SevenZip -ArgumentList @('x', '-bso0', '-bsp0', '-y', "-o$verify", $script:SlotArchive.FullName)) | Should -Be 0
|
||||
|
||||
Test-Path -LiteralPath (Join-Path $verify 'dirA\a.txt') | Should -BeTrue
|
||||
Test-Path -LiteralPath (Join-Path $verify 'dirB\b.txt') | Should -BeTrue
|
||||
Test-Path -LiteralPath (Join-Path $verify 'dirA\skip.log') | Should -BeFalse
|
||||
Test-Path -LiteralPath (Join-Path $verify 'Data\one.txt') | Should -BeTrue
|
||||
Test-Path -LiteralPath (Join-Path $verify 'Data\sub\deep.txt') | Should -BeTrue
|
||||
Test-Path -LiteralPath (Join-Path $verify 'Data\Cache\c.bin') | Should -BeFalse
|
||||
Test-Path -LiteralPath (Join-Path $verify 'Extra\two.txt') | Should -BeTrue
|
||||
Test-Path -LiteralPath (Join-Path $verify 'Modules\mod.txt') | Should -BeTrue
|
||||
Test-Path -LiteralPath (Join-Path $verify 'Cfg') -PathType Leaf | Should -BeTrue
|
||||
(Get-Content -LiteralPath (Join-Path $verify 'Cfg') -Raw).Trim() | Should -Be '{"a":1}'
|
||||
}
|
||||
|
||||
It '删源后恢复:每个目录只落回自己的父目录,兄弟目录不会被复制过去' {
|
||||
Remove-Item -LiteralPath $script:AppendA -Recurse -Force
|
||||
Remove-Item -LiteralPath $script:AppendB -Recurse -Force
|
||||
It '真实恢复:目录 Slot、文件 Slot 与 Include 都落回各自的原位' {
|
||||
Remove-Item -LiteralPath (Join-Path $script:SlotRoot 'apps') -Recurse -Force
|
||||
Remove-Item -LiteralPath $script:SlotInclude -Recurse -Force
|
||||
|
||||
$run = Invoke-BaknretScript -Script $script:RestoreScript -Parameters @{
|
||||
BackupListPath = $script:AppendList
|
||||
BackupDir = $script:AppendBackupDir
|
||||
BackupListPath = $script:SlotList
|
||||
BackupDir = $script:SlotBackupDir
|
||||
ConfigPath = $script:SlotConfig
|
||||
Force = $true
|
||||
}
|
||||
$run.ExitCode | Should -Be 0
|
||||
$run.Output | Should -Match '恢复成功: appkit'
|
||||
|
||||
Test-Path -LiteralPath (Join-Path $script:AppendA 'a.txt') | Should -BeTrue
|
||||
Test-Path -LiteralPath (Join-Path $script:AppendB 'b.txt') | Should -BeTrue
|
||||
(Get-Content -LiteralPath (Join-Path $script:SlotAppOne 'one.txt') -Raw).Trim() | Should -Be 'one'
|
||||
(Get-Content -LiteralPath (Join-Path $script:SlotAppOne 'sub\deep.txt') -Raw).Trim() | Should -Be 'deep'
|
||||
(Get-Content -LiteralPath (Join-Path $script:SlotAppTwo 'two.txt') -Raw).Trim() | Should -Be 'two'
|
||||
(Get-Content -LiteralPath (Join-Path $script:SlotInclude 'mod.txt') -Raw).Trim() | Should -Be 'mod'
|
||||
|
||||
# 关键:srcA 下不该冒出 dirB,srcB 下也不该冒出 dirA
|
||||
Test-Path -LiteralPath (Join-Path $script:AppendRoot 'srcA\dirB') | Should -BeFalse
|
||||
Test-Path -LiteralPath (Join-Path $script:AppendRoot 'srcB\dirA') | Should -BeFalse
|
||||
# 被 Slot 排除的缓存没有进过归档,自然也不会被恢复出来
|
||||
Test-Path -LiteralPath (Join-Path $script:SlotAppOne 'Cache\c.bin') | Should -BeFalse
|
||||
}
|
||||
|
||||
It '文件 Slot 在目标不存在时靠 manifest.layouts 恢复成文件(而不是目录)' {
|
||||
# 目标文件被删掉了,名录解析只能得到 IsFile=false;判据要靠 manifest 的 layouts。
|
||||
$restored = Join-Path $script:SlotCfgDir 'settings.json'
|
||||
(Test-Path -LiteralPath $restored -PathType Leaf) | Should -BeTrue
|
||||
(Get-Content -LiteralPath $restored -Raw).Trim() | Should -Be '{"a":1}'
|
||||
}
|
||||
|
||||
It '恢复之后 manifest 记下 lastRestoreAt' {
|
||||
$manifest = Read-BaknretManifest -Path (Join-Path $script:SlotBackupDir 'manifest.json')
|
||||
$manifest.items['appkit'].lastRestoreAt | Should -Not -BeNullOrEmpty
|
||||
}
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
Describe '集成:同一条目里两个同名目录会被拒绝执行' -Skip:(-not ($script:HasSevenZip -and $script:HasPwsh)) {
|
||||
Describe '集成:旧布局归档的回退恢复' -Skip:(-not ($script:HasSevenZip -and $script:HasPwsh)) {
|
||||
# ============================================================================
|
||||
|
||||
# Slot 布局是重构后才有的,Backups\ 里还躺着按旧布局(包内直接是 <源目录名>\...)
|
||||
# 生成的归档。恢复这类归档时必须回退到"把 <目标末级名> 解到目标父目录"的旧语义。
|
||||
|
||||
BeforeAll {
|
||||
$script:LegacyRoot = Join-Path $script:Sandbox 'legacy-layout'
|
||||
$script:LegacyHolder = Join-Path $script:LegacyRoot 'holder'
|
||||
$script:LegacyDestParent = Join-Path $script:LegacyRoot 'dest'
|
||||
$script:LegacyLeaf = 'My Code Space'
|
||||
New-Item -ItemType Directory -Path (Join-Path $script:LegacyHolder $script:LegacyLeaf) -Force | Out-Null
|
||||
New-Item -ItemType Directory -Path $script:LegacyDestParent -Force | Out-Null
|
||||
Set-Content -LiteralPath (Join-Path $script:LegacyHolder "$script:LegacyLeaf\legacy.txt") 'old-layout'
|
||||
|
||||
$destPath = Join-Path $script:LegacyDestParent $script:LegacyLeaf
|
||||
$script:LegacyCatalog = Write-ListFile -Path (Join-Path $script:LegacyRoot 'cat.psd1') -Content @"
|
||||
@{
|
||||
'oldapp' = @{ SlotX = @{ Path = '$destPath' } }
|
||||
}
|
||||
"@
|
||||
$script:LegacyConfig = Write-ListFile -Path (Join-Path $script:LegacyRoot 'config.psd1') -Content "@{ SoftwareCatalog = '$script:LegacyCatalog' }"
|
||||
$script:LegacyList = Write-ListFile -Path (Join-Path $script:LegacyRoot 'list.txt') -Content "oldapp`n"
|
||||
$script:LegacyBackupDir = Join-Path $script:LegacyRoot 'Backups'
|
||||
New-Item -ItemType Directory -Path $script:LegacyBackupDir -Force | Out-Null
|
||||
|
||||
# 手工造一个旧布局归档:顶层就是源目录名,不是 Slot 名。
|
||||
$script:LegacyArchive = Join-Path $script:LegacyBackupDir 'oldapp.7z'
|
||||
(Invoke-ExternalCommand -FilePath $script:SevenZip `
|
||||
-ArgumentList @('a', '-t7z', '-mx=1', '-bso0', '-bsp0', $script:LegacyArchive, $script:LegacyLeaf) `
|
||||
-WorkingDirectory $script:LegacyHolder) | Should -Be 0
|
||||
}
|
||||
|
||||
It '归档确实是旧布局:顶层是源目录名而不是 Slot 名' {
|
||||
$top = @(Get-ArchiveTopLevelNames -ArchivePath $script:LegacyArchive -SevenZip $script:SevenZip)
|
||||
(@($top | Sort-Object) -join ',') | Should -Be $script:LegacyLeaf
|
||||
}
|
||||
|
||||
It '归档里缺 Slot 层(真实旧归档)时按旧布局回退,把内容还原回原位' {
|
||||
# 归档里没有 SlotX,但有旧布局的 <目标末级名>;恢复端必须先问归档"这条路径在不在",
|
||||
# 不能靠 7z 的退出码猜(7z 对不存在的条目同样返回 0)。
|
||||
Remove-Item -LiteralPath (Join-Path $script:LegacyDestParent $script:LegacyLeaf) -Recurse -Force -ErrorAction SilentlyContinue
|
||||
|
||||
$run = Invoke-BaknretScript -Script $script:RestoreScript -Parameters @{
|
||||
BackupListPath = $script:LegacyList
|
||||
BackupDir = $script:LegacyBackupDir
|
||||
ConfigPath = $script:LegacyConfig
|
||||
Force = $true
|
||||
}
|
||||
|
||||
$run.ExitCode | Should -Be 0
|
||||
$run.Output | Should -Match '按旧布局回退'
|
||||
Test-Path -LiteralPath (Join-Path $script:LegacyDestParent "$script:LegacyLeaf\legacy.txt") | Should -BeTrue
|
||||
(Get-Content -LiteralPath (Join-Path $script:LegacyDestParent "$script:LegacyLeaf\legacy.txt") -Raw).Trim() | Should -Be 'old-layout'
|
||||
}
|
||||
|
||||
It '归档里既没有 Slot 层、也没有旧布局名字时明确失败(不再"成功地什么都没恢复")' {
|
||||
# 用 :: 覆盖把目标换成一个归档里根本不存在的末级名:两条路都走不通,
|
||||
# 必须报失败并说明原因,而不是打一句"恢复成功"却一个文件都没落地。
|
||||
$missingList = Write-ListFile -Path (Join-Path $script:LegacyRoot 'missing-list.txt') `
|
||||
-Content "oldapp :: $script:LegacyRoot\dest2\Nothing Here`n"
|
||||
|
||||
$run = Invoke-BaknretScript -Script $script:RestoreScript -Parameters @{
|
||||
BackupListPath = $missingList
|
||||
BackupDir = $script:LegacyBackupDir
|
||||
ConfigPath = $script:LegacyConfig
|
||||
Force = $true
|
||||
}
|
||||
|
||||
$run.ExitCode | Should -Be 1
|
||||
$run.Output | Should -Match '既没有'
|
||||
Test-Path -LiteralPath (Join-Path $script:LegacyRoot 'dest2\Nothing Here') | Should -BeFalse
|
||||
}
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
Describe '集成:归档内路径冲突会被拒绝执行' -Skip:(-not ($script:HasSevenZip -and $script:HasPwsh)) {
|
||||
# ============================================================================
|
||||
|
||||
BeforeAll {
|
||||
@@ -338,8 +515,9 @@ Describe '集成:同一条目里两个同名目录会被拒绝执行' -Skip:(-
|
||||
New-Item -ItemType Directory -Path $directory -Force | Out-Null
|
||||
Set-Content -LiteralPath (Join-Path $directory 'x.txt') 'x'
|
||||
}
|
||||
Write-ListFile -Path $script:RejectCatalog -Content "@{`n 'collide' = @('$p1', '$p2')`n}`n" | Out-Null
|
||||
$script:RejectList = Write-ListFile -Path (Join-Path $script:RejectRoot 'list.txt') -Content "collide`n"
|
||||
# Slot 叫 Data,Include 也要放进包内的 Data -> 同一个位置,必须报错
|
||||
Write-ListFile -Path $script:RejectCatalog -Content "@{`n 'collide' = @{ Data = @{ Path = '$p1' } }`n}`n" | Out-Null
|
||||
$script:RejectList = Write-ListFile -Path (Join-Path $script:RejectRoot 'list.txt') -Content "collide :+ Data:$p2`n"
|
||||
$script:RejectConfig = Write-ListFile -Path (Join-Path $script:RejectRoot 'config.psd1') -Content "@{ SoftwareCatalog = '$script:RejectCatalog' }`n"
|
||||
$script:RejectBackupDir = Join-Path $script:RejectRoot 'Backups'
|
||||
|
||||
@@ -352,9 +530,9 @@ Describe '集成:同一条目里两个同名目录会被拒绝执行' -Skip:(-
|
||||
}
|
||||
}
|
||||
|
||||
It '退出码 1,且给出"顶层同名"的原因,不生成归档' {
|
||||
It '退出码 1,且给出"归档内路径冲突"的原因,不生成归档' {
|
||||
$script:RejectRun.ExitCode | Should -Be 1
|
||||
$script:RejectRun.Output | Should -Match '顶层同名'
|
||||
$script:RejectRun.Output | Should -Match '归档内路径冲突'
|
||||
@(Get-ChildItem -LiteralPath $script:RejectBackupDir -File -Filter *.7z -ErrorAction SilentlyContinue).Count | Should -Be 0
|
||||
}
|
||||
|
||||
@@ -362,7 +540,7 @@ Describe '集成:同一条目里两个同名目录会被拒绝执行' -Skip:(-
|
||||
$manifest = Read-BaknretManifest -Path (Join-Path $script:RejectBackupDir 'manifest.json')
|
||||
$record = $manifest.items['collide']
|
||||
$record.action | Should -Be 'failed'
|
||||
$record.reason | Should -Match '顶层同名'
|
||||
$record.reason | Should -Match '归档内路径冲突'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,7 +559,7 @@ Describe '条目从清单里消失后,旧归档必须被点名为孤儿' -Skip
|
||||
Set-Content -LiteralPath (Join-Path $script:OrphanSource 'data.txt') 'hello'
|
||||
|
||||
$script:OrphanCatalog = Write-ListFile -Path (Join-Path $script:OrphanRoot 'cat.psd1') `
|
||||
-Content "@{`n 'my-app' = '$script:OrphanSource'`n}`n"
|
||||
-Content "@{ 'my-app' = @{ Default = @{ Path = '$script:OrphanSource' } } }`n"
|
||||
$script:OrphanConfig = Write-ListFile -Path (Join-Path $script:OrphanRoot 'config.psd1') `
|
||||
-Content "@{ SoftwareCatalog = '$script:OrphanCatalog' }`n"
|
||||
$script:OrphanList = Join-Path $script:OrphanRoot 'list.txt'
|
||||
|
||||
Reference in new issue
Block a user