Skip to content

Commit fb7cfea

Browse files
authored
feat: split artifact validation into separate round-trip phase (phase 3)
1 parent 67b7f1a commit fb7cfea

8 files changed

Lines changed: 132 additions & 62 deletions

.github/workflows/integration-test.yml

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -164,19 +164,33 @@ jobs:
164164
throw "Invalid log_level '$logLevel'. Allowed values: Info, Verbose, Debug."
165165
}
166166
167-
$skuName = '${{ secrets.APIM_SKU }}'
168-
if ([string]::IsNullOrWhiteSpace($skuName)) { $skuName = '${{ inputs.sku }}' }
169-
if ([string]::IsNullOrWhiteSpace($skuName)) { $skuName = 'StandardV2' }
170-
171167
./tests/integration/all-resource-types/run-roundtrip-phase2-extract.ps1 `
172168
-SourceSubscriptionId '${{ secrets.AZURE_SUBSCRIPTION_ID }}' `
173169
-SourceResourceGroup '${{ env.SOURCE_RG }}' `
174170
-SourceApimName '${{ env.SOURCE_APIM }}' `
171+
-LogLevel $logLevel `
172+
-ExtractOutputDir './extracted-artifacts'
173+
174+
- name: Run Round-Trip Phase 3 (Validate Extract)
175+
if: success()
176+
shell: pwsh
177+
run: |
178+
$logLevel = '${{ inputs.log_level }}'
179+
if ([string]::IsNullOrWhiteSpace($logLevel)) { $logLevel = 'Verbose' }
180+
if ($logLevel -notin @('Info', 'Verbose', 'Debug')) {
181+
throw "Invalid log_level '$logLevel'. Allowed values: Info, Verbose, Debug."
182+
}
183+
184+
$skuName = '${{ secrets.APIM_SKU }}'
185+
if ([string]::IsNullOrWhiteSpace($skuName)) { $skuName = '${{ inputs.sku }}' }
186+
if ([string]::IsNullOrWhiteSpace($skuName)) { $skuName = 'StandardV2' }
187+
188+
./tests/integration/all-resource-types/run-roundtrip-phase3-validate-extract.ps1 `
175189
-SkuName $skuName `
176190
-LogLevel $logLevel `
177191
-ExtractOutputDir './extracted-artifacts'
178192
179-
- name: Run Round-Trip Phase 3 (Publish)
193+
- name: Run Round-Trip Phase 4 (Publish)
180194
if: success()
181195
shell: pwsh
182196
env:
@@ -189,14 +203,14 @@ jobs:
189203
throw "Invalid log_level '$logLevel'. Allowed values: Info, Verbose, Debug."
190204
}
191205
192-
./tests/integration/all-resource-types/run-roundtrip-phase3-publish.ps1 `
206+
./tests/integration/all-resource-types/run-roundtrip-phase4-publish.ps1 `
193207
-TargetSubscriptionId '${{ secrets.AZURE_SUBSCRIPTION_ID }}' `
194208
-TargetResourceGroup '${{ env.TARGET_RG }}' `
195209
-TargetApimName '${{ env.TARGET_APIM }}' `
196210
-LogLevel $logLevel `
197211
-ExtractOutputDir './extracted-artifacts'
198212
199-
- name: Run Round-Trip Phase 4 (Compare)
213+
- name: Run Round-Trip Phase 5 (Compare)
200214
if: success()
201215
shell: pwsh
202216
run: |
@@ -206,7 +220,7 @@ jobs:
206220
throw "Invalid log_level '$logLevel'. Allowed values: Info, Verbose, Debug."
207221
}
208222
209-
./tests/integration/all-resource-types/run-roundtrip-phase4-compare.ps1 `
223+
./tests/integration/all-resource-types/run-roundtrip-phase5-compare.ps1 `
210224
-SourceSubscriptionId '${{ secrets.AZURE_SUBSCRIPTION_ID }}' `
211225
-SourceResourceGroup '${{ env.SOURCE_RG }}' `
212226
-SourceApimName '${{ env.SOURCE_APIM }}' `
@@ -223,11 +237,11 @@ jobs:
223237
path: ./extracted-artifacts/
224238
if-no-files-found: ignore
225239

226-
- name: Run Round-Trip Phase 5 (Teardown)
240+
- name: Run Round-Trip Phase 6 (Teardown)
227241
if: always()
228242
shell: pwsh
229243
run: |
230-
./tests/integration/all-resource-types/run-roundtrip-phase5-teardown.ps1 `
244+
./tests/integration/all-resource-types/run-roundtrip-phase6-teardown.ps1 `
231245
-SourceResourceGroup '${{ env.SOURCE_RG }}' `
232246
-TargetResourceGroup '${{ env.TARGET_RG }}' `
233247
-Location '${{ inputs.location }}' `

