tests/DeclarativeCollectorCoverage.Tests.ps1
|
#Requires -Modules @{ ModuleName = 'Pester'; ModuleVersion = '5.0.0' } <# AB#5659 — the COVERAGE gate for the declarative collector conversion, kept separate from the equivalence proof in tests/DeclarativeCollectorEquivalence.Tests.ps1. The equivalence tests discover whatever definitions exist and prove each one matches the `.ps1` it was lifted from. That is necessary but not sufficient: it is trivially satisfiable by converting nothing. This file asserts the other half — that the converted set is exactly the set the audit (docs/design/collector-audit.md) classified as pure data-shaping, minus a short list of collectors that each carry a written reason. Two files rather than one because the two questions fail for different reasons and want different answers: an equivalence failure means a conversion is WRONG, a coverage failure means a conversion is MISSING. #> BeforeAll { $script:RepoRoot = Split-Path -Parent $PSScriptRoot # Only the fixture loader is needed here, not the whole equivalence harness. $script:GeneratedFixtures = @{} function Get-GeneratedFixture { param([string]$Category) if (-not $script:GeneratedFixtures.ContainsKey($Category)) { $Path = Join-Path $script:RepoRoot "tests/fixtures/collector-equivalence/$Category.json" if (-not (Test-Path -LiteralPath $Path)) { throw "No generated equivalence fixture for category '$Category' at $Path" } $script:GeneratedFixtures[$Category] = Get-Content -LiteralPath $Path -Raw | ConvertFrom-Json } return $script:GeneratedFixtures[$Category] } } Describe 'Declarative collector coverage — the converted set is exactly the audited pure set, minus a listed few' { BeforeAll { $script:Audit = Get-Content -LiteralPath (Join-Path $script:RepoRoot 'tests/fixtures/collector-audit.json') -Raw | ConvertFrom-Json # The four PureShaping collectors NOT converted, each with the reason. Held as data in the test # rather than as prose in a design doc so that "convert everything the audit called pure" cannot # quietly become "convert everything that happened to be easy": editing this list is visible. # # Declared in BeforeAll, not at file scope: a file-scope variable is visible during DISCOVERY # (where -ForEach data is built) but not inside an It at run time. $script:UnconvertedPure = @( @{ Category = 'Management'; Name = 'AllSubscriptions' Reason = 'Its row loop iterates $Sub, not a filtered $Resources set, so there is no resource-type filter for the interpreter to drive; it also folds in a management-group chain built from a separate graph query.' } @{ Category = 'Management'; Name = 'AdvisorScore' Reason = 'Its $obj is built inside a nested if/else rather than as a statement of a row loop, so there is no single row-emitting level to lift.' } @{ Category = 'Networking'; Name = 'PublicIP' Reason = 'It has TWO $obj hashtable literals in opposite branches of an if/else -- the row SHAPE is conditional, which the schema deliberately cannot express (a Fields list is one shape).' } @{ Category = 'Monitor'; Name = 'Outages' Reason = 'AUDIT MISCLASSIFICATION: it builds its columns with New-Object -Com HTMLFile and reads $Html.body.innerText, so it is not pure shaping at all -- it depends on a COM component the audit never looked for (it only searched for Get-Az*/Invoke-*).' } ) $script:AllDefinitions = @( foreach ($Folder in (Get-ChildItem -LiteralPath (Join-Path $script:RepoRoot 'manifests/collectors') -Directory | Sort-Object Name)) { foreach ($File in (Get-ChildItem -LiteralPath $Folder.FullName -Filter '*.psd1' -File | Sort-Object Name)) { @{ Category = $Folder.Name; Name = $File.BaseName } } } ) } It 'every PureShaping collector either has a definition or is on the unconverted list' { $Missing = [System.Collections.Generic.List[string]]::new() foreach ($Entry in @($script:Audit | Where-Object { $_.Classification -eq 'PureShaping' })) { $Path = Join-Path $script:RepoRoot "manifests/collectors/$($Entry.Category)/$($Entry.Name).psd1" if (Test-Path -LiteralPath $Path) { continue } $Listed = @($script:UnconvertedPure | Where-Object { $_.Category -eq $Entry.Category -and $_.Name -eq $Entry.Name }) if (@($Listed).Count -eq 0) { $Missing.Add("$($Entry.Category)/$($Entry.Name)") } } $Missing -join ', ' | Should -BeNullOrEmpty } It 'nothing on the unconverted list has a definition after all' { # Keeps the list honest in the other direction: a stale entry here would understate coverage # and hide the fact that the obstacle was solved. foreach ($Entry in $script:UnconvertedPure) { (Join-Path $script:RepoRoot "manifests/collectors/$($Entry.Category)/$($Entry.Name).psd1") | Should -Not -Exist -Because "$($Entry.Category)/$($Entry.Name) is listed as unconverted: $($Entry.Reason)" } } It 'every definition has a generated fixture entry with at least one resource' { # A definition with no fixture entry would make its equivalence Describe throw rather than # silently pass, but a definition whose fixture entry is EMPTY would pass vacuously. foreach ($Entry in $script:AllDefinitions) { $Fixture = Get-GeneratedFixture -Category $Entry.Category $Fixture.collectors.PSObject.Properties.Name | Should -Contain $Entry.Name @($Fixture.collectors.($Entry.Name).resources).Count | Should -BeGreaterThan 0 } } It 'no generated fixture carries anything that looks like a real tenant identifier' { # The generated estates are entirely synthetic, but they live in tests/fixtures next to the # recorded ones and the repo's hard rule about subscription/tenant ids applies to every # committed file. The only GUIDs these may contain are the two placeholders. $Allowed = @('00000000-0000-0000-0000-000000000001', '00000000-0000-0000-0000-0000000000ff') $Pattern = [regex]'(?i)\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b' foreach ($File in (Get-ChildItem -LiteralPath (Join-Path $script:RepoRoot 'tests/fixtures/collector-equivalence') -Filter '*.json' -File)) { $Text = Get-Content -LiteralPath $File.FullName -Raw $Bad = @($Pattern.Matches($Text) | ForEach-Object { $_.Value } | Where-Object { $Allowed -notcontains $_.ToLowerInvariant() } | Select-Object -Unique) $Bad -join ', ' | Should -BeNullOrEmpty -Because "$($File.Name) must contain no identifier that could be real" } } } |