Skip to content

Commit 20f50d9

Browse files
CopilotEMaherCopilot
authored
feat: split integration test into separate phases (#92)
* Fix round-trip workflow auth context for apiops extract/publish * Refactor integration roundtrip into phased scripts * Update README to reflect new phases * re-arranging files into sub-folders. * updating masking behavior for auto-generated rg names * fixing issue in override file. * removing unnecessary folder * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * fix: remove unused SkuName from extract phase * adding default for location * adding overrides file handoff from phase 5 to 6 * fixing mcp probe * adding example to run test and save log * updating README to be more descriptive --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Elizabeth Maher <enewman@microsoft.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent 33c1150 commit 20f50d9

24 files changed

Lines changed: 1988 additions & 853 deletions

.github/workflows/integration-test.yml

Lines changed: 129 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# Licensed under the MIT license.
13
# ===========================================================================
24
# Integration Test — Extract→Publish Round-Trip
35
# ===========================================================================
@@ -31,6 +33,7 @@ on:
3133
required: true
3234
type: choice
3335
options:
36+
- Standard
3437
- StandardV2
3538
- Premium
3639
- PremiumV2
@@ -72,6 +75,8 @@ on:
7275
required: true
7376
APIM_PUBLISHER_EMAIL:
7477
required: true
78+
APIM_SKU:
79+
required: false
7580

7681
permissions:
7782
id-token: write
@@ -84,6 +89,8 @@ concurrency:
8489
env:
8590
SOURCE_RG: rg-apiops-bvt-source-${{ github.run_id }}
8691
TARGET_RG: rg-apiops-bvt-target-${{ github.run_id }}
92+
SOURCE_APIM: apiops-bvt-src-${{ github.run_id }}
93+
TARGET_APIM: apiops-bvt-tgt-${{ github.run_id }}
8794

8895
jobs:
8996
roundtrip-test:
@@ -103,14 +110,8 @@ jobs:
103110

104111
- run: npm ci && npm run build
105112

106-
- name: Azure Login (OIDC)
107-
uses: azure/login@v2
108-
with:
109-
client-id: ${{ secrets.AZURE_CLIENT_ID }}
110-
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
111-
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
112-
113-
- name: Run Round-Trip Test
113+
- name: Resolve Workflow Settings
114+
id: settings
114115
shell: pwsh
115116
run: |
116117
$logLevel = '${{ inputs.log_level }}'
@@ -119,61 +120,140 @@ jobs:
119120
throw "Invalid log_level '$logLevel'. Allowed values: Info, Verbose, Debug."
120121
}
121122
123+
$skuName = '${{ secrets.APIM_SKU }}'
124+
if ([string]::IsNullOrWhiteSpace($skuName)) { $skuName = '${{ inputs.sku }}' }
125+
if ([string]::IsNullOrWhiteSpace($skuName)) { $skuName = 'StandardV2' }
126+
127+
"logLevel=$logLevel" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
128+
"skuName=$skuName" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
129+
130+
- name: Azure Login
131+
uses: azure/login@v2
132+
with:
133+
client-id: ${{ secrets.AZURE_CLIENT_ID }}
134+
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
135+
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
136+
137+
- name: Run Round-Trip Phase 1 (Deploy)
138+
id: phase1
139+
shell: pwsh
140+
run: |
122141
$params = @{
123142
SourceResourceGroup = '${{ env.SOURCE_RG }}'
124143
TargetResourceGroup = '${{ env.TARGET_RG }}'
125-
SkuName = '${{ inputs.sku }}'
144+
SourceApimName = '${{ env.SOURCE_APIM }}'
145+
TargetApimName = '${{ env.TARGET_APIM }}'
146+
SourceSubscriptionId = '${{ secrets.AZURE_SUBSCRIPTION_ID }}'
147+
TargetSubscriptionId = '${{ secrets.AZURE_SUBSCRIPTION_ID }}'
148+
SkuName = '${{ steps.settings.outputs.skuName }}'
126149
Location = '${{ inputs.location }}'
127-
LogLevel = $logLevel
150+
LogLevel = '${{ steps.settings.outputs.logLevel }}'
128151
PublisherEmail = '${{ secrets.APIM_PUBLISHER_EMAIL }}'
129-
ExtractOutputDir = './extracted-artifacts'
130-
HardDelete = $true
131152
}
153+
./tests/integration/all-resource-types/phases/run-phase1-deploy.ps1 @params
132154
133-
./tests/integration/all-resource-types/run-roundtrip-test.ps1 @params
155+
- name: Azure Login - Refresh Before Phase 2
156+
if: success()
157+
uses: azure/login@v2
158+
with:
159+
# Phase 1 can run for a long time while APIM activates; refresh login
160+
# before extract/publish so apiops receives a fresh federated session.
161+
client-id: ${{ secrets.AZURE_CLIENT_ID }}
162+
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
163+
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
164+
165+
- name: Run Round-Trip Phase 2 (Extract)
166+
id: phase2
167+
if: success()
168+
shell: pwsh
169+
env:
170+
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
171+
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
172+
run: |
173+
./tests/integration/all-resource-types/phases/run-phase2-extract.ps1 `
174+
-SourceSubscriptionId '${{ steps.phase1.outputs.sourceSubscriptionId }}' `
175+
-SourceResourceGroup '${{ steps.phase1.outputs.sourceResourceGroup }}' `
176+
-SourceApimName '${{ steps.phase1.outputs.sourceApimName }}' `
177+
-LogLevel '${{ steps.settings.outputs.logLevel }}' `
178+
-ExtractOutputDir './extracted-artifacts'
179+
180+
- name: Run Round-Trip Phase 3 (Validate Extract)
181+
if: success()
182+
shell: pwsh
183+
run: |
184+
$skuName = '${{ steps.phase1.outputs.skuName }}'
185+
$extractOutputDir = '${{ steps.phase2.outputs.ExtractOutputDir }}'
186+
187+
./tests/integration/all-resource-types/phases/run-phase3-validate-extract.ps1 `
188+
-SkuName $skuName `
189+
-LogLevel '${{ steps.settings.outputs.logLevel }}' `
190+
-ExtractOutputDir $extractOutputDir
191+
192+
- name: Run Round-Trip Phase 4 (Create Overrides)
193+
id: phase4
194+
if: success()
195+
shell: pwsh
196+
run: |
197+
$extractOutputDir = '${{ steps.phase2.outputs.ExtractOutputDir }}'
198+
199+
./tests/integration/all-resource-types/phases/run-phase4-create-overrides.ps1 `
200+
-TargetSubscriptionId '${{ steps.phase1.outputs.targetSubscriptionId }}' `
201+
-TargetResourceGroup '${{ steps.phase1.outputs.targetResourceGroup }}' `
202+
-LogLevel '${{ steps.settings.outputs.logLevel }}' `
203+
-ExtractOutputDir $extractOutputDir
204+
205+
- name: Run Round-Trip Phase 5 (Publish)
206+
if: success()
207+
shell: pwsh
208+
env:
209+
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
210+
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
211+
run: |
212+
$extractOutputDir = '${{ steps.phase2.outputs.ExtractOutputDir }}'
213+
214+
./tests/integration/all-resource-types/phases/run-phase5-publish.ps1 `
215+
-TargetSubscriptionId '${{ steps.phase1.outputs.targetSubscriptionId }}' `
216+
-TargetResourceGroup '${{ steps.phase1.outputs.targetResourceGroup }}' `
217+
-TargetApimName '${{ steps.phase1.outputs.targetApimName }}' `
218+
-OverrideFile '${{ steps.phase4.outputs.overrideFile }}' `
219+
-LogLevel '${{ steps.settings.outputs.logLevel }}' `
220+
-ExtractOutputDir $extractOutputDir
221+
222+
- name: Run Round-Trip Phase 6 (Compare)
223+
if: success()
224+
shell: pwsh
225+
run: |
226+
./tests/integration/all-resource-types/phases/run-phase6-compare.ps1 `
227+
-SourceSubscriptionId '${{ steps.phase1.outputs.sourceSubscriptionId }}' `
228+
-SourceResourceGroup '${{ steps.phase1.outputs.sourceResourceGroup }}' `
229+
-SourceApimName '${{ steps.phase1.outputs.sourceApimName }}' `
230+
-TargetSubscriptionId '${{ steps.phase1.outputs.targetSubscriptionId }}' `
231+
-TargetResourceGroup '${{ steps.phase1.outputs.targetResourceGroup }}' `
232+
-TargetApimName '${{ steps.phase1.outputs.targetApimName }}' `
233+
-LogLevel '${{ steps.settings.outputs.logLevel }}'
134234
135235
- name: Upload Extracted Artifacts
136236
if: always()
137237
uses: actions/upload-artifact@v4
138238
with:
139-
name: extracted-artifacts-${{ inputs.sku }}
140-
path: ./extracted-artifacts/
239+
name: extracted-artifacts-${{ steps.phase1.outputs.skuName }}
240+
path: ${{ steps.phase2.outputs.ExtractOutputDir || './extracted-artifacts/' }}
141241
if-no-files-found: ignore
142242

143-
- name: Emergency Teardown
144-
if: failure()
243+
- name: Run Round-Trip Phase 7 (Teardown)
244+
if: always()
145245
shell: pwsh
146246
run: |
147-
$sourceRg = '${{ env.SOURCE_RG }}'
148-
$targetRg = '${{ env.TARGET_RG }}'
149-
$location = '${{ inputs.location }}'
150-
151-
# Capture APIM names before RG deletion so we can purge soft-deleted services.
152-
$sourceApimName = az apim list --resource-group $sourceRg --query "[0].name" -o tsv 2>$null
153-
$targetApimName = az apim list --resource-group $targetRg --query "[0].name" -o tsv 2>$null
154-
155-
Write-Host "🚨 Emergency teardown — deleting resource groups..."
156-
az group delete --name $sourceRg --yes --no-wait 2>$null
157-
az group delete --name $targetRg --yes --no-wait 2>$null
158-
159-
Write-Host "⏳ Waiting for resource group deletions before APIM purge..."
160-
$maxWaitSeconds = 900
161-
$waited = 0
162-
$interval = 30
163-
164-
while ($waited -lt $maxWaitSeconds) {
165-
$srcExists = (az group exists --name $sourceRg -o tsv 2>$null) -eq 'true'
166-
$tgtExists = (az group exists --name $targetRg -o tsv 2>$null) -eq 'true'
167-
if (-not $srcExists -and -not $tgtExists) {
168-
break
169-
}
170-
Start-Sleep -Seconds $interval
171-
$waited += $interval
172-
}
247+
$sourceResourceGroup = '${{ steps.phase1.outputs.sourceResourceGroup }}'
248+
if ([string]::IsNullOrWhiteSpace($sourceResourceGroup)) { $sourceResourceGroup = '${{ env.SOURCE_RG }}' }
173249
174-
foreach ($apimName in @($sourceApimName, $targetApimName)) {
175-
if (-not [string]::IsNullOrWhiteSpace($apimName)) {
176-
Write-Host "🗑️ Purging soft-deleted APIM: $apimName"
177-
az apim deletedservice purge --service-name $apimName --location $location 2>$null
178-
}
179-
}
250+
$targetResourceGroup = '${{ steps.phase1.outputs.targetResourceGroup }}'
251+
if ([string]::IsNullOrWhiteSpace($targetResourceGroup)) { $targetResourceGroup = '${{ env.TARGET_RG }}' }
252+
253+
$location = '${{ steps.phase1.outputs.location }}'
254+
if ([string]::IsNullOrWhiteSpace($location)) { $location = '${{ inputs.location }}' }
255+
256+
./tests/integration/all-resource-types/phases/run-phase7-teardown.ps1 `
257+
-SourceResourceGroup $sourceResourceGroup `
258+
-TargetResourceGroup $targetResourceGroup `
259+
-Location $location