tests/integration/all-resource-types/README.md

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -109,41 +109,51 @@ Requires an `integration-test` environment with secrets:
109109
- `AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, `AZURE_SUBSCRIPTION_ID` (OIDC)
110110
- `APIM_PUBLISHER_EMAIL`
111111

112-
### Phases 2–4 Scripts
112+
### Phases 2–5 Scripts
113113

114-
Phases 2–4 are independent scripts that can be called separately for targeted re-runs, without re-executing the full round-trip:
114+
Phases 2–5 are independent scripts that can be called separately for targeted re-runs, without re-executing the full round-trip:
115115

116116
#### Phase 2 — Extract
117117

118-
Extracts artifacts from the source APIM and validates the extracted structure.
118+
Extracts artifacts from the source APIM.
119119

120120
```powershell
121121
.\run-roundtrip-phase2-extract.ps1 `
122122
-SourceSubscriptionId "<source-sub-id>" `
123123
-SourceResourceGroup "rg-src" `
124-
-SourceApimName "apim-src" `
124+
-SourceApimName "apim-src"
125+
```
126+
127+
#### Phase 3 — Validate Extract
128+
129+
Validates the extracted artifact structure against the expected-structure manifest.
130+
131+
```powershell
132+
.\run-roundtrip-phase3-validate-extract.ps1 `
125133
-SkuName "StandardV2"
126134
```
127135

128-
#### Phase 3 — Publish
136+
Requires `ExtractOutputDir` (default: `./extracted-artifacts`) to already be populated by the extract step.
137+
138+
#### Phase 4 — Publish
129139

130140
Generates target environment overrides (Key Vault, App Insights, Event Hub) and publishes the extracted artifacts to the target APIM.
131141

132142
```powershell
133-
.\run-roundtrip-phase3-publish.ps1 `
143+
.\run-roundtrip-phase4-publish.ps1 `
134144
-TargetSubscriptionId "<target-sub-id>" `
135145
-TargetResourceGroup "rg-tgt" `
136146
-TargetApimName "apim-tgt"
137147
```
138148

139149
Requires `ExtractOutputDir` (default: `./extracted-artifacts`) to already be populated by the extract step.
140150

141-
#### Phase 4 — Compare
151+
#### Phase 5 — Compare
142152

143153
Compares source and target APIM instances via ARM REST API with deep property normalization.
144154

145155
```powershell
146-
.\run-roundtrip-phase4-compare.ps1 `
156+
.\run-roundtrip-phase5-compare.ps1 `
147157
-SourceSubscriptionId "<source-sub-id>" `
148158
-SourceResourceGroup "rg-src" `
149159
-SourceApimName "apim-src" `
@@ -154,7 +164,7 @@ Compares source and target APIM instances via ARM REST API with deep property no
154164

155165
Exit codes: `0` = match, `1` = differences found, `2` = error.
156166

157-
All three scripts accept `-LogLevel` (Info/Verbose/Debug), and phases 2 and 3 accept `-ExtractOutputDir`.
167+
All scripts accept `-LogLevel` (Info/Verbose/Debug), and phases 2–4 accept `-ExtractOutputDir`.
158168

159169
### Comparison Script
160170

