microsoft/hve-core
Publicmirrored from https://github.com/microsoft/hve-coreAvailable
scripts/tests/evals/Invoke-ArtifactModeration.Tests.ps1
192lines · modecode
| 1 | #Requires -Modules Pester |
| 2 | # Copyright (c) Microsoft Corporation. |
| 3 | # SPDX-License-Identifier: MIT |
| 4 | |
| 5 | BeforeAll { |
| 6 | $script:ScriptPath = Join-Path $PSScriptRoot '../../evals/Invoke-ArtifactModeration.ps1' |
| 7 | $script:OrigPath = $env:PATH |
| 8 | |
| 9 | function New-PythonStub { |
| 10 | param( |
| 11 | [Parameter(Mandatory)][string]$OutputJson |
| 12 | ) |
| 13 | $stubDir = Join-Path $TestDrive ("stub-" + [guid]::NewGuid().ToString('N')) |
| 14 | New-Item -ItemType Directory -Path $stubDir -Force | Out-Null |
| 15 | |
| 16 | $stubScript = Join-Path $stubDir 'python.ps1' |
| 17 | $jsonLiteral = $OutputJson.Replace("'", "''") |
| 18 | @" |
| 19 | param([Parameter(ValueFromRemainingArguments=`$true)]`$Args) |
| 20 | `$outIndex = [Array]::IndexOf(`$Args, '--output') |
| 21 | if (`$outIndex -lt 0) { exit 2 } |
| 22 | `$outPath = `$Args[`$outIndex + 1] |
| 23 | Set-Content -LiteralPath `$outPath -Value '$jsonLiteral' -Encoding utf8 -NoNewline |
| 24 | `$json = '$jsonLiteral' | ConvertFrom-Json |
| 25 | if (`$json.summary.flaggedCount -gt 0) { exit 1 } else { exit 0 } |
| 26 | "@ | Set-Content -LiteralPath $stubScript -Encoding utf8 -NoNewline |
| 27 | |
| 28 | if ($IsWindows) { |
| 29 | $shim = Join-Path $stubDir 'python.cmd' |
| 30 | "@pwsh -NoProfile -File `"$stubScript`" %*" | Set-Content -LiteralPath $shim -Encoding ascii -NoNewline |
| 31 | } |
| 32 | else { |
| 33 | # `.cmd` shims are not honored by PATH lookups on Linux/macOS CI, so |
| 34 | # emit an extensionless executable that `Get-Command python` resolves. |
| 35 | $shim = Join-Path $stubDir 'python' |
| 36 | "#!/usr/bin/env sh`npwsh -NoProfile -File `"$stubScript`" `"`$@`"`n" | Set-Content -LiteralPath $shim -Encoding ascii -NoNewline |
| 37 | & chmod +x $shim |
| 38 | } |
| 39 | return $stubDir |
| 40 | } |
| 41 | |
| 42 | function New-Manifest { |
| 43 | param( |
| 44 | [Parameter(Mandatory)][string]$Path, |
| 45 | [Parameter()][object[]]$Artifacts = @() |
| 46 | ) |
| 47 | $payload = @{ |
| 48 | artifacts = @($Artifacts) |
| 49 | } |
| 50 | $dir = Split-Path $Path -Parent |
| 51 | if ($dir -and -not (Test-Path -LiteralPath $dir)) { |
| 52 | New-Item -ItemType Directory -Force -Path $dir | Out-Null |
| 53 | } |
| 54 | $payload | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath $Path -Encoding utf8 -NoNewline |
| 55 | } |
| 56 | |
| 57 | function New-Repo { |
| 58 | $repo = Join-Path $TestDrive ("repo-" + [guid]::NewGuid().ToString('N')) |
| 59 | New-Item -ItemType Directory -Path $repo -Force | Out-Null |
| 60 | return $repo |
| 61 | } |
| 62 | |
| 63 | function New-EvalSpec { |
| 64 | param( |
| 65 | [Parameter(Mandatory)][string]$RepoRoot, |
| 66 | [Parameter(Mandatory)][string]$RelativePath |
| 67 | ) |
| 68 | $full = Join-Path $RepoRoot $RelativePath |
| 69 | $dir = Split-Path $full -Parent |
| 70 | if (-not (Test-Path -LiteralPath $dir)) { |
| 71 | New-Item -ItemType Directory -Path $dir -Force | Out-Null |
| 72 | } |
| 73 | Set-Content -LiteralPath $full -Value "category: skill-quality`nname: example" -Encoding utf8 -NoNewline |
| 74 | return $full |
| 75 | } |
| 76 | } |
| 77 | |
| 78 | Describe 'Invoke-ArtifactModeration.ps1' -Tag 'Unit' { |
| 79 | AfterEach { |
| 80 | $env:PATH = $script:OrigPath |
| 81 | Remove-Item Env:HVE_MODERATION_PYTHON -ErrorAction SilentlyContinue |
| 82 | } |
| 83 | |
| 84 | It 'Exits 2 when the eval root does not exist' { |
| 85 | $repo = New-Repo |
| 86 | $manifest = Join-Path $repo 'logs/changed-ai-artifacts.json' |
| 87 | $outFile = Join-Path $repo 'logs/moderation-artifacts.json' |
| 88 | |
| 89 | & pwsh -NoProfile -File $script:ScriptPath -ManifestPath $manifest -EvalRoot 'does-not-exist' -OutFile $outFile -RepoRoot $repo 2>$null |
| 90 | $LASTEXITCODE | Should -Be 2 |
| 91 | } |
| 92 | |
| 93 | It 'Writes empty artifacts result and exits 0 when no specs or artifacts exist' { |
| 94 | $repo = New-Repo |
| 95 | New-Item -ItemType Directory -Path (Join-Path $repo 'evals') -Force | Out-Null |
| 96 | $manifest = Join-Path $repo 'logs/changed-ai-artifacts.json' |
| 97 | $outFile = Join-Path $repo 'logs/moderation-artifacts.json' |
| 98 | |
| 99 | & pwsh -NoProfile -File $script:ScriptPath -ManifestPath $manifest -EvalRoot 'evals' -OutFile $outFile -RepoRoot $repo 3>$null |
| 100 | $LASTEXITCODE | Should -Be 0 |
| 101 | Test-Path $outFile | Should -BeTrue |
| 102 | $data = Get-Content -LiteralPath $outFile -Raw | ConvertFrom-Json |
| 103 | $data.scope | Should -Be 'artifacts' |
| 104 | $data.flagged | Should -BeFalse |
| 105 | $data.results.Count | Should -Be 0 |
| 106 | } |
| 107 | |
| 108 | It 'Moderates eval specs only and exits 0 when the manifest is missing' { |
| 109 | $repo = New-Repo |
| 110 | New-EvalSpec -RepoRoot $repo -RelativePath 'evals/skill-quality/example.eval.yaml' | Out-Null |
| 111 | $manifest = Join-Path $repo 'logs/changed-ai-artifacts.json' |
| 112 | $outFile = Join-Path $repo 'logs/moderation-artifacts.json' |
| 113 | |
| 114 | $stubDir = New-PythonStub -OutputJson '{"records":[{"id":"evals/skill-quality/example.eval.yaml","scores":{"toxicity":0.02},"flagged":false,"flaggedLabels":[]}],"summary":{"total":1,"flaggedCount":0}}' |
| 115 | $env:PATH = "$stubDir$([System.IO.Path]::PathSeparator)$($script:OrigPath)" |
| 116 | $env:HVE_MODERATION_PYTHON = 'python' |
| 117 | |
| 118 | & pwsh -NoProfile -File $script:ScriptPath -ManifestPath $manifest -EvalRoot 'evals' -OutFile $outFile -RepoRoot $repo 3>$null |
| 119 | $LASTEXITCODE | Should -Be 0 |
| 120 | Test-Path $outFile | Should -BeTrue |
| 121 | } |
| 122 | |
| 123 | It 'Skips deleted artifacts and exits 0 when nothing remains to moderate' { |
| 124 | $repo = New-Repo |
| 125 | New-Item -ItemType Directory -Path (Join-Path $repo 'evals') -Force | Out-Null |
| 126 | $manifest = Join-Path $repo 'logs/changed-ai-artifacts.json' |
| 127 | $outFile = Join-Path $repo 'logs/moderation-artifacts.json' |
| 128 | |
| 129 | New-Manifest -Path $manifest -Artifacts @( |
| 130 | @{ path = '.github/agents/removed.agent.md'; status = 'D' } |
| 131 | ) |
| 132 | |
| 133 | & pwsh -NoProfile -File $script:ScriptPath -ManifestPath $manifest -EvalRoot 'evals' -OutFile $outFile -RepoRoot $repo 3>$null |
| 134 | $LASTEXITCODE | Should -Be 0 |
| 135 | $data = Get-Content -LiteralPath $outFile -Raw | ConvertFrom-Json |
| 136 | $data.scope | Should -Be 'artifacts' |
| 137 | $data.flagged | Should -BeFalse |
| 138 | } |
| 139 | |
| 140 | It 'Skips manifest artifacts missing on disk and exits 0' { |
| 141 | $repo = New-Repo |
| 142 | New-Item -ItemType Directory -Path (Join-Path $repo 'evals') -Force | Out-Null |
| 143 | $manifest = Join-Path $repo 'logs/changed-ai-artifacts.json' |
| 144 | $outFile = Join-Path $repo 'logs/moderation-artifacts.json' |
| 145 | |
| 146 | New-Manifest -Path $manifest -Artifacts @( |
| 147 | @{ path = '.github/agents/ghost.agent.md'; status = 'modified' } |
| 148 | ) |
| 149 | |
| 150 | & pwsh -NoProfile -File $script:ScriptPath -ManifestPath $manifest -EvalRoot 'evals' -OutFile $outFile -RepoRoot $repo 3>$null |
| 151 | $LASTEXITCODE | Should -Be 0 |
| 152 | $data = Get-Content -LiteralPath $outFile -Raw | ConvertFrom-Json |
| 153 | $data.scope | Should -Be 'artifacts' |
| 154 | $data.flagged | Should -BeFalse |
| 155 | } |
| 156 | |
| 157 | It 'Moderates combined specs and changed artifacts and exits 0 when clean' { |
| 158 | $repo = New-Repo |
| 159 | New-EvalSpec -RepoRoot $repo -RelativePath 'evals/skill-quality/example.eval.yaml' | Out-Null |
| 160 | $artifact = '.github/agents/clean.agent.md' |
| 161 | New-Item -ItemType Directory -Path (Join-Path $repo '.github/agents') -Force | Out-Null |
| 162 | Set-Content -LiteralPath (Join-Path $repo $artifact) -Value "---`napplyTo: '**'`n---`nHello world." -Encoding utf8 -NoNewline |
| 163 | |
| 164 | $manifest = Join-Path $repo 'logs/changed-ai-artifacts.json' |
| 165 | $outFile = Join-Path $repo 'logs/moderation-artifacts.json' |
| 166 | New-Manifest -Path $manifest -Artifacts @( |
| 167 | @{ path = $artifact; status = 'modified' } |
| 168 | ) |
| 169 | |
| 170 | $stubDir = New-PythonStub -OutputJson '{"records":[],"summary":{"total":2,"flaggedCount":0}}' |
| 171 | $env:PATH = "$stubDir$([System.IO.Path]::PathSeparator)$($script:OrigPath)" |
| 172 | $env:HVE_MODERATION_PYTHON = 'python' |
| 173 | |
| 174 | & pwsh -NoProfile -File $script:ScriptPath -ManifestPath $manifest -EvalRoot 'evals' -OutFile $outFile -RepoRoot $repo 3>$null |
| 175 | $LASTEXITCODE | Should -Be 0 |
| 176 | Test-Path $outFile | Should -BeTrue |
| 177 | } |
| 178 | |
| 179 | It 'Propagates exit 1 when moderated content is flagged' { |
| 180 | $repo = New-Repo |
| 181 | New-EvalSpec -RepoRoot $repo -RelativePath 'evals/skill-quality/example.eval.yaml' | Out-Null |
| 182 | $manifest = Join-Path $repo 'logs/changed-ai-artifacts.json' |
| 183 | $outFile = Join-Path $repo 'logs/moderation-artifacts.json' |
| 184 | |
| 185 | $stubDir = New-PythonStub -OutputJson '{"records":[{"id":"evals/skill-quality/example.eval.yaml","scores":{"toxicity":0.95},"flagged":true,"flaggedLabels":["toxicity"]}],"summary":{"total":1,"flaggedCount":1}}' |
| 186 | $env:PATH = "$stubDir$([System.IO.Path]::PathSeparator)$($script:OrigPath)" |
| 187 | $env:HVE_MODERATION_PYTHON = 'python' |
| 188 | |
| 189 | & pwsh -NoProfile -File $script:ScriptPath -ManifestPath $manifest -EvalRoot 'evals' -OutFile $outFile -RepoRoot $repo 2>$null |
| 190 | $LASTEXITCODE | Should -Be 1 |
| 191 | } |
| 192 | } |
| 193 | |