diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 54faabcd6..28407fb72 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -7,27 +7,98 @@ pr: include: - master -jobs: -- job: "PR_build" - variables: - # Load the LabAuth client certificate from the local certificate store - # (installed into LocalMachine/My by template-install-dependencies.yaml) - # instead of the msidlabs KeyVault. Microsoft-hosted agents have no managed - # identity that can read the KeyVault, so DefaultAzureCredential would fail. - # The official OneBranch/Wilson pipeline does not set this and keeps using - # the KeyVault source. - UseLabAuthCertFromStore: true - pool: - vmImage: 'windows-2022' - demands: - - msbuild - - visualstudio - steps: - - template: build/template-install-dependencies.yaml - - template: build/template-build.yaml - - template: build/template-run-unit-tests.yaml - parameters: - # Exclude managed-identity E2E tests: they require an MI assigned to the - # host (IMDS), which Microsoft-hosted agents do not have. They still run in - # the official OneBranch pipeline on VM-based agents that have the MI. - e2eTestFilterCriteria: 'Category!=MI_E2E' +stages: + +# Full multi-TFM build + unit tests. The unit test project targets .NET Framework +# (net462/net472) as well as net8/net9/net10, so it needs the complete multi-TFM +# solution build. E2E and managed-identity tests run in their own stages below. +- stage: Build + displayName: 'Build & Unit Tests' + jobs: + - job: PR_build + variables: + # Load the LabAuth client certificate from the local certificate store + # (installed into LocalMachine/My by template-install-dependencies.yaml) + # instead of the msidlabs KeyVault. Microsoft-hosted agents have no managed + # identity that can read the KeyVault, so DefaultAzureCredential would fail. + # The official OneBranch/Wilson pipeline does not set this and keeps using + # the KeyVault source. + UseLabAuthCertFromStore: true + pool: + vmImage: 'windows-2022' + demands: + - msbuild + - visualstudio + steps: + - template: build/template-install-dependencies.yaml + - template: build/template-build.yaml + - template: build/template-test-unit.yaml + +# Integration tests only depend on .NET 8, so build just that project and run them +# in parallel with the full Build stage (dependsOn: [] -> no dependency on Build). +- stage: IntegrationTests + displayName: 'Integration Tests (.NET 8)' + dependsOn: [] + jobs: + - job: IntegrationTests + variables: + UseLabAuthCertFromStore: true + pool: + vmImage: 'windows-2022' + demands: + - msbuild + - visualstudio + steps: + - template: build/template-install-dependencies.yaml + - template: build/template-build-test-project.yaml + parameters: + Project: 'tests/Microsoft.Identity.Web.Test.Integration/Microsoft.Identity.Web.Test.Integration.csproj' + TargetFramework: 'net8.0' + - template: build/template-test-integration.yaml + +# Browser/host E2E tests (excluding managed identity). The E2E projects target +# net8.0 and pull their sample apps in as project references, so build just those +# projects and run in parallel with Build (dependsOn: []). +- stage: E2ETests + displayName: 'E2E Tests (.NET 8)' + dependsOn: [] + jobs: + - job: E2ETests + variables: + UseLabAuthCertFromStore: true + pool: + vmImage: 'windows-2022' + demands: + - msbuild + - visualstudio + steps: + - template: build/template-install-dependencies.yaml + - template: build/template-build-test-project.yaml + parameters: + Project: 'tests/E2E Tests/WebAppUiTests/WebAppUiTests.csproj' + TargetFramework: 'net8.0' + - template: build/template-build-test-project.yaml + parameters: + Project: 'tests/E2E Tests/TokenAcquirerTests/TokenAcquirerTests.csproj' + TargetFramework: 'net8.0' + - template: build/template-test-e2e.yaml + parameters: + # Exclude managed-identity E2E tests: they require an MI assigned to the + # host (IMDS), which Microsoft-hosted agents do not have. They run in the + # ManagedIdentityE2E stage below on the MSALMSIV2 VM pool. + e2eTestFilterCriteria: 'Category!=MI_E2E' + +# Managed-identity E2E tests require a VM with an assigned managed identity (IMDS), +# which Microsoft-hosted agents do not have. They run on the MSALMSIV2 VM pool and +# build only the E2E project (.NET 8), independent of the Build stage. +- stage: ManagedIdentityE2E + displayName: 'Managed Identity E2E (MSALMSIV2)' + dependsOn: [] + jobs: + - job: ManagedIdentityE2E + pool: + name: 'MSALMSIV2' + steps: + - template: build/template-run-managed-identity-e2e-tests.yaml + parameters: + TargetFramework: 'net8.0' diff --git a/build/template-build-test-project.yaml b/build/template-build-test-project.yaml new file mode 100644 index 000000000..477388ec4 --- /dev/null +++ b/build/template-build-test-project.yaml @@ -0,0 +1,29 @@ +# template-build-test-project.yaml +# Restore and build a single test project (and its project references) for one +# target framework. Used by the independent stages that only need .NET 8, so they +# don't wait on the full multi-TFM solution build. + +parameters: +- name: Project + type: string +- name: BuildConfiguration + type: string + default: 'Release' +- name: TargetFramework + type: string + default: 'net8.0' + +steps: + +- task: DotNetCoreCLI@2 + displayName: 'Restore ${{ parameters.Project }}' + inputs: + command: 'restore' + projects: '${{ parameters.Project }}' + +- task: DotNetCoreCLI@2 + displayName: 'Build ${{ parameters.Project }} (${{ parameters.TargetFramework }})' + inputs: + command: 'build' + projects: '${{ parameters.Project }}' + arguments: '--configuration ${{ parameters.BuildConfiguration }} --framework ${{ parameters.TargetFramework }} --no-restore' diff --git a/build/template-run-managed-identity-e2e-tests.yaml b/build/template-run-managed-identity-e2e-tests.yaml new file mode 100644 index 000000000..e2f20c947 --- /dev/null +++ b/build/template-run-managed-identity-e2e-tests.yaml @@ -0,0 +1,48 @@ +# template-run-managed-identity-e2e-tests.yaml +# Build the TokenAcquirer E2E project (.NET 8 only) and run the managed-identity +# E2E tests (Category=MI_E2E). These require a VM with an assigned managed +# identity (IMDS), so they run on the MSALMSIV2 pool, not Microsoft-hosted agents. +# Mirrors the MSAL.NET template-run-mi-e2e-imds.yaml approach. + +parameters: +- name: BuildConfiguration + type: string + default: 'Release' +- name: TargetFramework + type: string + default: 'net8.0' + +steps: + +# The repo global.json pins the .NET SDK (10.0.x). Install it so 'dotnet restore' +# succeeds, plus the .NET 8 runtime the tests execute under. +- task: UseDotNet@2 + displayName: 'Use .NET SDK 8.0.x' + inputs: + packageType: 'sdk' + version: '8.0.x' + +- task: UseDotNet@2 + displayName: 'Use .NET SDK from global.json' + inputs: + useGlobalJson: true + +- template: template-build-test-project.yaml + parameters: + Project: 'tests/E2E Tests/TokenAcquirerTests/TokenAcquirerTests.csproj' + BuildConfiguration: '${{ parameters.BuildConfiguration }}' + TargetFramework: '${{ parameters.TargetFramework }}' + +- task: VSTest@2 + displayName: 'Run managed identity E2E tests' + inputs: + testSelector: 'testAssemblies' + testAssemblyVer2: 'tests\E2E Tests\TokenAcquirerTests\bin\${{ parameters.BuildConfiguration }}\${{ parameters.TargetFramework }}\TokenAcquirerTests.dll' + searchFolder: '$(System.DefaultWorkingDirectory)' + testFiltercriteria: 'Category=MI_E2E' + runTestsInIsolation: true + rerunFailedTests: true + rerunMaxAttempts: '3' + runInParallel: false + failOnMinTestsNotRun: true + minimumExpectedTests: '1' diff --git a/build/template-run-unit-tests.yaml b/build/template-run-unit-tests.yaml index cc7895d7d..c92698c4d 100644 --- a/build/template-run-unit-tests.yaml +++ b/build/template-run-unit-tests.yaml @@ -1,5 +1,9 @@ # template-run-unit-tests.yaml -# Run all unit tests across the IdWeb project +# Aggregator that runs every test suite (unit + integration + E2E) in one job. +# Kept for consumers that want the full suite in a single stage (e.g. the release +# build). The PR pipeline composes the granular templates into separate stages +# instead: template-test-unit.yaml, template-test-integration.yaml and +# template-test-e2e.yaml. parameters: # VSTest filter applied to the E2E test run. Empty by default so every consumer @@ -12,70 +16,10 @@ parameters: steps: -- task: VSTest@2 - displayName: 'Run unit tests' - inputs: - testSelector: 'testAssemblies' - testAssemblyVer2: 'tests\Microsoft.Identity.Web.Test\bin\**\Microsoft.Identity.Web.Test.dll' - searchFolder: '$(System.DefaultWorkingDirectory)' - runInParallel: true - codeCoverageEnabled: true - failOnMinTestsNotRun: true - minimumExpectedTests: '1' - runSettingsFile: 'build\CodeCoverage.runsettings' +- template: template-test-unit.yaml -- task: VSTest@2 - displayName: 'Run integration tests' - inputs: - testSelector: 'testAssemblies' - testAssemblyVer2: 'tests\Microsoft.Identity.Web.Test.Integration\bin\**\Microsoft.Identity.Web.Test.Integration.dll' - searchFolder: '$(System.DefaultWorkingDirectory)' - rerunFailedTests: true - rerunMaxAttempts: '3' - runInParallel: false - codeCoverageEnabled: true - failOnMinTestsNotRun: false - minimumExpectedTests: '1' - runSettingsFile: 'build\CodeCoverage.runsettings' +- template: template-test-integration.yaml -- task: VSTest@2 - displayName: 'Run E2E tests' - inputs: - testSelector: 'testAssemblies' - testAssemblyVer2: | - tests\E2E Tests\WebAppUiTests\bin\**\WebAppUiTests.dll - tests\E2E Tests\TokenAcquirerTests\bin\**\TokenAcquirerTests.dll - tests\E2E Tests\NET 7 tests\IntegrationTests\bin\**\IntegrationTests.dll - searchFolder: '$(System.DefaultWorkingDirectory)' - testFiltercriteria: '${{ parameters.e2eTestFilterCriteria }}' - rerunFailedTests: true - rerunMaxAttempts: '3' - runInParallel: false - codeCoverageEnabled: true - failOnMinTestsNotRun: false - minimumExpectedTests: '1' - runSettingsFile: 'build\CodeCoverage.runsettings' - -- task: PublishBuildArtifacts@1 - displayName: 'Publish traces after test' - inputs: - PathtoPublish: '$(Build.SourcesDirectory)/tests/E2E Tests/PlaywrightTraces/' - ArtifactName: 'traces-after-tests-$(Build.BuildNumber)' - condition: failed() - -# Copy all packages out to staging -- task: CopyFiles@2 - displayName: 'Copy screenshots to staging directory' - inputs: - SourceFolder: '$(Build.SourcesDirectory)/tests/E2E Tests/' - Contents: '**/*screenshotFail.png' - TargetFolder: '$(Build.ArtifactStagingDirectory)\screenshots' - flattenFolders: true - condition: failed() - -- task: PublishBuildArtifacts@1 - displayName: 'Publish Screenshot after test' - inputs: - PathtoPublish: '$(Build.ArtifactStagingDirectory)\screenshots' - ArtifactName: 'ScreenshotFail' - condition: failed() \ No newline at end of file +- template: template-test-e2e.yaml + parameters: + e2eTestFilterCriteria: '${{ parameters.e2eTestFilterCriteria }}' diff --git a/build/template-test-e2e.yaml b/build/template-test-e2e.yaml new file mode 100644 index 000000000..5ebb082c5 --- /dev/null +++ b/build/template-test-e2e.yaml @@ -0,0 +1,55 @@ +# template-test-e2e.yaml +# Run the browser/host E2E tests (Playwright + multi-target). These require the +# full multi-TFM build output, so they run in the build stage. + +parameters: +# VSTest filter applied to the E2E run. Empty by default so the release build runs +# the full suite. The PR build passes 'Category!=MI_E2E' to exclude managed-identity +# tests, which run on the MSALMSIV2 pool in their own stage. +- name: e2eTestFilterCriteria + type: string + default: '' + +steps: + +- task: VSTest@2 + displayName: 'Run E2E tests' + inputs: + testSelector: 'testAssemblies' + testAssemblyVer2: | + tests\E2E Tests\WebAppUiTests\bin\**\WebAppUiTests.dll + tests\E2E Tests\TokenAcquirerTests\bin\**\TokenAcquirerTests.dll + tests\E2E Tests\NET 7 tests\IntegrationTests\bin\**\IntegrationTests.dll + searchFolder: '$(System.DefaultWorkingDirectory)' + testFiltercriteria: '${{ parameters.e2eTestFilterCriteria }}' + rerunFailedTests: true + rerunMaxAttempts: '3' + runInParallel: false + codeCoverageEnabled: true + failOnMinTestsNotRun: false + minimumExpectedTests: '1' + runSettingsFile: 'build\CodeCoverage.runsettings' + +- task: PublishBuildArtifacts@1 + displayName: 'Publish traces after test' + inputs: + PathtoPublish: '$(Build.SourcesDirectory)/tests/E2E Tests/PlaywrightTraces/' + ArtifactName: 'traces-after-tests-$(Build.BuildNumber)' + condition: failed() + +# Copy all packages out to staging +- task: CopyFiles@2 + displayName: 'Copy screenshots to staging directory' + inputs: + SourceFolder: '$(Build.SourcesDirectory)/tests/E2E Tests/' + Contents: '**/*screenshotFail.png' + TargetFolder: '$(Build.ArtifactStagingDirectory)\screenshots' + flattenFolders: true + condition: failed() + +- task: PublishBuildArtifacts@1 + displayName: 'Publish Screenshot after test' + inputs: + PathtoPublish: '$(Build.ArtifactStagingDirectory)\screenshots' + ArtifactName: 'ScreenshotFail' + condition: failed() diff --git a/build/template-test-integration.yaml b/build/template-test-integration.yaml new file mode 100644 index 000000000..3691c5e8b --- /dev/null +++ b/build/template-test-integration.yaml @@ -0,0 +1,19 @@ +# template-test-integration.yaml +# Run the Microsoft.Identity.Web integration tests. Only depend on .NET 8, so a +# consuming stage can build just this project and run in parallel with the build. + +steps: + +- task: VSTest@2 + displayName: 'Run integration tests' + inputs: + testSelector: 'testAssemblies' + testAssemblyVer2: 'tests\Microsoft.Identity.Web.Test.Integration\bin\**\Microsoft.Identity.Web.Test.Integration.dll' + searchFolder: '$(System.DefaultWorkingDirectory)' + rerunFailedTests: true + rerunMaxAttempts: '3' + runInParallel: false + codeCoverageEnabled: true + failOnMinTestsNotRun: false + minimumExpectedTests: '1' + runSettingsFile: 'build\CodeCoverage.runsettings' diff --git a/build/template-test-unit.yaml b/build/template-test-unit.yaml new file mode 100644 index 000000000..2e2cbd407 --- /dev/null +++ b/build/template-test-unit.yaml @@ -0,0 +1,16 @@ +# template-test-unit.yaml +# Run the Microsoft.Identity.Web unit tests. + +steps: + +- task: VSTest@2 + displayName: 'Run unit tests' + inputs: + testSelector: 'testAssemblies' + testAssemblyVer2: 'tests\Microsoft.Identity.Web.Test\bin\**\Microsoft.Identity.Web.Test.dll' + searchFolder: '$(System.DefaultWorkingDirectory)' + runInParallel: true + codeCoverageEnabled: true + failOnMinTestsNotRun: true + minimumExpectedTests: '1' + runSettingsFile: 'build\CodeCoverage.runsettings' diff --git a/tests/E2E Tests/TokenAcquirerTests/TokenAcquirer.cs b/tests/E2E Tests/TokenAcquirerTests/TokenAcquirer.cs index 3dadbc17b..7baec1ff1 100644 --- a/tests/E2E Tests/TokenAcquirerTests/TokenAcquirer.cs +++ b/tests/E2E Tests/TokenAcquirerTests/TokenAcquirer.cs @@ -561,9 +561,10 @@ public async Task AcquireTokenWithManagedIdentity_UserAssignedAsync() // Arrange const string scope = "https://vault.azure.net/.default"; const string baseUrl = "https://vault.azure.net"; - // User-assigned managed identity "Msal_Integration_tests" (RG MSAL_MSI, sub c1686c51-b717-4fe0-9af3-24a20a41fb0c). - // Shared/consolidated lab UAMI reused across MSAL and Id.Web E2E tests. - const string clientId = "45344e7d-c562-4be6-868f-18dac789c021"; + // User-assigned managed identity "msiv2uami" (RG MSIV2-Testing-MSALNET, + // sub c1686c51-b717-4fe0-9af3-24a20a41fb0c) assigned to the MSALMSIV2 + // VM pool that runs these MI E2E tests. Shared with MSAL.NET E2E tests. + const string clientId = "6325cd32-9911-41f3-819c-416cdf9104e7"; TokenAcquirerFactoryTesting.ResetTokenAcquirerFactoryInTest(); TokenAcquirerFactory tokenAcquirerFactory = TokenAcquirerFactory.GetDefaultInstance(); IServiceProvider serviceProvider = tokenAcquirerFactory.Build();