Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
8dfba0e
Initial plan
Copilot Jan 29, 2026
5d230ac
Add helper functions and refactor FederatedCredentials test to reuse …
Copilot Jan 29, 2026
0cccc74
Reorganize repository settings update logic in runtest.ps1
Copilot Jan 29, 2026
817d4a8
Address code review feedback: fix temp paths, add error handling, imp…
Copilot Jan 29, 2026
3caaa49
Remove repository creation fallback - require pre-existing repo with …
Copilot Jan 29, 2026
2309402
Address PR feedback: make params mandatory, remove keepCount, rename …
Copilot Jan 29, 2026
5664fcb
Merge branch 'main' into copilot/refactor-federated-credentials-test
mazhelez Jan 29, 2026
094ce51
Rename repository from tmp-bingmaps.appsource to e2e-bingmaps.appsour…
Copilot Jan 29, 2026
1a02db0
Merge branch 'main' into copilot/refactor-federated-credentials-test
mazhelez Jan 29, 2026
626a791
Add documentation to new helper functions and remove trailing whitesp…
Copilot Jan 29, 2026
6b92fde
Remove all trailing whitespaces from e2eTestHelper.psm1
Copilot Jan 29, 2026
ab19c26
Fix 'unknown flag: --quiet' error by removing unsupported flag from g…
Copilot Jan 30, 2026
44d1b6c
Merge main branch and resolve conflicts in e2eTestHelper.psm1
Copilot Feb 3, 2026
e1e4509
Disable FederatedCredentials test in E2E workflow matrix
Copilot Feb 4, 2026
755542e
Merge branch 'main' into copilot/refactor-federated-credentials-test
mazhelez Feb 4, 2026
abe45b6
Use config file for disabled scenarios and include them in matrix as …
Copilot Feb 5, 2026
0c3ffe4
Move disabled check from step level to job level
Copilot Feb 5, 2026
b82ea64
Update disabled-scenarios.json to use array format with scenario and …
Copilot Feb 5, 2026
3dc0c90
Fix: Move matrix.disabled check from job level to all steps (matrix c…
Copilot Feb 5, 2026
1694b3a
Filter disabled scenarios from matrix entirely to prevent job execution
Copilot Feb 5, 2026
28516a8
Add logging for disabled scenarios filtering in E2E workflow
Copilot Feb 5, 2026
348e87f
Update .github/workflows/E2E.yaml
mazhelez Feb 5, 2026
204f17b
Update .github/workflows/E2E.yaml
mazhelez Feb 5, 2026
b8d28e8
Address code review: use updated_at for timing, filter by event in fa…
Copilot Feb 5, 2026
c1c8df4
Add pagination to CleanupWorkflowRuns to handle repositories with >10…
Copilot Feb 5, 2026
bf74eae
Merge branch 'main' into copilot/refactor-federated-credentials-test
mazhelez Feb 5, 2026
aecb2c2
Update e2eTests/e2eTestHelper.psm1
mazhelez Feb 6, 2026
054ed9f
Update .github/workflows/E2E.yaml
mazhelez Feb 6, 2026
51f0c57
Address code review: add specific error handling, remove duplicate do…
Copilot Feb 6, 2026
0aeecb3
Refactor workflow tracking to use polling pattern instead of timestam…
Copilot Feb 6, 2026
fc1714d
Update e2eTests/e2eTestHelper.psm1
mazhelez Feb 6, 2026
b8a2625
Update e2eTests/scenarios/FederatedCredentials/runtest.ps1
mazhelez Feb 6, 2026
13bcc5e
Remove trailing whitespace from runtest.ps1
Copilot Feb 6, 2026
4c8d486
Remove unused $updateRun variable
Copilot Feb 6, 2026
9c9aa2a
Fix workflow tracking logic: capture run IDs before cleanup and remov…
Copilot Feb 6, 2026
ffa19c2
Merge branch 'main' into copilot/refactor-federated-credentials-test
mazhelez Feb 16, 2026
b50cb74
Fix race condition: update baseline after settings push to exclude it…
Copilot Feb 16, 2026
e8e5178
Merge branch 'main' into copilot/refactor-federated-credentials-test
mazhelez Feb 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions e2eTests/e2eTestHelper.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,116 @@ function SetRepositorySecret {
gh secret set $name -b $value --repo $repository
}

function CleanupOldWorkflowRuns {
Param(
[string] $repository,
[int] $keepCount = 10
)

if (!$repository) {
$repository = $defaultRepository
}

Write-Host -ForegroundColor Yellow "`nCleaning up old workflow runs in $repository (keeping last $keepCount)"

RefreshToken -repository $repository

# Get all workflow runs, sorted by created_at descending (newest first)
$runs = invoke-gh api "/repos/$repository/actions/runs?per_page=100" -silent -returnValue | ConvertFrom-Json

if ($runs.workflow_runs.Count -le $keepCount) {
Write-Host "Found $($runs.workflow_runs.Count) workflow runs, no cleanup needed"
return
}

# Get runs to delete (skip the most recent ones)
# Runs are already sorted by created_at descending from the API
$runsToDelete = $runs.workflow_runs | Select-Object -Skip $keepCount

Write-Host "Deleting $($runsToDelete.Count) old workflow runs..."
foreach ($run in $runsToDelete) {
try {
Write-Host "Deleting run $($run.id) ($($run.name) - $($run.status))"
invoke-gh api /repos/$repository/actions/runs/$($run.id) --method DELETE -silent | Out-Null
}
catch {
Write-Host "Warning: Failed to delete run $($run.id): $_"
}
}
Write-Host "Cleanup completed"
}

function ResetRepositoryToSource {
Param(
[string] $repository,
[string] $sourceRepository,
[string] $branch = "main"
)

if (!$repository) {
$repository = $defaultRepository
}

Write-Host -ForegroundColor Yellow "`nResetting repository $repository to match $sourceRepository"

RefreshToken -repository $repository

# Clone the repository locally if not already in it
$tempPath = [System.IO.Path]::GetTempPath()
$repoPath = Join-Path $tempPath ([System.Guid]::NewGuid().ToString())
New-Item $repoPath -ItemType Directory | Out-Null

Push-Location $repoPath
try {
Write-Host "Cloning $repository..."
invoke-gh repo clone $repository . -- --quiet
if ($LASTEXITCODE -ne 0) {
throw "Failed to clone repository $repository"
}

# Fetch the source repository content
Write-Host "Fetching source repository $sourceRepository..."
invoke-git remote add source "https://github.com/$sourceRepository.git"
if ($LASTEXITCODE -ne 0) {
throw "Failed to add remote source for $sourceRepository"
}

invoke-git fetch source $branch --quiet
if ($LASTEXITCODE -ne 0) {
throw "Failed to fetch branch $branch from source $sourceRepository"
}

# Reset the current branch to match the source
Write-Host "Resetting $branch to match source/$branch..."
invoke-git checkout $branch --quiet
if ($LASTEXITCODE -ne 0) {
throw "Failed to checkout branch $branch"
}

invoke-git reset --hard "source/$branch" --quiet
if ($LASTEXITCODE -ne 0) {
throw "Failed to reset branch $branch to source/$branch"
}

# Force push to update the repository
Write-Host "Force pushing changes..."
invoke-git push origin $branch --force --quiet
if ($LASTEXITCODE -ne 0) {
throw "Failed to push changes to $repository"
}

Write-Host "Repository reset completed successfully"
}
catch {
Write-Host "Error resetting repository: $_"
throw
}
finally {
Pop-Location
Remove-Item -Path $repoPath -Force -Recurse -ErrorAction SilentlyContinue
}
}

function CreateNewAppInFolder {
Param(
[string] $folder,
Expand Down
85 changes: 70 additions & 15 deletions e2eTests/scenarios/FederatedCredentials/runtest.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,20 @@ Write-Host -ForegroundColor Yellow @'
# This test uses the bcsamples-bingmaps.appsource repository and will deliver a new build of the app to AppSource.
# The bcsamples-bingmaps.appsource repository is setup to use an Azure KeyVault for secrets and app signing.
#
# During the test, the bcsamples-bingmaps.appsource repository will be copied to a new repository called tmp-bingmaps.appsource.
# The test requires a stable temporary repository called tmp-bingmaps.appsource that must be manually created
# with federated credentials configured before running this test.
# This is required because federated credentials no longer work with repository name-based matching,
# so the repository must remain stable to maintain the federated credential configuration.
# tmp-bingmaps.appsource has access to the same Azure KeyVault as bcsamples-bingmaps.appsource using federated credentials.
# The bcSamples-bingmaps.appsource repository is setup for continuous delivery to AppSource
# tmp-bingmaps.appsource also has access to the Entra ID app registration for delivering to AppSource using federated credentials.
# This test will deliver another build of the latest app version already delivered to AppSource (without go-live)
#
# This test tests the following scenario:
#
# - Create a new repository called tmp-bingmaps.appsource (based on bcsamples-bingmaps.appsource)
# - Verify that the repository tmp-bingmaps.appsource exists (error out if not)
# - Reset the repository to match bcsamples-bingmaps.appsource for deterministic state
# - Clean up old workflow runs to ensure proper workflow tracking
# - Update AL-Go System Files in branch main in tmp-bingmaps.appsource
# - Update version numbers in app.json in tmp-bingmaps.appsource in order to not be lower than the version number in AppSource (and not be higher than the next version from bcsamples-bingmaps.appsource)
# - Wait for CI/CD in branch main in repository tmp-bingmaps.appsource
Expand All @@ -59,34 +64,84 @@ $repository = "$githubOwner/tmp-bingmaps.appsource"
$template = "https://github.com/$appSourceTemplate"
$sourceRepository = 'microsoft/bcsamples-bingmaps.appsource' # E2E test will create a copy of this repository

# Create temp repository from sourceRepository
# Setup authentication and repository
SetTokenAndRepository -github:$github -githubOwner $githubOwner -appId $e2eAppId -appKey $e2eAppKey -repository $repository

# Check if the repository already exists
# This repository must exist with federated credentials already configured
gh api repos/$repository --method HEAD
if ($LASTEXITCODE -eq 0) {
Write-Host "Repository $repository already exists. Deleting it."
gh repo delete $repository --yes | Out-Host
Start-Sleep -Seconds 30
if ($LASTEXITCODE -ne 0) {
throw "Repository $repository does not exist. The repository must be created manually with federated credentials configured before running this test."
}

CreateAlGoRepository `
-github:$github `
-template "https://github.com/$sourceRepository" `
-repository $repository `
-addRepoSettings @{"ghTokenWorkflowSecretName" = "e2eghTokenWorkflow" }
# Repository exists - reuse it and reset to source state
# This is required because federated credentials no longer work with repository name-based matching,
# so the repository must remain stable across test runs
Write-Host "Repository $repository exists. Reusing and resetting to match source."

# Reset the repository to match the source repository
ResetRepositoryToSource -repository $repository -sourceRepository $sourceRepository -branch 'main'

# Clean up old workflow runs to prevent the list from growing and ensure we wait for the correct run
CleanupOldWorkflowRuns -repository $repository -keepCount 5

# Always set/update secrets (they may have changed or repo may have been reset)
SetRepositorySecret -repository $repository -name 'Azure_Credentials' -value $azureCredentials

# Re-apply the custom repository settings that were lost during reset
$tempPath = [System.IO.Path]::GetTempPath()
$repoPath = Join-Path $tempPath ([System.Guid]::NewGuid().ToString())
New-Item $repoPath -ItemType Directory | Out-Null
Push-Location $repoPath
try {
Write-Host "Re-applying repository settings..."
invoke-gh repo clone $repository . -- --quiet
$repoSettingsFile = ".github\AL-Go-Settings.json"
if (Test-Path $repoSettingsFile) {
Add-PropertiesToJsonFile -path $repoSettingsFile -properties @{"ghTokenWorkflowSecretName" = "e2eghTokenWorkflow"}
invoke-git add $repoSettingsFile
invoke-git commit -m "Update repository settings for test" --quiet
invoke-git push --quiet
}
else {
Write-Host "Warning: .github\AL-Go-Settings.json not found after cloning. Settings may not be applied correctly."
}
}
finally {
Pop-Location
Remove-Item -Path $repoPath -Force -Recurse -ErrorAction SilentlyContinue
}

# Upgrade AL-Go System Files to test version
RunUpdateAlGoSystemFiles -directCommit -wait -templateUrl $template -repository $repository | Out-Null
# Capture the run object to ensure we wait for the correct workflow run
$updateRun = RunUpdateAlGoSystemFiles -directCommit -wait -templateUrl $template -repository $repository

# Wait for CI/CD to complete
# The Update AL-Go System Files workflow triggers a CI/CD workflow via push event
# We need to wait for the CI/CD workflow that was triggered AFTER the update workflow completed
Write-Host "Waiting for CI/CD workflow to start (triggered by Update AL-Go System Files)..."
Start-Sleep -Seconds 60

# Get workflow runs that started after the update workflow
# Use created_at for consistent timestamp comparison, and add a small buffer for timing precision
$updateCreatedAt = [DateTime]$updateRun.created_at
$runs = invoke-gh api /repos/$repository/actions/runs -silent -returnValue | ConvertFrom-Json
$run = $runs.workflow_runs | Select-Object -First 1

# Find the CI/CD workflow run that started after the update workflow was created
$run = $runs.workflow_runs | Where-Object {
$_.event -eq 'push' -and [DateTime]$_.created_at -gt $updateCreatedAt
} | Select-Object -First 1

if (-not $run) {
# Fallback to the first workflow run if we can't find one based on timestamp
Write-Host "Warning: Could not find CI/CD run based on timestamp, using first run"
$run = $runs.workflow_runs | Select-Object -First 1
}

Write-Host "Waiting for CI/CD workflow run $($run.id) to complete..."
WaitWorkflow -repository $repository -runid $run.id -noError

# The CI/CD workflow should fail because the version number of the app in thie repository is lower than the version number in AppSource
# The CI/CD workflow should fail because the version number of the app in the repository is lower than the version number in AppSource
# Reason being that major.minor from the original bcsamples-bingmaps.appsource is the same and the build number in the newly created repository is lower than the one in AppSource
# This error is expected we will grab the version number from AppSource, add one to revision number (by switching to versioningstrategy 3 in the tmp repo) and use it in the next run
$MatchArr = Test-LogContainsFromRun -repository $repository -runid $run.id -jobName 'Deliver to AppSource' -stepName 'Deliver' -expectedText '(?m)^.*The new version number \((\d+(?:\.\d+){3})\) is lower than the existing version number \((\d+(?:\.\d+){3})\) in Partner Center.*$' -isRegEx
Expand Down