Removed extension attribute requirement for testing
This commit is contained in:
@@ -60,7 +60,7 @@ cd persona-engine
|
||||
### Step 2 — Prove the machine can run it, before touching a tenant
|
||||
|
||||
```bash
|
||||
pwsh -NoProfile -Command "& { $c = & ./tests/PesterConfiguration.ps1 -Suite Offline; Invoke-Pester -Configuration $c }"
|
||||
pwsh -NoProfile -Command '& { $c = & ./tests/PesterConfiguration.ps1 -Suite Offline; Invoke-Pester -Configuration $c }'
|
||||
```
|
||||
|
||||
Expect **354 passed, 0 failed**. This needs no credentials and no network. If it does not pass, stop
|
||||
|
||||
@@ -149,6 +149,42 @@ function Get-PersonaGraphStatusCode {
|
||||
return $null
|
||||
}
|
||||
|
||||
function Test-PersonaTargetAttributeUnavailable {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
True when a Graph 400 means the target attribute does not exist in this
|
||||
tenant, rather than some other client error.
|
||||
|
||||
.DESCRIPTION
|
||||
A dev tenant with no app registration has no persona extension property.
|
||||
Requesting it in $select then fails with 400 and a message naming the
|
||||
property. This distinguishes that specific, recoverable case from every
|
||||
other 400 (bad query syntax, an unrelated bad property, a permission denial
|
||||
phrased as 400), which must still fail loudly rather than being swallowed.
|
||||
|
||||
.PARAMETER ErrorRecord
|
||||
The error caught from Invoke-PersonaGraphRequest.
|
||||
|
||||
.PARAMETER TargetAttribute
|
||||
The configured target attribute name.
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
[OutputType([bool])]
|
||||
param(
|
||||
[Parameter(Mandatory)] $ErrorRecord,
|
||||
[Parameter(Mandatory)] [string] $TargetAttribute
|
||||
)
|
||||
|
||||
if ((Get-PersonaGraphStatusCode -ErrorRecord $ErrorRecord) -ne 400) { return $false }
|
||||
|
||||
$text = @($ErrorRecord.Exception.Message, $ErrorRecord.ErrorDetails.Message) -join ' '
|
||||
if ([string]::IsNullOrWhiteSpace($text)) { return $false }
|
||||
|
||||
if ($text -notmatch [regex]::Escape($TargetAttribute)) { return $false }
|
||||
|
||||
[bool]($text -match '(?i)could not find a property|invalid property|is not a valid property|does not exist on type')
|
||||
}
|
||||
|
||||
function Get-PersonaRetryAfterMs {
|
||||
<#
|
||||
.SYNOPSIS
|
||||
|
||||
@@ -98,10 +98,33 @@ function Invoke-PersonaEngineRun {
|
||||
: @(Get-PersonaUsers -SelectProperties $selectProperties)
|
||||
}
|
||||
catch {
|
||||
if ((-not $IsEnforcing) -and (Test-PersonaTargetAttributeUnavailable -ErrorRecord $_ -TargetAttribute $TargetAttribute)) {
|
||||
# Dev/test tenants often have no app registration yet, so the persona
|
||||
# extension property was never created. Preview mode never writes
|
||||
# regardless of what StoredPersona holds, so treating the attribute as
|
||||
# absent (null) here is safe and lets development proceed without one.
|
||||
# Enforcement still fails loudly - IsEnforcing gates this precisely
|
||||
# because writing to an attribute that does not exist must never be
|
||||
# silently tolerated.
|
||||
Write-Warning "Target attribute '$TargetAttribute' was not found in this tenant (no app registration / extension property?). Continuing in What-If mode with it treated as null for every account."
|
||||
|
||||
$fallbackProperties = @($selectProperties | Where-Object { $_ -cne $TargetAttribute })
|
||||
|
||||
try {
|
||||
$users = $UserObjectId `
|
||||
? @(Get-PersonaUsers -SelectProperties $fallbackProperties -UserObjectId $UserObjectId) `
|
||||
: @(Get-PersonaUsers -SelectProperties $fallbackProperties)
|
||||
}
|
||||
catch {
|
||||
return New-PersonaRunOutcome -Counters $counters -ExitCode $EXIT_ENUMERATION -FailureReason $_.Exception.Message
|
||||
}
|
||||
}
|
||||
else {
|
||||
# A partial population is worse than none: half a tenant classified looks
|
||||
# like a successful run to everything downstream.
|
||||
return New-PersonaRunOutcome -Counters $counters -ExitCode $EXIT_ENUMERATION -FailureReason $_.Exception.Message
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($graphUser in $users) {
|
||||
|
||||
|
||||
@@ -142,6 +142,68 @@ Describe 'Exit codes produced by the run loop' {
|
||||
}
|
||||
}
|
||||
|
||||
Describe 'Target attribute unavailable - dev tenants without an app registration' {
|
||||
|
||||
BeforeAll {
|
||||
$script:unavailableError = "Response status code does not indicate success: 400 (Bad Request): Could not find a property named '$($script:target)' on type 'microsoft.graph.user'."
|
||||
}
|
||||
|
||||
BeforeEach {
|
||||
Mock Write-Host { }
|
||||
Mock Write-Verbose { }
|
||||
Mock Write-Warning { }
|
||||
}
|
||||
|
||||
It 'continues in What-If mode, treating the attribute as null, when it is not registered' {
|
||||
Mock Get-PersonaUsers {
|
||||
param($SelectProperties, $UserObjectId, $PageSize)
|
||||
if ($SelectProperties -ccontains $script:target) { throw $script:unavailableError }
|
||||
@(New-TestPopulation -Count 5 -TargetAttribute $script:target)
|
||||
}
|
||||
|
||||
$outcome = Invoke-PersonaEngineRun -Configuration (New-TestRuntimeConfiguration -TargetAttribute $script:target) `
|
||||
-TargetAttribute $script:target -Context (New-TestAuditContext) -IsEnforcing:$false
|
||||
|
||||
$outcome.ExitCode | Should -Be 0
|
||||
$outcome.Counters.Processed | Should -Be 5
|
||||
Should -Invoke Get-PersonaUsers -Times 2 -Exactly
|
||||
Should -Invoke Write-Warning -Times 1 -Exactly
|
||||
}
|
||||
|
||||
It 'requests everything except the target attribute on the retry' {
|
||||
Mock Get-PersonaUsers {
|
||||
param($SelectProperties, $UserObjectId, $PageSize)
|
||||
if ($SelectProperties -ccontains $script:target) { throw $script:unavailableError }
|
||||
@(New-TestPopulation -Count 5 -TargetAttribute $script:target)
|
||||
}
|
||||
|
||||
$null = Invoke-PersonaEngineRun -Configuration (New-TestRuntimeConfiguration -TargetAttribute $script:target) `
|
||||
-TargetAttribute $script:target -Context (New-TestAuditContext) -IsEnforcing:$false
|
||||
|
||||
Should -Invoke Get-PersonaUsers -Times 1 -Exactly -ParameterFilter { $SelectProperties -cnotcontains $script:target }
|
||||
}
|
||||
|
||||
It 'still fails enumeration in enforcement mode - the fallback never applies to a real write run' {
|
||||
Mock Get-PersonaUsers { throw $script:unavailableError }
|
||||
|
||||
$outcome = Invoke-PersonaEngineRun -Configuration (New-TestRuntimeConfiguration -TargetAttribute $script:target) `
|
||||
-TargetAttribute $script:target -Context (New-TestAuditContext -Mode 'Enforce') -IsEnforcing
|
||||
|
||||
$outcome.ExitCode | Should -Be 3
|
||||
Should -Invoke Get-PersonaUsers -Times 1 -Exactly
|
||||
}
|
||||
|
||||
It 'does not swallow an unrelated enumeration failure even in What-If mode' {
|
||||
Mock Get-PersonaUsers { throw 'Graph request failed after 5 attempt(s) (last status: 503): service unavailable' }
|
||||
|
||||
$outcome = Invoke-PersonaEngineRun -Configuration (New-TestRuntimeConfiguration -TargetAttribute $script:target) `
|
||||
-TargetAttribute $script:target -Context (New-TestAuditContext) -IsEnforcing:$false
|
||||
|
||||
$outcome.ExitCode | Should -Be 3
|
||||
Should -Invoke Get-PersonaUsers -Times 1 -Exactly
|
||||
}
|
||||
}
|
||||
|
||||
Describe 'Exit codes owned by the entry script' {
|
||||
|
||||
BeforeAll {
|
||||
|
||||
Reference in New Issue
Block a user