|
| 1 | +#Requires -Version 7.0 |
| 2 | +<# |
| 3 | +.SYNOPSIS |
| 4 | + Validates configure_auth.ps1 --preflight-only behavior under each |
| 5 | + insufficient-permission scenario. No real Azure credentials are required. |
| 6 | +
|
| 7 | +.DESCRIPTION |
| 8 | + Creates temporary mock az.cmd / azd.cmd executables (backed by PowerShell |
| 9 | + helper scripts) in a temp folder, prepends that folder to PATH, then calls |
| 10 | + configure_auth.ps1 --preflight-only for each scenario and asserts the |
| 11 | + expected exit code and output text. |
| 12 | +
|
| 13 | + Scenarios tested (10 total): |
| 14 | + T01 Happy path — all checks pass |
| 15 | + T02 Check 1: Azure CLI not authenticated |
| 16 | + T03 Check 2: required azd env values missing |
| 17 | + T04 Check 3: Container Apps CLI extension absent |
| 18 | + T05 Check 4: no Contributor/Owner RBAC on resource group |
| 19 | + T06 Check 5: cannot read Entra app registrations |
| 20 | + T07 Check 6: target Container App is inaccessible |
| 21 | + T08 Check 7: Entra role below Application Administrator (FAIL) |
| 22 | + T09 Check 7: consent-only WARN — non-fatal (exit 0) |
| 23 | + T10 Check 7: service-principal login — dir check skipped (exit 0) |
| 24 | +
|
| 25 | +.EXAMPLE |
| 26 | + .\tests\infra-scripts\test_configure_auth_preflight.ps1 |
| 27 | +#> |
| 28 | + |
| 29 | +$ErrorActionPreference = "Stop" |
| 30 | +$ScriptDir = $PSScriptRoot |
| 31 | +$RepoRoot = Resolve-Path (Join-Path $ScriptDir "../..") |
| 32 | +$Subject = Join-Path $RepoRoot "infra/scripts/configure_auth.ps1" |
| 33 | + |
| 34 | +if (-not (Test-Path $Subject)) { |
| 35 | + Write-Error "configure_auth.ps1 not found at $Subject" |
| 36 | + exit 1 |
| 37 | +} |
| 38 | + |
| 39 | +$PassCount = 0 |
| 40 | +$FailCount = 0 |
| 41 | +$TempDir = Join-Path ([System.IO.Path]::GetTempPath()) "auth_pfl_test_$([guid]::NewGuid().ToString('N'))" |
| 42 | +New-Item -ItemType Directory -Path $TempDir | Out-Null |
| 43 | + |
| 44 | +# Clean up temp dir on script exit |
| 45 | +try { |
| 46 | + |
| 47 | +# ============================================================================= |
| 48 | +# Mock PowerShell helper for az.cmd |
| 49 | +# Behaviour controlled by AZ_MOCK_SCENARIO environment variable. |
| 50 | +# ============================================================================= |
| 51 | +$MockAzPs1Content = @' |
| 52 | +$allArgs = $args |
| 53 | +$SCENARIO = if ($env:AZ_MOCK_SCENARIO) { $env:AZ_MOCK_SCENARIO } else { "happy" } |
| 54 | +$S = ($allArgs -join " ") |
| 55 | +function has($t) { $S -like "*$t*" } |
| 56 | +
|
| 57 | +if ((has "account show") -and -not (has "role assignment")) { |
| 58 | + if ($SCENARIO -eq "no_auth") { exit 1 } |
| 59 | + if (has "tenantId") { Write-Output "mock-tenant-id"; exit 0 } |
| 60 | + if (has "user.name") { Write-Output "sp-mock@service.principal"; exit 0 } |
| 61 | + Write-Output "mock-sub-id-12345"; exit 0 |
| 62 | +} |
| 63 | +if (has "signed-in-user") { |
| 64 | + if ($SCENARIO -eq "sp_login") { exit 1 } |
| 65 | + Write-Output "mock-user-object-id-abc123"; exit 0 |
| 66 | +} |
| 67 | +if (has "role assignment list") { |
| 68 | + if ($SCENARIO -eq "no_rbac") { Write-Output ""; exit 0 } |
| 69 | + Write-Output "Contributor"; exit 0 |
| 70 | +} |
| 71 | +if ((has "ad app list") -and -not (has "ad app show")) { |
| 72 | + if ($SCENARIO -eq "no_entra_read") { exit 1 } |
| 73 | + Write-Output "mock-app-id-00001"; exit 0 |
| 74 | +} |
| 75 | +if ((has "containerapp") -and (has " --help")) { |
| 76 | + if ($SCENARIO -eq "no_extension") { exit 1 } |
| 77 | + exit 0 |
| 78 | +} |
| 79 | +if (has "containerapp show") { |
| 80 | + if ($SCENARIO -eq "no_container_app") { exit 1 } |
| 81 | + Write-Output "ca-testenv-web"; exit 0 |
| 82 | +} |
| 83 | +if (has "rest") { |
| 84 | + switch ($SCENARIO) { |
| 85 | + "insufficient_dir_role" { Write-Output "Directory Readers"; exit 0 } |
| 86 | + "consent_warn_only" { Write-Output "Application Administrator"; exit 0 } |
| 87 | + default { Write-Output "Global Administrator"; exit 0 } |
| 88 | + } |
| 89 | +} |
| 90 | +if (has "ad app show") { exit 1 } |
| 91 | +exit 0 |
| 92 | +'@ |
| 93 | + |
| 94 | +# ============================================================================= |
| 95 | +# Mock PowerShell helper for azd.cmd |
| 96 | +# Behaviour controlled by AZD_MOCK_SCENARIO environment variable. |
| 97 | +# ============================================================================= |
| 98 | +$MockAzdPs1Content = @' |
| 99 | +$allArgs = $args |
| 100 | +$SCENARIO = if ($env:AZD_MOCK_SCENARIO) { $env:AZD_MOCK_SCENARIO } else { "happy" } |
| 101 | +$S = ($allArgs -join " ") |
| 102 | +
|
| 103 | +if ($S -like "*env get-value*") { |
| 104 | + $KEY = $allArgs[-1] |
| 105 | + if ($SCENARIO -eq "no_env") { |
| 106 | + if ($KEY -eq "AZURE_ENV_NAME") { Write-Output "testenv" } else { Write-Output "" } |
| 107 | + exit 0 |
| 108 | + } |
| 109 | + switch ($KEY) { |
| 110 | + "AZURE_ENV_NAME" { Write-Output "testenv" } |
| 111 | + "AZURE_RESOURCE_GROUP" { Write-Output "mock-rg" } |
| 112 | + "AZURE_SUBSCRIPTION_ID" { Write-Output "mock-sub-id" } |
| 113 | + "AZURE_TENANT_ID" { Write-Output "mock-tenant-id" } |
| 114 | + "CONTAINER_WEB_APP_NAME" { Write-Output "ca-testenv-web" } |
| 115 | + "CONTAINER_WEB_APP_FQDN" { Write-Output "ca-testenv-web.azurecontainerapps.io" } |
| 116 | + "CONTAINER_API_APP_NAME" { Write-Output "ca-testenv-api" } |
| 117 | + "CONTAINER_API_APP_FQDN" { Write-Output "ca-testenv-api.azurecontainerapps.io" } |
| 118 | + default { Write-Output "" } |
| 119 | + } |
| 120 | + exit 0 |
| 121 | +} |
| 122 | +exit 0 |
| 123 | +'@ |
| 124 | + |
| 125 | +# Write helper scripts |
| 126 | +$MockAzPs1Content | Out-File -FilePath (Join-Path $TempDir "mock_az.ps1") -Encoding UTF8 |
| 127 | +$MockAzdPs1Content | Out-File -FilePath (Join-Path $TempDir "mock_azd.ps1") -Encoding UTF8 |
| 128 | + |
| 129 | +# Write .cmd wrappers — %~dp0 resolves to the directory containing the .cmd file |
| 130 | +@" |
| 131 | +@echo off |
| 132 | +pwsh -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "%~dp0mock_az.ps1" %* |
| 133 | +"@ | Out-File -FilePath (Join-Path $TempDir "az.cmd") -Encoding ASCII |
| 134 | + |
| 135 | +@" |
| 136 | +@echo off |
| 137 | +pwsh -NoProfile -NonInteractive -ExecutionPolicy Bypass -File "%~dp0mock_azd.ps1" %* |
| 138 | +"@ | Out-File -FilePath (Join-Path $TempDir "azd.cmd") -Encoding ASCII |
| 139 | + |
| 140 | +# ============================================================================= |
| 141 | +# Test runner |
| 142 | +# ============================================================================= |
| 143 | +function Run-Test { |
| 144 | + param( |
| 145 | + [string]$Name, |
| 146 | + [int] $ExpectedExit, |
| 147 | + [string]$ExpectedText = "", |
| 148 | + [string]$AzScenario = "happy", |
| 149 | + [string]$AzdScenario = "happy" |
| 150 | + ) |
| 151 | + |
| 152 | + $origPath = $env:PATH |
| 153 | + $env:PATH = "$TempDir;$env:PATH" |
| 154 | + $env:AZ_MOCK_SCENARIO = $AzScenario |
| 155 | + $env:AZD_MOCK_SCENARIO = $AzdScenario |
| 156 | + $env:AZURE_SKIP_AUTH_SETUP = "" |
| 157 | + |
| 158 | + $rawOutput = pwsh -NoProfile -NonInteractive -ExecutionPolicy Bypass ` |
| 159 | + -File $Subject "--preflight-only" 2>&1 |
| 160 | + $exitCode = $LASTEXITCODE |
| 161 | + $outputStr = ($rawOutput | Out-String) |
| 162 | + |
| 163 | + $env:PATH = $origPath |
| 164 | + $env:AZ_MOCK_SCENARIO = $null |
| 165 | + $env:AZD_MOCK_SCENARIO = $null |
| 166 | + |
| 167 | + $ok = $true; $reason = "" |
| 168 | + if ($exitCode -ne $ExpectedExit) { |
| 169 | + $ok = $false; $reason = "exit $exitCode (expected $ExpectedExit)" |
| 170 | + } elseif ($ExpectedText -and ($outputStr -notlike "*$ExpectedText*")) { |
| 171 | + $ok = $false; $reason = "expected text '$ExpectedText' not in output" |
| 172 | + } |
| 173 | + |
| 174 | + if ($ok) { |
| 175 | + Write-Host (" `u{2705} {0,-62}" -f $Name) |
| 176 | + $script:PassCount++ |
| 177 | + } else { |
| 178 | + Write-Host (" `u{274C} {0,-62} [{1}]" -f $Name, $reason) |
| 179 | + ($outputStr -split "`n" | Select-Object -Last 4) | ForEach-Object { |
| 180 | + Write-Host " $_" |
| 181 | + } |
| 182 | + $script:FailCount++ |
| 183 | + } |
| 184 | +} |
| 185 | + |
| 186 | +# ============================================================================= |
| 187 | +# Test scenarios |
| 188 | +# ============================================================================= |
| 189 | +Write-Host "" |
| 190 | +Write-Host "============================================================" |
| 191 | +Write-Host " configure_auth.ps1 — preflight permission scenario tests" |
| 192 | +Write-Host "============================================================" |
| 193 | + |
| 194 | +# T01 — Happy path: every check should pass |
| 195 | +Run-Test "T01 Happy path: all checks pass" ` |
| 196 | + -ExpectedExit 0 -ExpectedText "Preflight-only mode" ` |
| 197 | + -AzScenario "happy" -AzdScenario "happy" |
| 198 | + |
| 199 | +# T02 — Check 1: Azure CLI not authenticated |
| 200 | +Run-Test "T02 Check 1: not authenticated" ` |
| 201 | + -ExpectedExit 1 -ExpectedText "Azure CLI authenticated" ` |
| 202 | + -AzScenario "no_auth" -AzdScenario "happy" |
| 203 | + |
| 204 | +# T03 — Check 2: required azd env values missing |
| 205 | +Run-Test "T03 Check 2: missing required azd env values" ` |
| 206 | + -ExpectedExit 1 -ExpectedText "Required azd env values" ` |
| 207 | + -AzScenario "happy" -AzdScenario "no_env" |
| 208 | + |
| 209 | +# T04 — Check 3: Azure Container Apps CLI extension absent |
| 210 | +Run-Test "T04 Check 3: containerapp CLI extension missing" ` |
| 211 | + -ExpectedExit 1 -ExpectedText "Container Apps CLI" ` |
| 212 | + -AzScenario "no_extension" -AzdScenario "happy" |
| 213 | + |
| 214 | +# T05 — Check 4: no Contributor or Owner role on resource group |
| 215 | +Run-Test "T05 Check 4: no RBAC Contributor/Owner on resource group" ` |
| 216 | + -ExpectedExit 1 -ExpectedText "Contributor/Owner" ` |
| 217 | + -AzScenario "no_rbac" -AzdScenario "happy" |
| 218 | + |
| 219 | +# T06 — Check 5: cannot read Entra app registrations |
| 220 | +Run-Test "T06 Check 5: cannot read Entra app registrations" ` |
| 221 | + -ExpectedExit 1 -ExpectedText "Entra app registrations" ` |
| 222 | + -AzScenario "no_entra_read" -AzdScenario "happy" |
| 223 | + |
| 224 | +# T07 — Check 6: target Container App is inaccessible |
| 225 | +Run-Test "T07 Check 6: Container App is inaccessible" ` |
| 226 | + -ExpectedExit 1 -ExpectedText "Container App" ` |
| 227 | + -AzScenario "no_container_app" -AzdScenario "happy" |
| 228 | + |
| 229 | +# T08 — Check 7: Entra role present but below Application Administrator (FAIL) |
| 230 | +Run-Test "T08 Check 7: insufficient Entra directory role (FAIL)" ` |
| 231 | + -ExpectedExit 1 -ExpectedText "App-registration permission" ` |
| 232 | + -AzScenario "insufficient_dir_role" -AzdScenario "happy" |
| 233 | + |
| 234 | +# T09 — Check 7: Application Administrator present, consent role absent (WARN, non-fatal) |
| 235 | +Run-Test "T09 Check 7: consent-only WARN is non-fatal (exit 0)" ` |
| 236 | + -ExpectedExit 0 -ExpectedText "Admin-consent permission" ` |
| 237 | + -AzScenario "consent_warn_only" -AzdScenario "happy" |
| 238 | + |
| 239 | +# T10 — Check 7: service principal login — directory-role check skipped (WARN, non-fatal) |
| 240 | +Run-Test "T10 Check 7: SP login — directory-role check skipped (exit 0)" ` |
| 241 | + -ExpectedExit 0 -ExpectedText "directory-role check" ` |
| 242 | + -AzScenario "sp_login" -AzdScenario "happy" |
| 243 | + |
| 244 | +# ============================================================================= |
| 245 | +# Summary |
| 246 | +# ============================================================================= |
| 247 | +Write-Host "" |
| 248 | +Write-Host "============================================================" |
| 249 | +Write-Host " Results: $PassCount passed, $FailCount failed" |
| 250 | +Write-Host "============================================================" |
| 251 | +Write-Host "" |
| 252 | + |
| 253 | +} finally { |
| 254 | + Remove-Item -Path $TempDir -Recurse -Force -ErrorAction SilentlyContinue |
| 255 | +} |
| 256 | + |
| 257 | +if ($FailCount -gt 0) { exit 1 } |
0 commit comments