hugin/scripts/integration-test.ps1
2026-10-27 08:33:21 +01:00

125 lines
7.6 KiB
PowerShell

<#
.SYNOPSIS
Prüft hugin-install.ps1 unter Windows PowerShell 5.1 gegen einen laufenden Hugin.
.DESCRIPTION
Gegenstück zu scripts/integration-test.sh für Windows. Braucht einen Hugin mit
den Beispielpaketen, z. B. scripts/demo.sh auf einem anderen Rechner:
HOST=0.0.0.0 scripts/demo.sh # auf dem Mac/Linux-Rechner
Dann unter Windows (PowerShell 5.1, nicht pwsh):
powershell -ExecutionPolicy Bypass -File integration-test.ps1 `
-Server http://<rechner>:8080 -Key <Key "Softwareverteilung" aus .demo/keys.txt>
Ändert nichts außerhalb eines Temp-Ordners. Rückgabewert 0, wenn alles passt.
#>
param(
[Parameter(Mandatory = $true)][string]$Server,
[Parameter(Mandatory = $true)][string]$Key,
# Nur zum Vorabtest auf anderen Systemen, z. B. -PowerShell pwsh; unter Windows weglassen
[string]$PowerShell = 'powershell.exe'
)
Set-StrictMode -Version 2
$ErrorActionPreference = 'Stop'
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$Utf8 = New-Object System.Text.UTF8Encoding($false)
$failures = 0
function Check([string]$Name, [bool]$Ok, [string]$Info = '') {
if ($Ok) { Write-Host "OK $Name" } else { Write-Host "FEHLER $Name $Info" -ForegroundColor Red; $script:failures++ }
}
Write-Host "PowerShell $($PSVersionTable.PSVersion) ($($PSVersionTable.PSEdition))"
if ($PSVersionTable.PSVersion.Major -ne 5) { Write-Warning 'Dieser Test ist für Windows PowerShell 5.1 gedacht.' }
$work = Join-Path ([IO.Path]::GetTempPath()) ("hugin-test-" + [Guid]::NewGuid().ToString('N').Substring(0, 8))
$repo = Join-Path $work 'repo'
New-Item -ItemType Directory -Path $repo -Force | Out-Null
try {
# Skript vom Server laden, wie eine Softwareverteilung es tun würde
$script = Join-Path $work 'hugin-install.ps1'
Invoke-WebRequest -UseBasicParsing -Uri ($Server.TrimEnd('/') + '/api/v1/delivery/client.ps1') `
-Headers @{ Authorization = "Bearer $Key" } -OutFile $script
$raw = [IO.File]::ReadAllBytes($script)
Check 'client.ps1 mit BOM (für PowerShell 5.1)' ($raw[0] -eq 0xEF -and $raw[1] -eq 0xBB -and $raw[2] -eq 0xBF)
[IO.File]::WriteAllText((Join-Path $repo 'CLAUDE.md'), "# Werk`r`n`r`nUnser Projekt.`r`n", $Utf8)
[IO.File]::WriteAllText((Join-Path $repo 'opencode.json'), "{`n `"model`": `"anthropic/claude-sonnet-5-5`"`n}`n", $Utf8)
function Run([string[]]$More) {
$output = & $PowerShell -NoProfile -ExecutionPolicy Bypass -File $script -Server $Server -Key $Key `
-Repo $repo -Tools 'claude=2.1.302,opencode=1.19.3' -Client 'windows-test' @More 2>&1 | Out-String
return @{ Text = $output; Rc = $LASTEXITCODE }
}
$first = Run @('-Packages', 'pub-munin-nutzen,pub-hausregeln,pub-code-review')
Check 'Erstinstallation' ($first.Rc -eq 0) $first.Text
foreach ($rel in '.claude\skills\pub-munin-nutzen\SKILL.md', '.claude\agents\pub-code-review.md',
'.opencode\agents\pub-code-review.md', '.hugin\rules\pub-hausregeln.md', '.mcp.json', 'hugin.lock.json') {
Check "Datei $rel" (Test-Path -LiteralPath (Join-Path $repo $rel))
}
$skill = [IO.File]::ReadAllBytes((Join-Path $repo '.claude\skills\pub-munin-nutzen\SKILL.md'))
Check 'SKILL.md ohne BOM' (-not ($skill[0] -eq 0xEF -and $skill[1] -eq 0xBB))
Check 'SKILL.md mit LF, ohne CRLF' (-not $Utf8.GetString($skill).Contains("`r`n"))
$claude = [IO.File]::ReadAllText((Join-Path $repo 'CLAUDE.md'), $Utf8)
Check 'CLAUDE.md behält Inhalt und CRLF' ($claude.StartsWith("# Werk`r`n`r`nUnser Projekt.`r`n`r`n<!-- hugin:begin") -and -not ($claude -replace "`r`n", '').Contains("`n"))
foreach ($json in 'opencode.json', '.mcp.json', 'hugin.lock.json') {
$text = [IO.File]::ReadAllText((Join-Path $repo $json), $Utf8)
$valid = $true; try { $null = $text | ConvertFrom-Json } catch { $valid = $false }
Check "$json gültiges JSON ohne BOM" ($valid -and -not $text.StartsWith([char]0xFEFF))
}
$oc = [IO.File]::ReadAllText((Join-Path $repo 'opencode.json'), $Utf8) | ConvertFrom-Json
Check 'opencode.json: eigenes Modell bleibt, MCP und instructions ergänzt' (
$oc.model -eq 'anthropic/claude-sonnet-5-5' -and $oc.mcp.munin.type -eq 'remote' -and @($oc.instructions) -contains '.hugin/rules/*.md')
# Eine Liste mit einem Eintrag muss eine Liste bleiben (Eigenheit von ConvertTo-Json in 5.1)
Check 'instructions bleibt eine Liste' ([IO.File]::ReadAllText((Join-Path $repo 'opencode.json'), $Utf8) -match '"instructions":\s*\[')
$second = Run @('-Packages', 'pub-munin-nutzen,pub-hausregeln,pub-code-review')
Check 'Zweiter Lauf ändert nichts' ($second.Rc -eq 0 -and $second.Text -match 'Dateien: 0 schreiben, 0 löschen') $second.Text
$dryRun = Run @('-Packages', 'pub-munin-nutzen', '-DryRun')
Check 'Trockenlauf schreibt nichts' ($dryRun.Text -match 'Trockenlauf' -and (Test-Path (Join-Path $repo '.claude\agents\pub-code-review.md')))
$unsubscribed = Run @('-Packages', 'pub-munin-nutzen')
Check 'Abbestellen räumt auf' ($unsubscribed.Rc -eq 0 -and -not (Test-Path (Join-Path $repo '.claude\agents\pub-code-review.md')) -and
-not ([IO.File]::ReadAllText((Join-Path $repo 'CLAUDE.md'), $Utf8).Contains('hugin:begin'))) $unsubscribed.Text
Check 'CLAUDE.md wieder wie vorher' ([IO.File]::ReadAllText((Join-Path $repo 'CLAUDE.md'), $Utf8) -eq "# Werk`r`n`r`nUnser Projekt.`r`n")
[IO.File]::AppendAllText((Join-Path $repo '.claude\skills\pub-munin-nutzen\SKILL.md'), "von Hand`n", $Utf8)
$manual = Run @('-Packages', 'pub-munin-nutzen,pub-hausregeln')
Check 'Handänderung wird erkannt und nichts geschrieben' ($manual.Rc -ne 0 -and $manual.Text -match 'von Hand' -and
-not (Test-Path (Join-Path $repo '.hugin\rules\pub-hausregeln.md'))) $manual.Text
$overwritten = Run @('-Packages', 'pub-munin-nutzen,pub-hausregeln', '-Overwrite')
Check '-Overwrite setzt zurück' ($overwritten.Rc -eq 0 -and (Test-Path (Join-Path $repo '.hugin\rules\pub-hausregeln.md')))
$repo2 = Join-Path $work 'repo2'; New-Item -ItemType Directory -Path $repo2 | Out-Null
[IO.File]::WriteAllText((Join-Path $repo2 'opencode.json'), "{`n // eigenes`n `"model`": `"x`"`n}`n", $Utf8)
$jsonc = & $PowerShell -NoProfile -ExecutionPolicy Bypass -File $script -Server $Server -Key $Key -Repo $repo2 `
-Tools 'opencode=1.19.3' -Packages pub-hausregeln 2>&1 | Out-String
Check 'JSON mit Kommentaren bleibt unangetastet' ($LASTEXITCODE -ne 0 -and $jsonc -match 'Kommentare' -and
[IO.File]::ReadAllText((Join-Path $repo2 'opencode.json'), $Utf8).Contains('// eigenes'))
# Eine Junction im Repo darf nicht nach draußen führen (z. B. bei Ausführung als Admin)
$repo3 = Join-Path $work 'repo3'; $outside = Join-Path $work 'outside'
New-Item -ItemType Directory -Path $repo3, $outside | Out-Null
if ($PSVersionTable.PSEdition -eq 'Desktop' -or $IsWindows) {
New-Item -ItemType Junction -Path (Join-Path $repo3 '.opencode') -Target $outside | Out-Null
} else {
New-Item -ItemType SymbolicLink -Path (Join-Path $repo3 '.opencode') -Target $outside | Out-Null
}
$link = & $PowerShell -NoProfile -ExecutionPolicy Bypass -File $script -Server $Server -Key $Key -Repo $repo3 `
-Tools 'opencode=1.19.3' -Packages pub-code-review 2>&1 | Out-String
Check 'Junction nach draußen wird abgelehnt' ($LASTEXITCODE -ne 0 -and $link -match 'Junction' -and
@(Get-ChildItem -LiteralPath $outside -Force).Count -eq 0) $link
# Junction vor dem Aufräumen lösen, damit Remove-Item nicht dem Link folgt
(Get-Item -LiteralPath (Join-Path $repo3 '.opencode') -Force).Delete()
} finally {
Remove-Item -LiteralPath $work -Recurse -Force -ErrorAction SilentlyContinue
}
if ($failures -eq 0) { Write-Host "`nAlles ok." -ForegroundColor Green; exit 0 }
Write-Host "`n$failures Prüfung(en) fehlgeschlagen." -ForegroundColor Red
exit 1