<# Regenerates the invalid-configuration corpus. Each file is a valid configuration with exactly ONE thing broken, named after the finding code it must produce. One defect per file is the point: a fixture with two problems cannot prove which one produced the finding, and a validator that reported the wrong code would still pass. Run this only when adding a condition. The generated files are committed, so a reviewer sees the fixture in the diff rather than a script that produces it. pwsh ./tests/TestData/InvalidConfigs/New-InvalidConfigCorpus.ps1 #> [CmdletBinding()] param( [string] $OutputDirectory = $PSScriptRoot ) $ErrorActionPreference = 'Stop' function New-BaseDocument { @{ configVersion = '1.0.0' engine = @{ targetAttribute = 'extension__' approvedWritableAttributes = @('extension__') maxConditionDepth = 5 summaryInterval = 25 defaultMembershipMode = 'direct' } dataSources = @{ groups = @{ enabled = $true } roles = @{ enabled = $true } } logging = @{ destination = 'stream'; traceConditionValues = $false } personas = @('Employee', 'Guest', 'Tier0-Admin') rules = @( @{ id = 'RULE-0010-GUEST'; name = 'Guest accounts'; description = 'Accounts whose user type is Guest.' enabled = $true; priority = 10; persona = 'Guest' match = @{ operator = 'all'; conditions = @(@{ type = 'property'; property = 'UserType'; operator = 'equals'; value = 'Guest' }) } } @{ id = 'RULE-0900-EMPLOYEE'; name = 'Employees'; description = 'Default classification for member accounts.' enabled = $true; priority = 900; persona = 'Employee' match = @{ operator = 'all'; conditions = @(@{ type = 'property'; property = 'Department'; operator = 'isNotNull' }) } } ) } } function New-MembershipRule { param([hashtable] $Condition) @{ id = 'RULE-0030-TIER0'; name = 'Tier 0 administrators'; description = 'Members of the Tier 0 group.' enabled = $true; priority = 30; persona = 'Tier0-Admin' match = @{ operator = 'all'; conditions = @($Condition) } } } $cases = [ordered]@{} # ---------------------------------------------------------------- VR-002 $d = New-BaseDocument $d.rules[1].id = 'RULE-0010-GUEST' $cases['PE-SEM-001-duplicate-rule-id'] = $d $d = New-BaseDocument $d.rules[1].priority = 10 $cases['PE-SEM-002-duplicate-priority'] = $d $d = New-BaseDocument foreach ($rule in $d.rules) { $rule.enabled = $false } $cases['PE-SEM-003-no-enabled-rules'] = $d $d = New-BaseDocument $d.engine.targetAttribute = ' ' $cases['PE-SEM-004-blank-target-attribute'] = $d $d = New-BaseDocument $d.engine.approvedWritableAttributes = @('extension__') $cases['PE-SEM-005-target-not-approved'] = $d $d = New-BaseDocument $d.dataSources.roles.enabled = $false $d.rules += New-MembershipRule -Condition @{ type = 'role'; operator = 'memberOf'; roleIds = @('') } $cases['PE-SEM-006-unavailable-data-source'] = $d $d = New-BaseDocument $d.rules += New-MembershipRule -Condition @{ type = 'membership'; operator = 'memberOf' } $cases['PE-SEM-007-memberof-without-groups'] = $d $d = New-BaseDocument $d.rules[0].match.conditions[0] = @{ type = 'property'; property = 'UserType'; operator = 'in' } $cases['PE-SEM-008-in-without-values'] = $d $d = New-BaseDocument $d.rules[1].match.conditions[0] = @{ type = 'property'; property = 'Department'; operator = 'isNotNull'; value = 'Finance' } $cases['PE-SEM-009-isnull-with-value'] = $d $d = New-BaseDocument $d.rules[1].persona = 'Undeclared-Persona' $cases['PE-SEM-010-undeclared-persona'] = $d $d = New-BaseDocument $d.rules[1].persona = 'Unclassified' $cases['PE-SEM-011-unclassified-as-persona'] = $d $d = New-BaseDocument $d.engine.maxConditionDepth = 2 $d.rules[0].match = @{ operator = 'all' conditions = @( @{ operator = 'all'; conditions = @( @{ operator = 'all'; conditions = @( @{ type = 'property'; property = 'UserType'; operator = 'equals'; value = 'Guest' } ) } ) } ) } $cases['PE-SEM-012-depth-over-configured-maximum'] = $d $d = New-BaseDocument $d.engine.maxConditionDepth = 25 $cases['PE-SEM-013-depth-over-hard-ceiling'] = $d $d = New-BaseDocument $d.dataSources.groups.membershipMode = 'direct' $d.rules += New-MembershipRule -Condition @{ type = 'membership'; operator = 'memberOf'; membershipMode = 'transitive' groupObjectIds = @('00000000-0000-0000-0000-0000000000a0') } $cases['PE-SEM-014-mode-not-enabled-globally'] = $d $d = New-BaseDocument $d.rules[1].match.conditions[0] = @{ type = 'property'; property = 'employeeHireDate'; operator = 'isNotNull' } $cases['PE-SEM-015-unsupported-property'] = $d $d = New-BaseDocument $d.rules[1].match.conditions[0] = @{ type = 'property'; property = 'Department'; operator = 'matchesRegex'; value = '[unclosed' } $cases['PE-SEM-016-invalid-regex'] = $d # ---------------------------------------------------------------- VR-003 $d = New-BaseDocument $d.engine.targetAttribute = '' $d.engine.approvedWritableAttributes = @('extension__') $cases['PE-SAF-001-blank-target-production'] = $d $d = New-BaseDocument $d.engine.targetAttribute = 'department' $d.engine.approvedWritableAttributes = @('department') $cases['PE-SAF-002-unsupported-writable-attribute'] = $d $d = New-BaseDocument $d.dataSources.groups.enabled = $false $d.rules += New-MembershipRule -Condition @{ type = 'membership'; operator = 'memberOf'; groupObjectIds = @('00000000-0000-0000-0000-0000000000a0') } $cases['PE-SAF-003-group-rules-without-group-retrieval'] = $d $d = New-BaseDocument $d.configVersion = '0.9.0' $cases['PE-SAF-004-version-downgrade'] = $d $d = New-BaseDocument $d.rules = @($d.rules[0]) $cases['PE-SAF-005-rule-removed-without-version-change'] = $d $d = New-BaseDocument $d.logging.traceConditionValues = $true $cases['PE-SAF-006-tracing-without-acknowledgement'] = $d # ---------------------------------------------------------------- baseline # The comparison baseline for PE-SAF-004 and PE-SAF-005. Valid on its own. $d = New-BaseDocument $cases['baseline-deployed'] = $d foreach ($name in $cases.Keys) { $path = Join-Path $OutputDirectory "$name.json" Set-Content -LiteralPath $path -Value ($cases[$name] | ConvertTo-Json -Depth 32) -Encoding utf8NoBOM Write-Host "wrote $name.json" } Write-Host ("{0} fixture(s) written to {1}" -f $cases.Count, $OutputDirectory)