@@ -177,9 +187,10 @@ Exit codes: `0` = match, `1` = differences found, `2` = error.
177187
| `run-roundtrip-test.ps1` | Master orchestrator for the full test |
178188
| `run-roundtrip-phase1-deploy.ps1` | Phase 1: deploy source + target APIM instances |
179189
| `run-roundtrip-phase2-extract.ps1` | Phase 2: extract artifacts from source APIM |
180-
| `run-roundtrip-phase3-publish.ps1` | Phase 3: generate overrides + publish to target |
181-
| `run-roundtrip-phase4-compare.ps1` | Phase 4: compare source vs target via ARM |
182-
| `run-roundtrip-phase5-teardown.ps1` | Phase 5: tear down resource groups |
190+
| `run-roundtrip-phase3-validate-extract.ps1` | Phase 3: validate extracted artifact structure |
191+
| `run-roundtrip-phase4-publish.ps1` | Phase 4: generate overrides + publish to target |
192+
| `run-roundtrip-phase5-compare.ps1` | Phase 5: compare source vs target via ARM |
193+
| `run-roundtrip-phase6-teardown.ps1` | Phase 6: tear down resource groups |
183194
| `Compare-ApimInstance.ps1` | ARM REST comparison script (standalone) |
184195
| `Test-ExtractedArtifact.ps1` | Validate extracted artifact structure |
185196
| `expected-structure.json` | Manifest of expected extracted files |

tests/integration/all-resource-types/run-roundtrip-phase2-extract.ps1

Lines changed: 5 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#requires -Version 7.0
22
<#
33
.SYNOPSIS
4-
Phase 2 — Extract artifacts from the source APIM instance and validate structure.
4+
Phase 2 — Extract artifacts from the source APIM instance.
55
#>
66

