fix(tools): UTF-8 BOM для PowerShell 5.1, безопасный AllowCopy, доп. тесты
- AgentAssets.psm1 и sync-agent-assets.ps1 пересохранены в UTF-8 с BOM: powershell.exe читает файлы без BOM в системной ANSI-кодировке и ломает парсинг на кириллических строковых литералах (pwsh 7 работал за счёт автодетекта и маскировал баг). - Copy-Item в ветке -AllowCopy получил -ErrorAction Stop: без него ошибка копирования писалась как non-terminating и функция возвращала 'Copied' при частично скопированном каталоге, если вызывающий не выставил $ErrorActionPreference='Stop' сам. - Test-AgentSkillLink сравнивает пути через [string]::Equals(...,OrdinalIgnoreCase) явно, а не через регистронезависимый -eq. - Добавлены тесты: отсутствующий SourcePath, перелинковка на другой источник (старый target остаётся нетронутым) и AllowCopy-регрессия (мок Copy-Item с [CmdletBinding()], чтобы корректно эмулировать ErrorAction реального cmdlet).
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
Set-StrictMode -Version Latest
|
||||
Set-StrictMode -Version Latest
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
@@ -15,7 +15,8 @@ function Test-AgentSkillLink {
|
||||
if ($null -eq $item -or $item.LinkType -ne 'Junction') { return $false }
|
||||
|
||||
$actual = [System.IO.Path]::GetFullPath(($item.Target | Select-Object -First 1))
|
||||
return $actual.TrimEnd('\') -eq ([System.IO.Path]::GetFullPath($SourcePath)).TrimEnd('\')
|
||||
$expected = [System.IO.Path]::GetFullPath($SourcePath)
|
||||
return [string]::Equals($actual.TrimEnd('\'), $expected.TrimEnd('\'), [StringComparison]::OrdinalIgnoreCase)
|
||||
}
|
||||
|
||||
<#
|
||||
@@ -60,7 +61,7 @@ function Sync-AgentSkillLink {
|
||||
if (-not $AllowCopy) {
|
||||
throw "не удалось создать junction $LinkPath -> $SourcePath ($($_.Exception.Message)). Повторите с -AllowCopy, если копия допустима."
|
||||
}
|
||||
Copy-Item -LiteralPath $SourcePath -Destination $LinkPath -Recurse -Force
|
||||
Copy-Item -LiteralPath $SourcePath -Destination $LinkPath -Recurse -Force -ErrorAction Stop
|
||||
return 'Copied'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#requires -Version 5.1
|
||||
#requires -Version 5.1
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[switch] $Check,
|
||||
|
||||
@@ -47,4 +47,51 @@ Describe 'Sync-AgentSkillLink' {
|
||||
Test-AgentSkillLink -SourcePath $t.Source -LinkPath $t.Link | Should -BeTrue
|
||||
} finally { Remove-Item -LiteralPath $t.Root -Recurse -Force -ErrorAction SilentlyContinue }
|
||||
}
|
||||
|
||||
It 'бросает понятную ошибку, если SourcePath не существует' {
|
||||
$t = New-TempTree
|
||||
try {
|
||||
$missing = Join-Path $t.Root 'plugin/skills/nope'
|
||||
{ Sync-AgentSkillLink -SourcePath $missing -LinkPath $t.Link } | Should -Throw '*нет исходного навыка*'
|
||||
} finally { Remove-Item -LiteralPath $t.Root -Recurse -Force -ErrorAction SilentlyContinue }
|
||||
}
|
||||
|
||||
It 'перелинковка: ссылка на другой источник не трогает файлы старого источника' {
|
||||
$t = New-TempTree
|
||||
try {
|
||||
$source2 = Join-Path $t.Root 'plugin/skills/demo2'
|
||||
New-Item -ItemType Directory -Path $source2 -Force | Out-Null
|
||||
Set-Content -LiteralPath (Join-Path $source2 'SKILL.md') -Value 'demo2' -Encoding utf8
|
||||
|
||||
Sync-AgentSkillLink -SourcePath $t.Source -LinkPath $t.Link | Should -Be 'Created'
|
||||
Sync-AgentSkillLink -SourcePath $source2 -LinkPath $t.Link | Should -Be 'Created'
|
||||
|
||||
Get-Content -LiteralPath (Join-Path $t.Link 'SKILL.md') | Should -Be 'demo2'
|
||||
Get-Content -LiteralPath (Join-Path $t.Source 'SKILL.md') | Should -Be 'demo'
|
||||
} finally { Remove-Item -LiteralPath $t.Root -Recurse -Force -ErrorAction SilentlyContinue }
|
||||
}
|
||||
|
||||
It 'AllowCopy: ошибка Copy-Item не должна проглатываться независимо от ErrorActionPreference вызывающего' {
|
||||
# Явно 'Continue' — воспроизводим вызывающего БЕЗ $ErrorActionPreference='Stop'
|
||||
# (run-ps-tests.ps1 сам выставляет 'Stop' на уровне скрипта, и это маскировало бы баг).
|
||||
$ErrorActionPreference = 'Continue'
|
||||
$t = New-TempTree
|
||||
# Родительский каталог создаём заранее — иначе Mock New-Item с -ParameterFilter
|
||||
# по Junction перехватит и вызов New-Item -ItemType Directory (родитель LinkPath)
|
||||
# и провалится с "No mock matched", маскируя настоящий баг Copy-Item.
|
||||
New-Item -ItemType Directory -Path (Split-Path -Parent $t.Link) -Force | Out-Null
|
||||
try {
|
||||
Mock -ModuleName AgentAssets New-Item -ParameterFilter { $ItemType -eq 'Junction' } { throw 'junction недоступен (симуляция)' }
|
||||
# [CmdletBinding()] обязателен: только у advanced-функции переданный вызывающим
|
||||
# -ErrorAction реально локализует $ErrorActionPreference внутри тела мока —
|
||||
# иначе мок не воспроизводит поведение настоящего Copy-Item.
|
||||
Mock -ModuleName AgentAssets Copy-Item {
|
||||
[CmdletBinding()]
|
||||
param($LiteralPath, $Destination, [switch]$Recurse, [switch]$Force)
|
||||
Write-Error 'copy failed (симуляция)'
|
||||
}
|
||||
|
||||
{ Sync-AgentSkillLink -SourcePath $t.Source -LinkPath $t.Link -AllowCopy } | Should -Throw
|
||||
} finally { Remove-Item -LiteralPath $t.Root -Recurse -Force -ErrorAction SilentlyContinue }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user