microsoft/hve-core
Publicmirrored fromhttps://github.com/microsoft/hve-coreAvailable
scripts/tests/plugins/Test-RemovedMaturityExclusion.Tests.ps1
117lines · modecode
| 1 | #Requires -Modules Pester |
| 2 | # Copyright (c) Microsoft Corporation. |
| 3 | # SPDX-License-Identifier: MIT |
| 4 | |
| 5 | <# |
| 6 | .SYNOPSIS |
| 7 | Regression tests asserting that items tombstoned with `maturity: removed` |
| 8 | in any `collections/*.collection.yml` do not appear anywhere under |
| 9 | `plugins/` (file paths or text content). |
| 10 | |
| 11 | .DESCRIPTION |
| 12 | A `maturity: removed` marker is a tombstone: the item must be excluded |
| 13 | from every plugin output (directory listings, plugin.json manifests, |
| 14 | READMEs, and any aggregated collection such as hve-core-all). This guard |
| 15 | catches regressions where a removed item leaks back into generated |
| 16 | plugin artifacts via aggregation, on-disk discovery, or stale state. |
| 17 | #> |
| 18 | |
| 19 | BeforeAll { |
| 20 | Import-Module powershell-yaml -ErrorAction Stop |
| 21 | Import-Module (Join-Path $PSScriptRoot '../../collections/Modules/CollectionHelpers.psm1') -Force |
| 22 | |
| 23 | $script:RepoRoot = Resolve-Path (Join-Path $PSScriptRoot '../../..') | Select-Object -ExpandProperty Path |
| 24 | $script:PluginsRoot = Join-Path $script:RepoRoot 'plugins' |
| 25 | $script:CollectionsRoot = Join-Path $script:RepoRoot 'collections' |
| 26 | |
| 27 | $script:RemovedItems = @() |
| 28 | $collectionFiles = Get-ChildItem -Path $script:CollectionsRoot -Filter '*.collection.yml' -File -ErrorAction SilentlyContinue |
| 29 | foreach ($file in $collectionFiles) { |
| 30 | $manifest = Get-Content -Path $file.FullName -Raw | ConvertFrom-Yaml |
| 31 | if ($null -eq $manifest -or $null -eq $manifest.items) { continue } |
| 32 | foreach ($item in $manifest.items) { |
| 33 | $effective = Resolve-CollectionItemMaturity -Maturity $item.maturity |
| 34 | if ($effective -eq 'removed') { |
| 35 | $script:RemovedItems += [pscustomobject]@{ |
| 36 | Collection = $file.Name |
| 37 | Path = $item.path |
| 38 | Leaf = Split-Path -Path $item.path -Leaf |
| 39 | Kind = $item.kind |
| 40 | } |
| 41 | } |
| 42 | } |
| 43 | } |
| 44 | |
| 45 | $script:PluginFiles = @() |
| 46 | if (Test-Path -Path $script:PluginsRoot) { |
| 47 | $script:PluginFiles = Get-ChildItem -Path $script:PluginsRoot -Recurse -File -Include '*.md', '*.json', '*.yml', '*.yaml' -ErrorAction SilentlyContinue |
| 48 | } |
| 49 | } |
| 50 | |
| 51 | Describe 'Removed maturity exclusion from plugins' { |
| 52 | It 'Discovers at least one tombstoned item in collections (sanity check)' { |
| 53 | if (-not (Test-Path -Path $script:PluginsRoot)) { |
| 54 | Set-ItResult -Skipped -Because 'plugins/ directory not present' |
| 55 | return |
| 56 | } |
| 57 | $script:RemovedItems.Count | Should -BeGreaterThan 0 -Because 'this test is only meaningful when at least one collection item is tombstoned with maturity: removed' |
| 58 | } |
| 59 | |
| 60 | It 'Excludes tombstoned items from every generated plugin file' { |
| 61 | if (-not (Test-Path -Path $script:PluginsRoot)) { |
| 62 | Set-ItResult -Skipped -Because 'plugins/ directory not present' |
| 63 | return |
| 64 | } |
| 65 | if ($script:RemovedItems.Count -eq 0) { |
| 66 | Set-ItResult -Skipped -Because 'no removed items declared in any collection manifest' |
| 67 | return |
| 68 | } |
| 69 | if ($script:PluginFiles.Count -eq 0) { |
| 70 | Set-ItResult -Skipped -Because 'no plugin files discovered under plugins/' |
| 71 | return |
| 72 | } |
| 73 | |
| 74 | $leaks = @() |
| 75 | foreach ($removed in $script:RemovedItems) { |
| 76 | $patterns = @($removed.Path, $removed.Leaf) | Sort-Object -Unique |
| 77 | foreach ($pattern in $patterns) { |
| 78 | $patternMatches = $script:PluginFiles | Select-String -SimpleMatch -Pattern $pattern -ErrorAction SilentlyContinue |
| 79 | foreach ($match in $patternMatches) { |
| 80 | $relative = $match.Path.Substring($script:RepoRoot.Length).TrimStart('\', '/') |
| 81 | $leaks += "[$($removed.Collection)] removed '$($removed.Path)' leaks into ${relative}:$($match.LineNumber) (matched '$pattern')" |
| 82 | } |
| 83 | } |
| 84 | } |
| 85 | |
| 86 | if ($leaks.Count -gt 0) { |
| 87 | $message = "Found $($leaks.Count) leak(s) of removed items in plugin outputs:`n - " + ($leaks -join "`n - ") |
| 88 | throw $message |
| 89 | } |
| 90 | } |
| 91 | |
| 92 | It 'Excludes tombstoned items from every plugin directory name' { |
| 93 | if (-not (Test-Path -Path $script:PluginsRoot)) { |
| 94 | Set-ItResult -Skipped -Because 'plugins/ directory not present' |
| 95 | return |
| 96 | } |
| 97 | if ($script:RemovedItems.Count -eq 0) { |
| 98 | Set-ItResult -Skipped -Because 'no removed items declared in any collection manifest' |
| 99 | return |
| 100 | } |
| 101 | |
| 102 | $allEntries = Get-ChildItem -Path $script:PluginsRoot -Recurse -ErrorAction SilentlyContinue |
| 103 | $leaks = @() |
| 104 | foreach ($removed in $script:RemovedItems) { |
| 105 | $entryMatches = $allEntries | Where-Object { $_.Name -eq $removed.Leaf } |
| 106 | foreach ($match in $entryMatches) { |
| 107 | $relative = $match.FullName.Substring($script:RepoRoot.Length).TrimStart('\', '/') |
| 108 | $leaks += "[$($removed.Collection)] removed '$($removed.Path)' leaks as path: $relative" |
| 109 | } |
| 110 | } |
| 111 | |
| 112 | if ($leaks.Count -gt 0) { |
| 113 | $message = "Found $($leaks.Count) tombstoned item(s) materialized under plugins/:`n - " + ($leaks -join "`n - ") |
| 114 | throw $message |
| 115 | } |
| 116 | } |
| 117 | } |