77
[CmdletBinding()]
@@ -15,10 +15,6 @@ param(
1515
[Parameter(Mandatory)]
1616
[string]$SourceApimName,
1717

18-
[Parameter(Mandatory)]
19-
[ValidateSet('Developer', 'Premium', 'StandardV2', 'PremiumV2')]
20-
[string]$SkuName,
21-
2218
[ValidateSet('Info', 'Verbose', 'Debug')]
2319
[string]$LogLevel = 'Verbose',
2420

@@ -29,12 +25,10 @@ $ErrorActionPreference = 'Stop'
2925
$VerbosePreference = if ($LogLevel -in @('Verbose', 'Debug')) { 'Continue' } else { 'SilentlyContinue' }
3026
$DebugPreference = if ($LogLevel -eq 'Debug') { 'Continue' } else { 'SilentlyContinue' }
3127

32-
$maskingModule = Join-Path $PSScriptRoot 'MaskingHelpers.psm1'
33-
$apiopsModule = Join-Path $PSScriptRoot 'ApiopsHelpers.psm1'
34-
$validateScript = Join-Path $PSScriptRoot 'Test-ExtractedArtifact.ps1'
35-
$manifestFile = Join-Path $PSScriptRoot 'expected-structure.json'
28+
$maskingModule = Join-Path $PSScriptRoot 'MaskingHelpers.psm1'
29+
$apiopsModule = Join-Path $PSScriptRoot 'ApiopsHelpers.psm1'
3630

37-
foreach ($requiredFile in @($maskingModule, $apiopsModule, $validateScript, $manifestFile)) {
31+
foreach ($requiredFile in @($maskingModule, $apiopsModule)) {
3832
if (-not (Test-Path $requiredFile)) {
3933
Write-Error "Required file not found: $requiredFile"
4034
exit 2
@@ -44,7 +38,6 @@ foreach ($requiredFile in @($maskingModule, $apiopsModule, $validateScript, $man
4438
Import-Module $maskingModule -Force
4539
Import-Module $apiopsModule -Force
4640

47-
$exitCode = 0
4841
$apiopsLogLevel = Get-ApiopsLogLevel -ScriptLogLevel $LogLevel
4942
$apiopsAuthArgs = Get-ApiopsAuthArgs
5043

@@ -80,22 +73,4 @@ if (-not $extractedFiles -or $extractedFiles.Count -eq 0) {
8073
exit 2
8174
}
8275

83-
Write-Host "🔎 Extract — Validate extracted artifact structure"
84-
$validateArgs = @{
85-
ExtractedDir = $ExtractOutputDir
86-
ManifestFile = $manifestFile
87-
SkuName = $SkuName
88-
}
89-
switch ($LogLevel) {
90-
'Verbose' { $validateArgs.Verbose = $true }
91-
'Debug' { $validateArgs.Debug = $true }
92-
}
93-
& $validateScript @validateArgs
94-
$validateExitCode = $LASTEXITCODE
95-
if ($validateExitCode -ne 0) {
96-
Write-Host "❌ Artifact validation failed (exit code $validateExitCode)"
97-
$exitCode = if ($validateExitCode -eq 2) { 2 } else { 1 }
98-
Write-Host "⚠️ Continuing despite validation failures..."
99-
}
100-
101-
exit $exitCode
76+
exit 0
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
#requires -Version 7.0
2+
<#
3+
.SYNOPSIS
4+
Phase 3 — Validate extracted artifact structure against the expected-structure manifest.
5+
#>
6+
7+
[CmdletBinding()]
8+
param(
9+
[Parameter(Mandatory)]
10+
[ValidateSet('Developer', 'Premium', 'StandardV2', 'PremiumV2')]
11+
[string]$SkuName,
12+
13+
[ValidateSet('Info', 'Verbose', 'Debug')]
14+
[string]$LogLevel = 'Verbose',
15+
16+
[string]$ExtractOutputDir = "$PSScriptRoot/extracted-artifacts"
17+
)
18+
19+
$ErrorActionPreference = 'Stop'
20+
$VerbosePreference = if ($LogLevel -in @('Verbose', 'Debug')) { 'Continue' } else { 'SilentlyContinue' }
21+
$DebugPreference = if ($LogLevel -eq 'Debug') { 'Continue' } else { 'SilentlyContinue' }
22+
23+
$validateScript = Join-Path $PSScriptRoot 'Test-ExtractedArtifact.ps1'
24+
$manifestFile = Join-Path $PSScriptRoot 'expected-structure.json'
25+
26+
foreach ($requiredFile in @($validateScript, $manifestFile)) {
27+
if (-not (Test-Path $requiredFile)) {
28+
Write-Error "Required file not found: $requiredFile"
29+
exit 2
30+
}
31+
}
32+
33+
if (-not (Test-Path $ExtractOutputDir)) {
34+
Write-Error "ExtractOutputDir not found: $ExtractOutputDir — run the extract step first"
35+
exit 2
36+
}
37+
38+
Write-Host "🔎 Extract — Validate extracted artifact structure"
39+
$validateArgs = @{
40+
ExtractedDir = $ExtractOutputDir
41+
ManifestFile = $manifestFile
42+
SkuName = $SkuName
43+
}
44+
switch ($LogLevel) {
45+
'Verbose' { $validateArgs.Verbose = $true }
46+
'Debug' { $validateArgs.Debug = $true }
47+
}
48+
& $validateScript @validateArgs
49+
$validateExitCode = $LASTEXITCODE
50+
51+
if ($validateExitCode -ne 0) {
52+
Write-Host "❌ Artifact validation failed (exit code $validateExitCode)"
53+
exit $validateExitCode
54+
}
55+
56+
exit 0

tests/integration/all-resource-types/run-roundtrip-phase3-publish.ps1 renamed to tests/integration/all-resource-types/run-roundtrip-phase4-publish.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#requires -Version 7.0
22
<#
33
.SYNOPSIS
4-
Phase 3 — Generate target environment overrides and publish artifacts to target APIM.
4+
Phase 4 — Generate target environment overrides and publish artifacts to target APIM.
55
#>
66

77
[CmdletBinding()]

tests/integration/all-resource-types/run-roundtrip-phase4-compare.ps1 renamed to tests/integration/all-resource-types/run-roundtrip-phase5-compare.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#requires -Version 7.0
22
<#
33
.SYNOPSIS
4-
Phase 4 — Compare source and target APIM instances via ARM REST API.
4+
Phase 5 — Compare source and target APIM instances via ARM REST API.
55
#>
66

77
[CmdletBinding()]

tests/integration/all-resource-types/run-roundtrip-phase5-teardown.ps1 renamed to tests/integration/all-resource-types/run-roundtrip-phase6-teardown.ps1

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
#requires -Version 7.0
2+
<#
3+
.SYNOPSIS
4+
Phase 6 — Tear down source and target APIM resource groups.
5+
#>
26

37
[CmdletBinding()]
48
param(
@@ -24,7 +28,7 @@ if ($SkipTeardown) {
2428
exit 0
2529
}
2630

27-
Write-Host "🧹 PHASE 5 — Teardown"
31+
Write-Host "🧹 PHASE 6 — Teardown"
2832

2933
$sourceApimName = $null
3034
$targetApimName = $null

tests/integration/all-resource-types/run-roundtrip-test.ps1

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,12 @@ if (-not $SourceSubscriptionId -or -not $TargetSubscriptionId) {
8585

8686
$phase1Script = Join-Path $PSScriptRoot 'run-roundtrip-phase1-deploy.ps1'
8787
$phase2Script = Join-Path $PSScriptRoot 'run-roundtrip-phase2-extract.ps1'
88-
$phase3Script = Join-Path $PSScriptRoot 'run-roundtrip-phase3-publish.ps1'
89-
$phase4Script = Join-Path $PSScriptRoot 'run-roundtrip-phase4-compare.ps1'
90-
$phase5Script = Join-Path $PSScriptRoot 'run-roundtrip-phase5-teardown.ps1'
88+
$phase3Script = Join-Path $PSScriptRoot 'run-roundtrip-phase3-validate-extract.ps1'
89+
$phase4Script = Join-Path $PSScriptRoot 'run-roundtrip-phase4-publish.ps1'
90+
$phase5Script = Join-Path $PSScriptRoot 'run-roundtrip-phase5-compare.ps1'
91+
$phase6Script = Join-Path $PSScriptRoot 'run-roundtrip-phase6-teardown.ps1'
9192

92-
foreach ($requiredFile in @($phase1Script, $phase2Script, $phase3Script, $phase4Script, $phase5Script)) {
93+
foreach ($requiredFile in @($phase1Script, $phase2Script, $phase3Script, $phase4Script, $phase5Script, $phase6Script)) {
9394
if (-not (Test-Path $requiredFile)) {
9495
Write-Error "Required file not found: $requiredFile"
9596
exit 2
@@ -120,7 +121,6 @@ try {
120121
-SourceSubscriptionId $SourceSubscriptionId `
121122
-SourceResourceGroup $SourceResourceGroup `
122123
-SourceApimName $SourceApimName `
123-
-SkuName $SkuName `
124124
-LogLevel $LogLevel `
125125
-ExtractOutputDir $ExtractOutputDir
126126

@@ -130,6 +130,16 @@ try {
130130
}
131131

132132
& $phase3Script `
133+
-SkuName $SkuName `
134+
-LogLevel $LogLevel `
135+
-ExtractOutputDir $ExtractOutputDir
136+
137+
if ($LASTEXITCODE -ne 0) {
138+
$exitCode = $LASTEXITCODE
139+
exit $exitCode
140+
}
141+
142+
& $phase4Script `
133143
-TargetSubscriptionId $TargetSubscriptionId `
134144
-TargetResourceGroup $TargetResourceGroup `
135145
-TargetApimName $TargetApimName `
@@ -141,7 +151,7 @@ try {
141151
exit $exitCode
142152
}
143153

144-
& $phase4Script `
154+
& $phase5Script `
145155
-SourceSubscriptionId $SourceSubscriptionId `
146156
-SourceResourceGroup $SourceResourceGroup `
147157
-SourceApimName $SourceApimName `
@@ -153,7 +163,7 @@ try {
153163
$exitCode = $LASTEXITCODE
154164
}
155165
finally {
156-
& $phase5Script `
166+
& $phase6Script `
157167
-SourceResourceGroup $SourceResourceGroup `
158168
-TargetResourceGroup $TargetResourceGroup `
159169
-Location $Location `

0 commit comments

Comments
 (0)