.gitignore

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,11 @@ Desktop.ini
3939
.local-extract*/
4040

4141
# Files for integration tests
42-
tests/integration/all-resource-types/logs/**
43-
tests/integration/all-resource-types/extracted-artifacts*/**
44-
tests/integration/all-resource-types/target-apim.json
45-
tests/integration/all-resource-types/source-apim.json
46-
tests/integration/all-resource-types/source-apim-post-activation.bicep
42+
tests/integration/all-resource-types/**/logs/**
43+
tests/integration/all-resource-types/**/extracted-artifacts*/**
44+
tests/integration/all-resource-types/bicep/target-apim.json
45+
tests/integration/all-resource-types/bicep/source-apim.json
46+
tests/integration/all-resource-types/bicep/source-apim-post-activation.bicep
4747

4848
# Environment variables
4949
.env
@@ -57,4 +57,3 @@ tests/integration/all-resource-types/source-apim-post-activation.bicep
5757
# Squad: SubSquad activation file (local to this machine)
5858
.squad-workstream
5959
*.d.ts.map
60-
tests/integration/all-resource-types/source-apim-post-activation.json

tests/contract/.gitkeep

Whitespace-only changes.

tests/integration/all-resource-types/Compare-ApimInstance.ps1

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# Licensed under the MIT license.
13
<#
24
.SYNOPSIS
35
Compares two Azure API Management instances via ARM REST API.

tests/integration/all-resource-types/DeploymentHelpers.psm1

Lines changed: 0 additions & 65 deletions
This file was deleted.

0 commit comments

Comments
 (0)