diff --git a/.azure-pipelines/ci-build.yml b/.azure-pipelines/ci-build.yml index fb7413240..5e8a40e98 100644 --- a/.azure-pipelines/ci-build.yml +++ b/.azure-pipelines/ci-build.yml @@ -21,6 +21,8 @@ pr: variables: buildPlatform: 'Any CPU' buildConfiguration: 'Release' + NuGetOrganizationName: 'openapinet' + privateFeedBaseUrl: 'https://microsoftgraph.pkgs.visualstudio.com/0985d294-5762-4bc2-a565-161ef349ca3e/_packaging/GraphDeveloperExperiences_Public' ProductBinPath: '$(Build.SourcesDirectory)\src\Microsoft.OpenApi\bin\$(BuildConfiguration)' REGISTRY: 'msgraphprodregistry.azurecr.io' IMAGE_NAME: 'public/openapi/hidi' @@ -69,7 +71,7 @@ extends: - task: UseDotNet@2 displayName: 'Use .NET 10' inputs: - version: 10.x + useGlobalJson: true # Install the nuget tool. - task: NuGetToolInstaller@1 @@ -88,7 +90,7 @@ extends: - + "@ | Set-Content -Path "$(Build.SourcesDirectory)/nuget.config" -Encoding UTF8 @@ -100,14 +102,18 @@ extends: arguments: '--configuration $(BuildConfiguration) --no-incremental' # Run the Unit test - - task: DotNetCoreCLI@2 + - pwsh: | + dotnet test "$(Build.SourcesDirectory)\Microsoft.OpenApi.slnx" ` + --configuration $(BuildConfiguration) ` + --no-build ` + -- ` + --report-azdo ` + --publish-azdo-test-results ` + --results-directory="$(Agent.TempDirectory)\TestResults" ` + --minimum-expected-tests 1 displayName: 'test' - inputs: - command: test - projects: '$(Build.SourcesDirectory)\Microsoft.OpenApi.slnx' - arguments: '--configuration $(BuildConfiguration) --no-build' - - task: EsrpCodeSigning@5 + - task: EsrpCodeSigning@6 displayName: 'ESRP CodeSigning binaries' inputs: ConnectedServiceName: 'Federated DevX ESRP Managed Identity Connection' @@ -173,7 +179,7 @@ extends: - pwsh: dotnet pack $(Build.SourcesDirectory)/src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj -o $(Build.ArtifactStagingDirectory) --configuration $(BuildConfiguration) --no-build --include-symbols --include-source /p:SymbolPackageFormat=snupkg displayName: 'pack Hidi' - - task: EsrpCodeSigning@5 + - task: EsrpCodeSigning@6 displayName: 'ESRP CodeSigning Nuget Packages' inputs: ConnectedServiceName: 'Federated DevX ESRP Managed Identity Connection' @@ -212,7 +218,7 @@ extends: displayName: publish Hidi as executable inputs: command: 'publish' - arguments: -c Release --runtime win-x64 /p:PublishSingleFile=true /p:PackAsTool=false --self-contained --output $(Build.ArtifactStagingDirectory)/Microsoft.OpenApi.Hidi + arguments: -c Release --runtime win-x64 -p:RestoreConfigFile=$(Build.SourcesDirectory)\nuget.config /p:PublishSingleFile=true /p:PackAsTool=false --self-contained --output $(Build.ArtifactStagingDirectory)/Microsoft.OpenApi.Hidi projects: 'src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj' publishWebProjects: False zipAfterPublish: false @@ -222,7 +228,16 @@ extends: inputs: targetFolder: $(Build.ArtifactStagingDirectory)/Nugets sourceFolder: $(Build.ArtifactStagingDirectory) - content: '*.nupkg' + Contents: | + *.nupkg + *.snupkg + + - task: CopyFiles@2 + displayName: 'Include version-check script in Nugets artifact' + inputs: + SourceFolder: '$(Build.SourcesDirectory)/scripts' + Contents: 'check-nuget-package-published.ps1' + TargetFolder: '$(Build.ArtifactStagingDirectory)/Nugets/scripts' # Copy repository files to be used in the deploy stage - task: CopyFiles@2 @@ -261,13 +276,46 @@ extends: pool: vmImage: ubuntu-latest steps: - - task: 1ES.PublishNuget@1 - displayName: 'NuGet push' + - task: PowerShell@2 + displayName: 'Check whether NuGet package version already published (idempotent)' + inputs: + targetType: filePath + filePath: '$(Pipeline.Workspace)/scripts/check-nuget-package-published.ps1' + arguments: '-PackageId "Microsoft.OpenApi.Hidi" -PackageDirectory "$(Pipeline.Workspace)" -NuGetServiceIndexUrl "$(privateFeedBaseUrl)/nuget/v3/index.json"' + pwsh: true + env: + FEED_ACCESS_TOKEN: $(System.AccessToken) + - task: CopyFiles@2 + displayName: 'Stage Hidi NuGet packages for ESRP release' + condition: and(succeeded(), ne(variables['nugetAlreadyPublished'], 'true')) + inputs: + SourceFolder: '$(Pipeline.Workspace)' + Contents: | + Microsoft.OpenApi.Hidi.*.nupkg + Microsoft.OpenApi.Hidi.*.snupkg + TargetFolder: '$(Pipeline.Workspace)/nuget-packages/$(NuGetOrganizationName)/hidi' + CleanTargetFolder: true + - task: EsrpRelease@14 + displayName: 'ESRP Release - Hidi NuGet' + condition: and(succeeded(), ne(variables['nugetAlreadyPublished'], 'true')) inputs: - packagesToPush: '$(Pipeline.Workspace)/Microsoft.OpenApi.Hidi.*.nupkg' - packageParentPath: '$(Pipeline.Workspace)' - nuGetFeedType: external - publishFeedCredentials: 'OpenAPI Nuget Connection' + connectedservicename: 'Federated DevX ESRP Managed Identity Connection' + usemanagedidentity: false + keyvaultname: 'akv-prod-eastus' + authcertname: 'ReferenceLibraryPrivateCert' + signcertname: 'ReferencePackagePublisherCertificate' + clientid: '65035b7f-7357-4f29-bf25-c5ee5c3949f8' + intent: 'packagedistribution' + contenttype: 'NuGet' + organizationname: '$(NuGetOrganizationName)' + contentsource: 'Folder' + folderlocation: '$(Pipeline.Workspace)/nuget-packages/$(NuGetOrganizationName)/hidi' + waitforreleasecompletion: true + owners: 'christiano@microsoft.com,ramsess@microsoft.com,gavinbarron@microsoft.com,jingjingjia@microsoft.com,peombwa@microsoft.com,treicys@microsoft.com' + approvers: 'christiano@microsoft.com,ramsess@microsoft.com,gavinbarron@microsoft.com,jingjingjia@microsoft.com,peombwa@microsoft.com,treicys@microsoft.com' + serviceendpointurl: 'https://api.esrp.microsoft.com/' + mainpublisher: 'ESRPRELPACMAN' + domaintenantid: 'cdc5aeea-15c5-4db6-b079-fcadd2505dc2' - deployment: deploy_lib condition: and(contains(variables['build.SourceBranch'], 'refs/tags/v'), succeeded()) @@ -286,21 +334,48 @@ extends: pool: vmImage: ubuntu-latest steps: - - pwsh: | - $fileNames = "$(Pipeline.Workspace)/Microsoft.OpenApi.Hidi.*.nupkg", "$(Pipeline.Workspace)/Microsoft.OpenApi.YamlReader.*.nupkg", "$(Pipeline.Workspace)/Microsoft.OpenApi.Workbench.*.nupkg" - foreach($fileName in $fileNames) { - if(Test-Path $fileName) { - Remove-Item $fileName -Verbose - } - } - displayName: remove other nupkgs to avoid duplication - - task: 1ES.PublishNuget@1 - displayName: 'NuGet push' + - task: PowerShell@2 + displayName: 'Check whether NuGet package version already published (idempotent)' inputs: - packagesToPush: '$(Pipeline.Workspace)/Microsoft.OpenApi.*.nupkg' - packageParentPath: '$(Pipeline.Workspace)' - nuGetFeedType: external - publishFeedCredentials: 'OpenAPI Nuget Connection' + targetType: filePath + filePath: '$(Pipeline.Workspace)/scripts/check-nuget-package-published.ps1' + arguments: '-PackageId "Microsoft.OpenApi" -PackageDirectory "$(Pipeline.Workspace)" -NuGetServiceIndexUrl "$(privateFeedBaseUrl)/nuget/v3/index.json"' + pwsh: true + env: + FEED_ACCESS_TOKEN: $(System.AccessToken) + - task: CopyFiles@2 + displayName: 'Stage OpenAPI NuGet packages for ESRP release' + condition: and(succeeded(), ne(variables['nugetAlreadyPublished'], 'true')) + inputs: + SourceFolder: '$(Pipeline.Workspace)' + Contents: | + Microsoft.OpenApi.*.nupkg + Microsoft.OpenApi.*.snupkg + !Microsoft.OpenApi.Hidi.* + !Microsoft.OpenApi.YamlReader.* + TargetFolder: '$(Pipeline.Workspace)/nuget-packages/$(NuGetOrganizationName)/openapi' + CleanTargetFolder: true + - task: EsrpRelease@14 + displayName: 'ESRP Release - OpenAPI NuGet' + condition: and(succeeded(), ne(variables['nugetAlreadyPublished'], 'true')) + inputs: + connectedservicename: 'Federated DevX ESRP Managed Identity Connection' + usemanagedidentity: false + keyvaultname: 'akv-prod-eastus' + authcertname: 'ReferenceLibraryPrivateCert' + signcertname: 'ReferencePackagePublisherCertificate' + clientid: '65035b7f-7357-4f29-bf25-c5ee5c3949f8' + intent: 'packagedistribution' + contenttype: 'NuGet' + organizationname: '$(NuGetOrganizationName)' + contentsource: 'Folder' + folderlocation: '$(Pipeline.Workspace)/nuget-packages/$(NuGetOrganizationName)/openapi' + waitforreleasecompletion: true + owners: 'christiano@microsoft.com,ramsess@microsoft.com,gavinbarron@microsoft.com,jingjingjia@microsoft.com,peombwa@microsoft.com,treicys@microsoft.com' + approvers: 'christiano@microsoft.com,ramsess@microsoft.com,gavinbarron@microsoft.com,jingjingjia@microsoft.com,peombwa@microsoft.com,treicys@microsoft.com' + serviceendpointurl: 'https://api.esrp.microsoft.com/' + mainpublisher: 'ESRPRELPACMAN' + domaintenantid: 'cdc5aeea-15c5-4db6-b079-fcadd2505dc2' - deployment: deploy_yaml_reader condition: and(contains(variables['build.SourceBranch'], 'refs/tags/v'), succeeded()) @@ -319,13 +394,46 @@ extends: pool: vmImage: ubuntu-latest steps: - - task: 1ES.PublishNuget@1 - displayName: 'NuGet push' + - task: PowerShell@2 + displayName: 'Check whether NuGet package version already published (idempotent)' + inputs: + targetType: filePath + filePath: '$(Pipeline.Workspace)/scripts/check-nuget-package-published.ps1' + arguments: '-PackageId "Microsoft.OpenApi.YamlReader" -PackageDirectory "$(Pipeline.Workspace)" -NuGetServiceIndexUrl "$(privateFeedBaseUrl)/nuget/v3/index.json"' + pwsh: true + env: + FEED_ACCESS_TOKEN: $(System.AccessToken) + - task: CopyFiles@2 + displayName: 'Stage YAML reader NuGet packages for ESRP release' + condition: and(succeeded(), ne(variables['nugetAlreadyPublished'], 'true')) inputs: - packagesToPush: '$(Pipeline.Workspace)/Microsoft.OpenApi.YamlReader.*.nupkg' - packageParentPath: '$(Pipeline.Workspace)' - nuGetFeedType: external - publishFeedCredentials: 'OpenAPI Nuget Connection' + SourceFolder: '$(Pipeline.Workspace)' + Contents: | + Microsoft.OpenApi.YamlReader.*.nupkg + Microsoft.OpenApi.YamlReader.*.snupkg + TargetFolder: '$(Pipeline.Workspace)/nuget-packages/$(NuGetOrganizationName)/yaml-reader' + CleanTargetFolder: true + - task: EsrpRelease@14 + displayName: 'ESRP Release - YAML reader NuGet' + condition: and(succeeded(), ne(variables['nugetAlreadyPublished'], 'true')) + inputs: + connectedservicename: 'Federated DevX ESRP Managed Identity Connection' + usemanagedidentity: false + keyvaultname: 'akv-prod-eastus' + authcertname: 'ReferenceLibraryPrivateCert' + signcertname: 'ReferencePackagePublisherCertificate' + clientid: '65035b7f-7357-4f29-bf25-c5ee5c3949f8' + intent: 'packagedistribution' + contenttype: 'NuGet' + organizationname: '$(NuGetOrganizationName)' + contentsource: 'Folder' + folderlocation: '$(Pipeline.Workspace)/nuget-packages/$(NuGetOrganizationName)/yaml-reader' + waitforreleasecompletion: true + owners: 'christiano@microsoft.com,ramsess@microsoft.com,gavinbarron@microsoft.com,jingjingjia@microsoft.com,peombwa@microsoft.com,treicys@microsoft.com' + approvers: 'christiano@microsoft.com,ramsess@microsoft.com,gavinbarron@microsoft.com,jingjingjia@microsoft.com,peombwa@microsoft.com,treicys@microsoft.com' + serviceendpointurl: 'https://api.esrp.microsoft.com/' + mainpublisher: 'ESRPRELPACMAN' + domaintenantid: 'cdc5aeea-15c5-4db6-b079-fcadd2505dc2' - deployment: create_github_release condition: and(contains(variables['build.SourceBranch'], 'refs/tags/v'), succeeded()) @@ -455,7 +563,33 @@ extends: displayName: 'Get current date' name: setdate condition: eq(variables['Build.SourceBranch'], variables['PREVIEW_BRANCH']) - + + # Keep feed credentials out of the Docker build context and image layers. + - pwsh: | + if ([string]::IsNullOrWhiteSpace($env:FEED_ACCESS_TOKEN)) { + throw "No Azure Artifacts access token available for the Docker build." + } + $feedAccessToken = [System.Security.SecurityElement]::Escape($env:FEED_ACCESS_TOKEN) + @" + + + + + + + + + + + + + + + "@ | Set-Content -Path "$(Agent.TempDirectory)/hidi-docker.nuget.config" -Encoding UTF8 + displayName: 'Create Docker NuGet config (central feed)' + env: + FEED_ACCESS_TOKEN: $(System.AccessToken) + - script: | docker run --privileged --rm msgraphprodregistry.azurecr.io/tonistiigi/binfmt --install all displayName: "Enable multi-platform builds" @@ -474,6 +608,7 @@ extends: # Using quotes around tags to prevent flag interpretation docker buildx build \ --platform linux/amd64,linux/arm64/v8 \ + --secret id=nuget_config,src="$(Agent.TempDirectory)/hidi-docker.nuget.config" \ --push \ -t "$(REGISTRY)/$(IMAGE_NAME):nightly" \ -t "$(REGISTRY)/$(IMAGE_NAME):${VERSION}.${BUILDDATE}${RUNNUMBER}" \ @@ -486,6 +621,7 @@ extends: echo "Building Docker image for release..." docker buildx build\ --platform linux/amd64,linux/arm64/v8 \ + --secret id=nuget_config,src="$(Agent.TempDirectory)/hidi-docker.nuget.config" \ --push \ -t "$(REGISTRY)/$(IMAGE_NAME):latest" \ -t "$(REGISTRY)/$(IMAGE_NAME):${VERSION}" \ @@ -493,6 +629,14 @@ extends: displayName: 'Build and Push Release Image' condition: contains(variables['Build.SourceBranch'], 'refs/tags/v') + - pwsh: | + $configPath = "$(Agent.TempDirectory)/hidi-docker.nuget.config" + if (Test-Path $configPath) { + Remove-Item $configPath -Force + } + displayName: 'Remove Docker NuGet config' + condition: always() + # once the nuget has been released, fill this form to get the public documentation updated. # https://dev.azure.com/msft-skilling/Content/_workitems/create/User%20Story?templateId=39fb91e3-64a2-4c8a-83db-b2bdf3603dd3&ownerId=c4a28f90-17ae-4384-b514-7273392b082b # https://learn.microsoft.com/en-us/dotnet/api/microsoft.openapi diff --git a/.github/dependabot.yml b/.github/dependabot.yml index db2d8ebfb..22ae8e30f 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,6 +5,10 @@ updates: open-pull-requests-limit: 10 schedule: interval: daily + groups: + codeql: + patterns: + - github/codeql-action* cooldown: default-days: 7 - package-ecosystem: nuget @@ -16,9 +20,12 @@ updates: MicrosoftExtensions: patterns: - Microsoft.Extensions.* - coverlet: + testing: patterns: - coverlet.* + - Microsoft.NET.Test.Sdk + - Microsoft.Testing.* + - xunit.* cooldown: default-days: 7 - package-ecosystem: dotnet-sdk diff --git a/.github/workflows/auto-merge-dependabot.yml b/.github/workflows/auto-merge-dependabot.yml index 9d9039433..f6dafa170 100644 --- a/.github/workflows/auto-merge-dependabot.yml +++ b/.github/workflows/auto-merge-dependabot.yml @@ -19,14 +19,14 @@ jobs: steps: - name: Dependabot metadata id: metadata - uses: dependabot/fetch-metadata@v3.1.0 + uses: dependabot/fetch-metadata@25dd0e34f4fe68f24cc83900b1fe3fe149efef98 # v3.1.0 with: github-token: "${{ secrets.GITHUB_TOKEN }}" - name: Enable auto-merge for Dependabot PRs # Only if version bump is not a major version change if: ${{steps.metadata.outputs.update-type != 'version-update:semver-major'}} - run: gh pr merge --auto --merge "$PR_URL" + run: gh pr merge --auto --squash "$PR_URL" env: PR_URL: ${{github.event.pull_request.html_url}} GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index d274b00c2..4ae9e6ae0 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -18,18 +18,18 @@ jobs: GITHUB_RUN_NUMBER: ${{ github.run_number }} steps: - name: Setup .NET 8 - uses: actions/setup-dotnet@v6 + uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0 with: dotnet-version: 8.x - name: Setup .NET 10 - uses: actions/setup-dotnet@v6 + uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0 with: dotnet-version: 10.x - name: Checkout repository id: checkout_repo - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: ref: ${{ github.event.pull_request.head.sha || github.sha }} token: ${{ secrets.GITHUB_TOKEN }} @@ -45,7 +45,7 @@ jobs: id: run_unit_tests shell: pwsh run: | - dotnet test Microsoft.OpenApi.slnx -c Release --no-build -v n --collect:"XPlat Code Coverage" + dotnet test --solution Microsoft.OpenApi.slnx -c Release -v n --results-directory=./TestResults --coverlet --coverlet-output-format cobertura --report-gh - name: Install report generator shell: pwsh @@ -55,7 +55,7 @@ jobs: - name: Generate coverage report shell: pwsh run: | - reportgenerator -reports:**/coverage.cobertura.xml -targetdir:./reports/coverage -reporttypes:"Html;MarkdownSummaryGithub;Cobertura" + reportgenerator -reports:./TestResults/**/coverage.cobertura.*.xml -targetdir:./reports/coverage -reporttypes:"Html;MarkdownSummaryGithub;Cobertura" - name: Add coverage to job summary shell: bash @@ -64,14 +64,14 @@ jobs: - name: Upload coverage report if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && github.actor != 'dependabot[bot]') || (github.event_name != 'pull_request' && github.ref_name == github.event.repository.default_branch) - uses: actions/upload-code-coverage@v1.4.1 + uses: actions/upload-code-coverage@2b21a77928be8d5168c2b9581a67f2adbebacc52 # v1.4.4 with: file: ./reports/coverage/Cobertura.xml language: CSharp label: code-coverage/dotnet - name: Upload coverage artifact - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: coverage path: reports/coverage @@ -80,15 +80,15 @@ jobs: name: Validate Project for Trimming runs-on: windows-latest steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Setup .NET 8 - uses: actions/setup-dotnet@v6 + uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0 with: dotnet-version: 8.x - name: Setup .NET 10 - uses: actions/setup-dotnet@v6 + uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0 with: dotnet-version: 10.x @@ -102,15 +102,15 @@ jobs: needs: [ci] steps: - name: Checkout repository - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Setup .NET 8 - uses: actions/setup-dotnet@v6 + uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0 with: dotnet-version: 8.x - name: Setup .NET 10 - uses: actions/setup-dotnet@v6 + uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0 with: dotnet-version: 10.x @@ -125,7 +125,7 @@ jobs: working-directory: ./performance/benchmark - name: Publish benchmark results - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: if-no-files-found: error name: benchmark-results diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index aba7add18..b2311adf8 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -20,21 +20,21 @@ jobs: steps: - name: Checkout repository id: checkout_repo - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Setup .NET 8 - uses: actions/setup-dotnet@v6 + uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0 with: dotnet-version: 8.0.x - name: Setup .NET 10 - uses: actions/setup-dotnet@v6 + uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0 with: dotnet-version: 10.0.x - name: Initialize CodeQL id: init_codeql - uses: github/codeql-action/init@v4.37.5 + uses: github/codeql-action/init@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2 with: queries: security-and-quality @@ -54,6 +54,6 @@ jobs: - name: Perform CodeQL Analysis id: analyze_codeql - uses: github/codeql-action/analyze@v4.37.5 + uses: github/codeql-action/analyze@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2 # Built with ❤ by [Pipeline Foundation](https://pipeline.foundation) \ No newline at end of file diff --git a/.github/workflows/promote-shipped-apis.yml b/.github/workflows/promote-shipped-apis.yml index b3c7e8b6d..825b8944f 100644 --- a/.github/workflows/promote-shipped-apis.yml +++ b/.github/workflows/promote-shipped-apis.yml @@ -7,6 +7,10 @@ on: - support/v2 workflow_dispatch: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + jobs: promote-apis: runs-on: ubuntu-latest @@ -16,13 +20,22 @@ jobs: steps: - name: Generate GitHub App token id: app-token - uses: actions/create-github-app-token@v3 + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 with: client-id: ${{ vars.RELEASE_PLEASE_TOKEN_PROVIDER_APP_ID }} private-key: ${{ secrets.RELEASE_PLEASE_TOKEN_PROVIDER_PEM }} + permission-contents: write + permission-pull-requests: write + + - name: Get GitHub App user ID + id: get-user-id + shell: bash + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + run: echo "user-id=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT" - name: Checkout code - uses: actions/checkout@v7 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 token: ${{ steps.app-token.outputs.token }} @@ -32,9 +45,9 @@ jobs: env: GH_TOKEN: ${{ steps.app-token.outputs.token }} run: | - git config --global user.name "github-actions[bot]" - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global url."https://$($env:GH_TOKEN)@github.com/".insteadOf "https://github.com/" + git config --global user.name "${{ steps.app-token.outputs.app-slug }}[bot]" + git config --global user.email "${{ steps.get-user-id.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com" + git config --global url."https://x-access-token:${{ steps.app-token.outputs.token }}@github.com/".insteadOf "https://github.com/" - name: Check for existing PR id: check_pr diff --git a/.github/workflows/release-please-gha.yml b/.github/workflows/release-please-gha.yml index 48e87c4b5..74a6233fb 100644 --- a/.github/workflows/release-please-gha.yml +++ b/.github/workflows/release-please-gha.yml @@ -34,7 +34,7 @@ jobs: if: needs.check-secret.outputs.has-token == 'true' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Validate PublicAPI.Unshipped.txt files are empty shell: pwsh @@ -62,13 +62,13 @@ jobs: - name: Generate GitHub App token id: app-token - uses: actions/create-github-app-token@v3 + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 with: client-id: ${{ vars.RELEASE_PLEASE_TOKEN_PROVIDER_APP_ID }} private-key: ${{ secrets.RELEASE_PLEASE_TOKEN_PROVIDER_PEM }} - name: Release Please - uses: googleapis/release-please-action@v5 + uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0 with: token: ${{ steps.app-token.outputs.token }} config-file: release-please-config.json diff --git a/.github/workflows/sonarcloud.yml b/.github/workflows/sonarcloud.yml index afd0135b0..a6954d70d 100644 --- a/.github/workflows/sonarcloud.yml +++ b/.github/workflows/sonarcloud.yml @@ -35,23 +35,23 @@ jobs: runs-on: windows-latest steps: - name: Set up JDK 17 - uses: actions/setup-java@v5.7.0 + uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6.0.1 with: - distribution: 'adopt' + distribution: 'temurin' java-version: 17 - name: Setup .NET 8 - uses: actions/setup-dotnet@v6 + uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0 with: dotnet-version: 8.0.x - name: Setup .NET 10 - uses: actions/setup-dotnet@v6 + uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0 with: dotnet-version: 10.0.x - - uses: actions/checkout@v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - name: Cache SonarCloud packages - uses: actions/cache@v6 + uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: ~/.sonar/cache key: ${{ runner.os }}-sonar @@ -61,12 +61,10 @@ jobs: - name: Build and analyze env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any - CollectCoverage: true - CoverletOutputFormat: 'opencover' # https://github.com/microsoft/vstest/issues/4014#issuecomment-1307913682 shell: pwsh run: | - dotnet tool run dotnet-sonarscanner begin /k:"microsoft_OpenAPI.NET" /o:"microsoft" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.cs.opencover.reportsPaths="test/**/coverage.opencover.xml" + dotnet tool run dotnet-sonarscanner begin /k:"microsoft_OpenAPI.NET" /o:"microsoft" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.cs.opencover.reportsPaths="**/coverage.opencover*.xml" dotnet workload restore dotnet build - dotnet test Microsoft.OpenApi.slnx --no-build --verbosity normal /p:CollectCoverage=true /p:CoverletOutputFormat=opencover - dotnet tool run dotnet-sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}" \ No newline at end of file + dotnet test --solution Microsoft.OpenApi.slnx --verbosity normal --coverlet --coverlet-output-format opencover --report-gh + dotnet tool run dotnet-sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}" diff --git a/.gitignore b/.gitignore index 258fee87a..09353aba3 100644 --- a/.gitignore +++ b/.gitignore @@ -127,7 +127,7 @@ nCrunchTemp_* *.mm.* AutoTest.Net/ -# Web workbench (sass) +# Sass cache .sass-cache/ # Installshield output folder diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 9127b1bd5..f7929cb4c 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "3.9.0" + ".": "3.10.2" } \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index 66912fac4..ad2bdf11d 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -49,23 +49,7 @@ "console": "internalConsole", "stopAtEntry": false, "requireExactSource": false, - }, - { - // Use IntelliSense to find out which attributes exist for C# debugging - // Use hover for the description of the existing attributes - // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/main/debugger-launchjson.md - "name": "Launch Workbench", - "type": "coreclr", - "request": "launch", - "preLaunchTask": "build", - // If you have changed target frameworks, make sure to update the program path. - "program": "${workspaceFolder}/src/Microsoft.OpenApi.WorkBench/bin/Debug/net8.0-windows/Microsoft.OpenApi.Workbench.exe", - "args": [], - "cwd": "${workspaceFolder}/src/Microsoft.OpenApi.Workbench", - // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console - "console": "internalConsole", - "stopAtEntry": false - }, + }, { "name": ".NET Core Attach", "type": "coreclr", diff --git a/.vscode/settings.json b/.vscode/settings.json index 186b10bea..619467ff3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,6 +6,7 @@ }, "cSpell.words": [ "csdl", - "Hidi" + "Hidi", + "Xunit" ] } \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index e68597ddc..c61ccd31c 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -43,12 +43,6 @@ "/consoleloggerparameters:NoSummary" ], "problemMatcher": "$msCompile" - }, - { - "label": "workbench", - "type": "shell", - "command": "src/Microsoft.OpenApi.WorkBench/bin/Debug/Microsoft.OpenApi.WorkBench.exe", - "problemMatcher": [] } ] } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 558c2a93f..0077ae140 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,32 @@ # Changelog +## [3.10.2](https://github.com/microsoft/OpenAPI.NET/compare/v3.10.1...v3.10.2) (2026-08-20) + + +### Bug Fixes + +* duplicate schema example when serializing to v2 ([#3045](https://github.com/microsoft/OpenAPI.NET/issues/3045)) ([50f5a2b](https://github.com/microsoft/OpenAPI.NET/commit/50f5a2bb119089bb48987494d906cb1a95cddbd1)) + +## [3.10.1](https://github.com/microsoft/OpenAPI.NET/compare/v3.10.0...v3.10.1) (2026-08-19) + + +### Bug Fixes + +* circular ref guard ([#3033](https://github.com/microsoft/OpenAPI.NET/issues/3033)) ([8018d40](https://github.com/microsoft/OpenAPI.NET/commit/8018d40aac5fc6ed5e022e6203ee917ecf36472c)) +* harden yaml parsing ([#3027](https://github.com/microsoft/OpenAPI.NET/issues/3027)) ([e0d5e9d](https://github.com/microsoft/OpenAPI.NET/commit/e0d5e9d105ad1a7b29ff6c628793614f5610f048)) + +## [3.10.0](https://github.com/microsoft/OpenAPI.NET/compare/v3.9.0...v3.10.0) (2026-08-11) + + +### Features + +* do not ignore multiple types when serializing to 3.0 ([#2960](https://github.com/microsoft/OpenAPI.NET/issues/2960)) ([ebaf27a](https://github.com/microsoft/OpenAPI.NET/commit/ebaf27ac46f8820e7b817cc33e693632c2fc908a)) + + +### Bug Fixes + +* bound YAML anchor/alias expansion to prevent OOM (billion laughs) ([#3000](https://github.com/microsoft/OpenAPI.NET/issues/3000)) ([2179326](https://github.com/microsoft/OpenAPI.NET/commit/21793261d6860fb6e607b71fd95c0bd320724764)) + ## [3.9.0](https://github.com/microsoft/OpenAPI.NET/compare/v3.8.0...v3.9.0) (2026-07-15) diff --git a/Directory.Build.props b/Directory.Build.props index aec9507b2..1971fa2c6 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -8,17 +8,19 @@ https://github.com/Microsoft/OpenAPI.NET https://github.com/microsoft/OpenAPI.NET/releases true + OpenSource + 2027-09 http://go.microsoft.com/fwlink/?LinkID=288890 https://github.com/Microsoft/OpenAPI.NET © Microsoft Corporation. All rights reserved. OpenAPI .NET - 3.9.0 + 3.10.2 true - + \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 25f1ec589..4e932b975 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,13 @@ -FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build-env +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build-env WORKDIR /app COPY ./src ./hidi/src COPY ./Directory.Build.props ./hidi/Directory.Build.props COPY ./README.md ./hidi/README.md WORKDIR /app/hidi -RUN dotnet publish ./src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj -c Release +# CI supplies the private feed config as a secret; local builds use default NuGet sources. +RUN --mount=type=secret,id=nuget_config,target=/app/hidi/NuGet.Config \ + dotnet publish ./src/Microsoft.OpenApi.Hidi/Microsoft.OpenApi.Hidi.csproj -c Release FROM mcr.microsoft.com/dotnet/runtime:8.0-jammy-chiseled AS runtime WORKDIR /app diff --git a/Microsoft.OpenApi.slnx b/Microsoft.OpenApi.slnx index f764776bf..8356bb428 100644 --- a/Microsoft.OpenApi.slnx +++ b/Microsoft.OpenApi.slnx @@ -9,7 +9,6 @@ - diff --git a/README.md b/README.md index 3c77f909f..8622d64d8 100644 --- a/README.md +++ b/README.md @@ -91,22 +91,6 @@ In order to test the validity of an OpenApi document, we avail the following too A commandline tool for validating and transforming OpenAPI descriptions. [Installation guidelines and documentation](https://github.com/microsoft/OpenAPI.NET/blob/main/src/Microsoft.OpenApi.Hidi/readme.md) -- Microsoft.OpenApi.Workbench - - A workbench tool consisting of a GUI where you can test and convert OpenAPI descriptions in both JSON and YAML from v2-->v3 and vice versa. - - #### Installation guidelines: - 1. Clone the repo locally by running this command: - `git clone https://github.com/microsoft/OpenAPI.NET.git` - 2. Open the solution file `(.slnx)` in the root of the project with Visual Studio - 3. Navigate to the `src/Microsoft.OpenApi.Workbench` directory and set it as the startup project - 4. Run the project and you'll see a GUI pop up resembling the one below: - - - ![workbench preview](https://raw.githubusercontent.com/microsoft/OpenAPI.NET/main/docs/images/workbench.png "a screenshot of the workbench application") - - 5. Copy and paste your OpenAPI descriptions in the **Input Content** window or paste the path to the descriptions file in the **Input File** textbox and click on `Convert` to render the results. - # Contributing This project welcomes contributions and suggestions. Most contributions require you to agree to a diff --git a/agents.md b/agents.md new file mode 100644 index 000000000..abeed736e --- /dev/null +++ b/agents.md @@ -0,0 +1,7 @@ +# Regex handling + +- Analyze regex patterns for matching complexity and excessive backtracking, including on long and near-matching inputs. Prefer equivalent patterns with less backtracking when available, while preserving matching semantics and target-framework compatibility. +- For fixed patterns on modern targets, use source-generated regexes with explicit match timeouts (`GeneratedRegex` under `NET8_0_OR_GREATER`). +- Use conditional compilation to provide a regular `Regex` with the same pattern and an explicit match timeout for older targets. Do not duplicate regex validation with a manually maintained character scanner. +- Older-runtime regex matching may still time out under load because timeouts use wall-clock time. If consumers encounter this limitation, recommend upgrading to a modern runtime that uses the source-generated implementation. +- Keep shared patterns in constants and reference those constants in validation diagnostics and tests. diff --git a/docs/images/workbench.png b/docs/images/workbench.png deleted file mode 100644 index 898fe9b5b..000000000 Binary files a/docs/images/workbench.png and /dev/null differ diff --git a/global.json b/global.json index d0c1ec64c..7494875e1 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,8 @@ { "sdk": { - "version": "10.0.302" + "version": "10.0.401" + }, + "test": { + "runner": "Microsoft.Testing.Platform" } } \ No newline at end of file diff --git a/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report-github.md b/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report-github.md index 6ee697332..95726f79a 100644 --- a/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report-github.md +++ b/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report-github.md @@ -1,20 +1,20 @@ ``` BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat) -INTEL XEON PLATINUM 8573C 2.30GHz, 1 CPU, 4 logical and 2 physical cores -.NET SDK 10.0.302 - [Host] : .NET 8.0.29 (8.0.29, 8.0.2926.32403), X64 RyuJIT x86-64-v4 - ShortRun : .NET 8.0.29 (8.0.29, 8.0.2926.32403), X64 RyuJIT x86-64-v4 +AMD EPYC 7763 2.45GHz, 1 CPU, 4 logical and 2 physical cores +.NET SDK 10.0.400 + [Host] : .NET 8.0.30 (8.0.30, 8.0.3026.36720), X64 RyuJIT x86-64-v3 + ShortRun : .NET 8.0.30 (8.0.30, 8.0.3026.36720), X64 RyuJIT x86-64-v3 Job=ShortRun IterationCount=3 LaunchCount=1 WarmupCount=3 ``` -| Method | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated | -|------------- |---------------:|--------------:|-------------:|----------:|----------:|----------:|-------------:| -| PetStoreYaml | 477.6 μs | 237.53 μs | 13.02 μs | 3.9063 | - | - | 375.67 KB | -| PetStoreJson | 193.3 μs | 29.03 μs | 1.59 μs | 1.9531 | - | - | 209.67 KB | -| GHESYaml | 878,325.2 μs | 555,688.50 μs | 30,459.16 μs | 4000.0000 | 3000.0000 | 1000.0000 | 310814.2 KB | -| GHESJson | 225,445.4 μs | 35,058.33 μs | 1,921.67 μs | 1000.0000 | - | - | 140426.65 KB | -| GHESNextYaml | 1,254,063.7 μs | 245,210.30 μs | 13,440.80 μs | 8000.0000 | 6000.0000 | 2000.0000 | 512928.9 KB | -| GHESNextJson | 588,121.5 μs | 83,680.85 μs | 4,586.83 μs | 5000.0000 | 4000.0000 | 1000.0000 | 344754.7 KB | +| Method | Mean | Error | StdDev | Gen0 | Gen1 | Gen2 | Allocated | +|------------- |---------------:|--------------:|-------------:|-----------:|-----------:|----------:|-------------:| +| PetStoreYaml | 569.0 μs | 108.17 μs | 5.93 μs | 19.5313 | - | - | 327.8 KB | +| PetStoreJson | 250.8 μs | 20.85 μs | 1.14 μs | 11.7188 | 1.9531 | - | 209.67 KB | +| GHESYaml | 831,169.7 μs | 217,393.05 μs | 11,916.05 μs | 18000.0000 | 10000.0000 | 2000.0000 | 267570.8 KB | +| GHESJson | 367,711.4 μs | 135,955.63 μs | 7,452.19 μs | 9000.0000 | 8000.0000 | 2000.0000 | 140917.06 KB | +| GHESNextYaml | 1,040,243.2 μs | 167,595.29 μs | 9,186.46 μs | 30000.0000 | 11000.0000 | 2000.0000 | 469507.05 KB | +| GHESNextJson | 615,173.6 μs | 143,051.39 μs | 7,841.13 μs | 22000.0000 | 10000.0000 | 2000.0000 | 345247.91 KB | diff --git a/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report.csv b/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report.csv index c5aeb2762..bb632a223 100644 --- a/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report.csv +++ b/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report.csv @@ -1,7 +1,7 @@ Method,Job,AnalyzeLaunchVariance,EvaluateOverhead,MaxAbsoluteError,MaxRelativeError,MinInvokeCount,MinIterationTime,OutlierMode,Affinity,EnvironmentVariables,Jit,LargeAddressAware,Platform,PowerPlanMode,Runtime,AllowVeryLargeObjects,Concurrent,CpuGroups,Force,HeapAffinitizeMask,HeapCount,NoAffinitize,RetainVm,Server,Arguments,BuildConfiguration,Clock,EngineFactory,NuGetReferences,Toolchain,IsMutator,InvocationCount,IterationCount,IterationTime,LaunchCount,MaxIterationCount,MaxWarmupIterationCount,MemoryRandomization,MinIterationCount,MinWarmupIterationCount,RunStrategy,UnrollFactor,WarmupCount,Mean,Error,StdDev,Gen0,Gen1,Gen2,Allocated -PetStoreYaml,ShortRun,False,Default,Default,Default,Default,Default,Default,1111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 8.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,3,Default,1,Default,Default,Default,Default,Default,Default,16,3,477.6 μs,237.53 μs,13.02 μs,3.9063,0.0000,0.0000,375.67 KB -PetStoreJson,ShortRun,False,Default,Default,Default,Default,Default,Default,1111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 8.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,3,Default,1,Default,Default,Default,Default,Default,Default,16,3,193.3 μs,29.03 μs,1.59 μs,1.9531,0.0000,0.0000,209.67 KB -GHESYaml,ShortRun,False,Default,Default,Default,Default,Default,Default,1111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 8.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,3,Default,1,Default,Default,Default,Default,Default,Default,16,3,"878,325.2 μs","555,688.50 μs","30,459.16 μs",4000.0000,3000.0000,1000.0000,310814.2 KB -GHESJson,ShortRun,False,Default,Default,Default,Default,Default,Default,1111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 8.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,3,Default,1,Default,Default,Default,Default,Default,Default,16,3,"225,445.4 μs","35,058.33 μs","1,921.67 μs",1000.0000,0.0000,0.0000,140426.65 KB -GHESNextYaml,ShortRun,False,Default,Default,Default,Default,Default,Default,1111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 8.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,3,Default,1,Default,Default,Default,Default,Default,Default,16,3,"1,254,063.7 μs","245,210.30 μs","13,440.80 μs",8000.0000,6000.0000,2000.0000,512928.9 KB -GHESNextJson,ShortRun,False,Default,Default,Default,Default,Default,Default,1111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 8.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,3,Default,1,Default,Default,Default,Default,Default,Default,16,3,"588,121.5 μs","83,680.85 μs","4,586.83 μs",5000.0000,4000.0000,1000.0000,344754.7 KB +PetStoreYaml,ShortRun,False,Default,Default,Default,Default,Default,Default,1111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 8.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,3,Default,1,Default,Default,Default,Default,Default,Default,16,3,569.0 μs,108.17 μs,5.93 μs,19.5313,0.0000,0.0000,327.8 KB +PetStoreJson,ShortRun,False,Default,Default,Default,Default,Default,Default,1111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 8.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,3,Default,1,Default,Default,Default,Default,Default,Default,16,3,250.8 μs,20.85 μs,1.14 μs,11.7188,1.9531,0.0000,209.67 KB +GHESYaml,ShortRun,False,Default,Default,Default,Default,Default,Default,1111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 8.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,3,Default,1,Default,Default,Default,Default,Default,Default,16,3,"831,169.7 μs","217,393.05 μs","11,916.05 μs",18000.0000,10000.0000,2000.0000,267570.8 KB +GHESJson,ShortRun,False,Default,Default,Default,Default,Default,Default,1111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 8.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,3,Default,1,Default,Default,Default,Default,Default,Default,16,3,"367,711.4 μs","135,955.63 μs","7,452.19 μs",9000.0000,8000.0000,2000.0000,140917.06 KB +GHESNextYaml,ShortRun,False,Default,Default,Default,Default,Default,Default,1111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 8.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,3,Default,1,Default,Default,Default,Default,Default,Default,16,3,"1,040,243.2 μs","167,595.29 μs","9,186.46 μs",30000.0000,11000.0000,2000.0000,469507.05 KB +GHESNextJson,ShortRun,False,Default,Default,Default,Default,Default,Default,1111,Empty,RyuJit,Default,X64,8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c,.NET 8.0,False,True,False,True,Default,Default,False,False,False,Default,Default,Default,Default,Default,Default,Default,Default,3,Default,1,Default,Default,Default,Default,Default,Default,16,3,"615,173.6 μs","143,051.39 μs","7,841.13 μs",22000.0000,10000.0000,2000.0000,345247.91 KB diff --git a/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report.html b/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report.html index f015841f0..c9feca148 100644 --- a/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report.html +++ b/performance/benchmark/BenchmarkDotNet.Artifacts/results/performance.Descriptions-report.html @@ -2,7 +2,7 @@ -performance.Descriptions-20260807-144733 +performance.Descriptions-20260819-014451 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Microsoft.OpenApi.Workbench/Themes/Metro/Styles.WPF.xaml b/src/Microsoft.OpenApi.Workbench/Themes/Metro/Styles.WPF.xaml deleted file mode 100644 index 988e787b8..000000000 --- a/src/Microsoft.OpenApi.Workbench/Themes/Metro/Styles.WPF.xaml +++ /dev/null @@ -1,830 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Visible - - - - - - - - - - - Visible - - - - - - - - - - - - - - Visible - - - - - - - - - - - Visible - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Microsoft.OpenApi.Workbench/Themes/Metro/Theme.Colors.xaml b/src/Microsoft.OpenApi.Workbench/Themes/Metro/Theme.Colors.xaml deleted file mode 100644 index 02babe07a..000000000 --- a/src/Microsoft.OpenApi.Workbench/Themes/Metro/Theme.Colors.xaml +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - - - - - - - #FF282828 - #FF3F3F3F - #FF565656 - #FF858585 - #FFB9B9B9 - #FFD7D7D7 - #FFE7E7E7 - #FFF4F4F4 - #FFF9F9F9 - #FFFFFFFF - - - #E5FFFFFF - #BFFFFFFF - #99FFFFFF - #72FFFFFF - #4CFFFFFF - #00FFFFFF - - - #72000000 - #4C000000 - #26000000 - #00000000 - #66E2E2E2 - - - #FF0086AF - #FF00AADE - #FF80D5EF - #FFB2E1EF - #2600AADE - - - #FFD0284C - #FFF55E7F - #FFFFCAD5 - - - #FF006481 - #FF8A9B0F - #FF3E4700 - #FFF14D0F - #FF8D2E00 - #FF81106B - #FF410135 - #FFFCA910 - #FF8D4902 - #FF037A54 - #FF003F2A - #FF154D85 - #FF02284D - #FF543511 - #FF211303 - #FF89806D - #FF393225 - #FF58458B - #FF211347 - #7FB9B9B9 - #33565656 - #7F3F3F3F - #FF686868 - #8000AADE - #CC3F3F3F - - - - #FF0092BE - #FF00AADE - #FF2BB9E5 - #FF55C8EB - #FF80D7F2 - - \ No newline at end of file diff --git a/src/Microsoft.OpenApi.YamlReader/Microsoft.OpenApi.YamlReader.csproj b/src/Microsoft.OpenApi.YamlReader/Microsoft.OpenApi.YamlReader.csproj index 8e7dcfde9..5a04ce302 100644 --- a/src/Microsoft.OpenApi.YamlReader/Microsoft.OpenApi.YamlReader.csproj +++ b/src/Microsoft.OpenApi.YamlReader/Microsoft.OpenApi.YamlReader.csproj @@ -28,20 +28,12 @@ - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - + - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - + - - - - + + @@ -71,4 +63,4 @@ - \ No newline at end of file + diff --git a/src/Microsoft.OpenApi.YamlReader/OpenApiReaderSettingsExtensions.cs b/src/Microsoft.OpenApi.YamlReader/OpenApiReaderSettingsExtensions.cs index ee5add0a0..006d1fb3d 100644 --- a/src/Microsoft.OpenApi.YamlReader/OpenApiReaderSettingsExtensions.cs +++ b/src/Microsoft.OpenApi.YamlReader/OpenApiReaderSettingsExtensions.cs @@ -1,4 +1,5 @@ -using Microsoft.OpenApi.YamlReader; +using System; +using Microsoft.OpenApi.YamlReader; namespace Microsoft.OpenApi.Reader; @@ -17,4 +18,18 @@ public static void AddYamlReader(this OpenApiReaderSettings settings) settings.TryAddReader(OpenApiConstants.Yaml, yamlReader); settings.TryAddReader(OpenApiConstants.Yml, yamlReader); } + + /// + /// Adds a YAML reader for the specified format using per-reader resource limits. + /// + /// The settings to add the reader to. + /// The YAML reader settings. + public static void AddYamlReader(this OpenApiReaderSettings settings, OpenApiYamlReaderSettings yamlSettings) + { + if (settings is null) throw new ArgumentNullException(nameof(settings)); + if (yamlSettings is null) throw new ArgumentNullException(nameof(yamlSettings)); + var yamlReader = new OpenApiYamlReader(yamlSettings); + settings.TryAddReader(OpenApiConstants.Yaml, yamlReader); + settings.TryAddReader(OpenApiConstants.Yml, yamlReader); + } } diff --git a/src/Microsoft.OpenApi.YamlReader/OpenApiYamlReader.cs b/src/Microsoft.OpenApi.YamlReader/OpenApiYamlReader.cs index cea996152..13f17d77d 100644 --- a/src/Microsoft.OpenApi.YamlReader/OpenApiYamlReader.cs +++ b/src/Microsoft.OpenApi.YamlReader/OpenApiYamlReader.cs @@ -7,9 +7,8 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.OpenApi.Reader; -using SharpYaml.Serialization; +using SharpYaml; using System; -using System.Linq; using System.Text; namespace Microsoft.OpenApi.YamlReader @@ -17,10 +16,46 @@ namespace Microsoft.OpenApi.YamlReader /// /// Reader for parsing YAML files into an OpenAPI document. /// + /// + /// Input is converted directly from SharpYaml parser events so resource limits are enforced + /// before SharpYaml's recursive YAML model loader can compose or expand the document. + /// public class OpenApiYamlReader : IOpenApiReader { private const int copyBufferSize = 4096; private static readonly OpenApiJsonReader _jsonReader = new(); + private readonly OpenApiYamlReaderSettings _yamlSettings; + + /// + /// Initializes a YAML reader using the current legacy global conversion limits. + /// + public OpenApiYamlReader() + : this(new() + { + MaxDepth = YamlConverter.MaxDepth, + MaxNodeCount = YamlConverter.MaxNodeCount, + MaxAliasExpansionNodeCount = YamlConverter.MaxAliasExpansionNodeCount, + }) + { + } + + /// + /// Initializes a YAML reader with immutable per-reader resource limits. + /// + /// The YAML reader settings. + public OpenApiYamlReader(OpenApiYamlReaderSettings settings) + { + if (settings is null) throw new ArgumentNullException(nameof(settings)); + settings.Validate(); + _yamlSettings = new() + { + MaxDepth = settings.MaxDepth, + MaxNodeCount = settings.MaxNodeCount, + MaxAliasExpansionNodeCount = settings.MaxAliasExpansionNodeCount, + MaxInputByteCount = settings.MaxInputByteCount, + MaxScalarLength = settings.MaxScalarLength, + }; + } /// public async Task ReadAsync(Stream input, @@ -29,16 +64,33 @@ public async Task ReadAsync(Stream input, CancellationToken cancellationToken = default) { if (input is null) throw new ArgumentNullException(nameof(input)); + if (settings is null) throw new ArgumentNullException(nameof(settings)); if (input is MemoryStream memoryStream) { - return UpdateFormat(Read(memoryStream, location, settings)); + return ReadCore(memoryStream, location, settings, cancellationToken); } else { using var preparedStream = new MemoryStream(); - await input.CopyToAsync(preparedStream, copyBufferSize, cancellationToken).ConfigureAwait(false); + try + { + await CopyToMemoryStreamAsync( + input, + preparedStream, + _yamlSettings.MaxInputByteCount, + cancellationToken).ConfigureAwait(false); + } + catch (OpenApiReaderException ex) + { + return new() + { + Document = null, + Diagnostic = CreateDiagnostic(new(ex)), + }; + } + preparedStream.Position = 0; - return UpdateFormat(Read(preparedStream, location, settings)); + return ReadCore(preparedStream, location, settings, cancellationToken); } } @@ -46,14 +98,22 @@ public async Task ReadAsync(Stream input, public ReadResult Read(MemoryStream input, Uri location, OpenApiReaderSettings settings) + => ReadCore(input, location, settings, CancellationToken.None); + + private ReadResult ReadCore(MemoryStream input, + Uri location, + OpenApiReaderSettings settings, + CancellationToken cancellationToken) { if (input is null) throw new ArgumentNullException(nameof(input)); if (settings is null) throw new ArgumentNullException(nameof(settings)); + cancellationToken.ThrowIfCancellationRequested(); JsonNode jsonNode; // Parse the YAML text in the stream into a sequence of JsonNodes try { + EnsureInputWithinLimit(input, _yamlSettings.MaxInputByteCount); #if NET // this represents net core, net5 and up using var stream = new StreamReader(input, default, true, -1, settings.LeaveStreamOpen); @@ -61,33 +121,65 @@ public ReadResult Read(MemoryStream input, // the implementation differs and results in a null reference exception in NETFX using var stream = new StreamReader(input, Encoding.UTF8, true, 4096, settings.LeaveStreamOpen); #endif - jsonNode = LoadJsonNodesFromYamlDocument(stream); + jsonNode = LoadJsonNodesFromYamlDocument(stream, cancellationToken); } catch (JsonException ex) { - var diagnostic = new OpenApiDiagnostic(); - diagnostic.Errors.Add(new($"#line={ex.LineNumber}", ex.Message)); - diagnostic.Format = OpenApiConstants.Yaml; return new() { Document = null, - Diagnostic = diagnostic, + Diagnostic = CreateDiagnostic(new($"#line={ex.LineNumber}", ex.Message)), }; } catch (OpenApiReaderException ex) { - var diagnostic = new OpenApiDiagnostic(); - diagnostic.Errors.Add(new(ex)); - diagnostic.Format = OpenApiConstants.Yaml; return new() { Document = null, - Diagnostic = diagnostic, + Diagnostic = CreateDiagnostic(new(ex)), }; } + cancellationToken.ThrowIfCancellationRequested(); return UpdateFormat(Read(jsonNode, location, settings)); } + + private static async Task CopyToMemoryStreamAsync( + Stream input, + MemoryStream output, + uint maxInputByteCount, + CancellationToken cancellationToken) + { + var buffer = new byte[copyBufferSize]; + long totalBytesRead = 0; + int bytesRead; + while ((bytesRead = await input.ReadAsync( + buffer, + 0, + buffer.Length, + cancellationToken).ConfigureAwait(false)) > 0) + { + if (bytesRead > (long)maxInputByteCount - totalBytesRead) + { + throw CreateInputLimitException(maxInputByteCount); + } + + await output.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false); + totalBytesRead += bytesRead; + } + } + + private static void EnsureInputWithinLimit(MemoryStream input, uint maxInputByteCount) + { + if (input.Length - input.Position > maxInputByteCount) + { + throw CreateInputLimitException(maxInputByteCount); + } + } + + private static OpenApiReaderException CreateInputLimitException(uint maxInputByteCount) + => new($"The YAML input exceeds the maximum supported size of {maxInputByteCount} bytes."); + private static ReadResult UpdateFormat(ReadResult result) { result.Diagnostic ??= new OpenApiDiagnostic(); @@ -114,13 +206,22 @@ public static ReadResult Read(JsonNode jsonNode, Uri location, OpenApiReaderSett // Parse the YAML try { - using var stream = new StreamReader(input); - jsonNode = LoadJsonNodesFromYamlDocument(stream); + EnsureInputWithinLimit(input, _yamlSettings.MaxInputByteCount); +#if NET + using var stream = new StreamReader(input, default, true, -1, settings?.LeaveStreamOpen ?? false); +#else + using var stream = new StreamReader(input, Encoding.UTF8, true, 4096, settings?.LeaveStreamOpen ?? false); +#endif + jsonNode = LoadJsonNodesFromYamlDocument(stream, CancellationToken.None); } catch (JsonException ex) { - diagnostic = new(); - diagnostic.Errors.Add(new($"#line={ex.LineNumber}", ex.Message)); + diagnostic = CreateDiagnostic(new($"#line={ex.LineNumber}", ex.Message)); + return default; + } + catch (OpenApiReaderException ex) + { + diagnostic = CreateDiagnostic(new(ex)); return default; } @@ -134,20 +235,34 @@ public static ReadResult Read(JsonNode jsonNode, Uri location, OpenApiReaderSett } /// - /// Helper method to turn streams into a sequence of JsonNodes + /// Converts the first YAML document in a stream into a JSON node. /// /// Stream containing YAML formatted text - /// Instance of a YamlDocument - static JsonNode LoadJsonNodesFromYamlDocument(TextReader input) + /// Propagates notification that parsing should be cancelled. + /// The converted JSON node. + private JsonNode LoadJsonNodesFromYamlDocument(TextReader input, CancellationToken cancellationToken) { - var yamlStream = new YamlStream(); - yamlStream.Load(input); - if (yamlStream.Documents.Any() && yamlStream.Documents[0].ToJsonNode() is { } jsonNode) + try { - return jsonNode; + return new YamlJsonParser(_yamlSettings).Parse(input, cancellationToken); } + catch (YamlException ex) + { + var location = ex.Start.Line >= 0 + ? $" at line {ex.Start.Line + 1}, column {ex.Start.Column + 1}" + : string.Empty; + throw new OpenApiReaderException($"Unable to parse the YAML document{location}: {ex.Message}", ex); + } + } - throw new InvalidOperationException("No documents found in the YAML stream."); + private static OpenApiDiagnostic CreateDiagnostic(OpenApiError error) + { + var diagnostic = new OpenApiDiagnostic + { + Format = OpenApiConstants.Yaml, + }; + diagnostic.Errors.Add(error); + return diagnostic; } } } diff --git a/src/Microsoft.OpenApi.YamlReader/OpenApiYamlReaderSettings.cs b/src/Microsoft.OpenApi.YamlReader/OpenApiYamlReaderSettings.cs new file mode 100644 index 000000000..b525f65c6 --- /dev/null +++ b/src/Microsoft.OpenApi.YamlReader/OpenApiYamlReaderSettings.cs @@ -0,0 +1,73 @@ +using System; + +namespace Microsoft.OpenApi.YamlReader; + +/// +/// Configures resource limits for an . +/// +public sealed class OpenApiYamlReaderSettings +{ + /// + /// Default maximum number of input bytes read from a single YAML document (128 MiB). + /// Bounds the buffered copy of a non-seekable stream, so an endless or oversized response body + /// cannot exhaust memory before parsing begins. + /// + public const uint DefaultMaxInputByteCount = 128 * 1024 * 1024; + + /// + /// Default maximum length of a single YAML scalar value (65,536 UTF-16 code units). + /// Bounds the cost of any one key, string, number, date or block literal. For reference, the + /// longest scalar in the Microsoft Graph beta description is 1,833 code units, so this leaves + /// substantial headroom for legitimate documents. + /// + public const uint DefaultMaxScalarLength = 64 * 1024; + + /// + /// Gets or sets the maximum YAML nesting depth. + /// Defaults to and cannot exceed + /// . + /// + public uint MaxDepth { get; set; } = YamlConverter.DefaultMaxDepth; + + /// + /// Gets or sets the maximum number of JSON nodes materialized from one YAML document. + /// Defaults to and cannot exceed + /// . + /// + public uint MaxNodeCount { get; set; } = YamlConverter.DefaultMaxNodeCount; + + /// + /// Gets or sets the maximum number of JSON nodes materialized specifically from aliases. + /// Defaults to . + /// + public uint MaxAliasExpansionNodeCount { get; set; } = YamlConverter.DefaultMaxAliasExpansionNodeCount; + + /// + /// Gets or sets the maximum number of input bytes read from one YAML document. + /// Defaults to . + /// + public uint MaxInputByteCount { get; set; } = DefaultMaxInputByteCount; + + /// + /// Gets or sets the maximum length of one YAML scalar value. + /// Defaults to . + /// + public uint MaxScalarLength { get; set; } = DefaultMaxScalarLength; + + internal void Validate() + { + YamlConverter.ValidateMaxDepth(MaxDepth, nameof(MaxDepth)); + YamlConverter.ValidateMaxNodeCount(MaxNodeCount, nameof(MaxNodeCount)); + ValidatePositive(MaxAliasExpansionNodeCount, nameof(MaxAliasExpansionNodeCount)); + ValidatePositive(MaxInputByteCount, nameof(MaxInputByteCount)); + ValidatePositive(MaxScalarLength, nameof(MaxScalarLength)); + } + + private static void ValidatePositive(uint value, string parameterName) + { + if (value == 0) + { + throw new ArgumentOutOfRangeException(parameterName, $"{parameterName} must be greater than zero."); + } + } +} diff --git a/src/Microsoft.OpenApi.YamlReader/PublicAPI.Shipped.txt b/src/Microsoft.OpenApi.YamlReader/PublicAPI.Shipped.txt index 1d7c628ca..4c258a43e 100644 --- a/src/Microsoft.OpenApi.YamlReader/PublicAPI.Shipped.txt +++ b/src/Microsoft.OpenApi.YamlReader/PublicAPI.Shipped.txt @@ -21,3 +21,24 @@ static Microsoft.OpenApi.YamlReader.YamlConverter.MaxDepth.get -> uint static Microsoft.OpenApi.YamlReader.YamlConverter.MaxDepth.set -> void static Microsoft.OpenApi.YamlReader.YamlConverter.MaxNodeCount.get -> uint static Microsoft.OpenApi.YamlReader.YamlConverter.MaxNodeCount.set -> void +Microsoft.OpenApi.YamlReader.OpenApiYamlReader.OpenApiYamlReader(Microsoft.OpenApi.YamlReader.OpenApiYamlReaderSettings! settings) -> void +Microsoft.OpenApi.YamlReader.OpenApiYamlReaderSettings +Microsoft.OpenApi.YamlReader.OpenApiYamlReaderSettings.MaxAliasExpansionNodeCount.get -> uint +Microsoft.OpenApi.YamlReader.OpenApiYamlReaderSettings.MaxAliasExpansionNodeCount.set -> void +Microsoft.OpenApi.YamlReader.OpenApiYamlReaderSettings.MaxDepth.get -> uint +Microsoft.OpenApi.YamlReader.OpenApiYamlReaderSettings.MaxDepth.set -> void +Microsoft.OpenApi.YamlReader.OpenApiYamlReaderSettings.MaxInputByteCount.get -> uint +Microsoft.OpenApi.YamlReader.OpenApiYamlReaderSettings.MaxInputByteCount.set -> void +Microsoft.OpenApi.YamlReader.OpenApiYamlReaderSettings.MaxNodeCount.get -> uint +Microsoft.OpenApi.YamlReader.OpenApiYamlReaderSettings.MaxNodeCount.set -> void +Microsoft.OpenApi.YamlReader.OpenApiYamlReaderSettings.MaxScalarLength.get -> uint +Microsoft.OpenApi.YamlReader.OpenApiYamlReaderSettings.MaxScalarLength.set -> void +Microsoft.OpenApi.YamlReader.OpenApiYamlReaderSettings.OpenApiYamlReaderSettings() -> void +const Microsoft.OpenApi.YamlReader.OpenApiYamlReaderSettings.DefaultMaxInputByteCount = 134217728 -> uint +const Microsoft.OpenApi.YamlReader.OpenApiYamlReaderSettings.DefaultMaxScalarLength = 65536 -> uint +const Microsoft.OpenApi.YamlReader.YamlConverter.DefaultMaxAliasExpansionNodeCount = 5000 -> uint +const Microsoft.OpenApi.YamlReader.YamlConverter.MaximumAllowedDepth = 256 -> uint +const Microsoft.OpenApi.YamlReader.YamlConverter.MaximumAllowedNodeCount = 10000000 -> uint +static Microsoft.OpenApi.Reader.OpenApiReaderSettingsExtensions.AddYamlReader(this Microsoft.OpenApi.Reader.OpenApiReaderSettings! settings, Microsoft.OpenApi.YamlReader.OpenApiYamlReaderSettings! yamlSettings) -> void +static Microsoft.OpenApi.YamlReader.YamlConverter.MaxAliasExpansionNodeCount.get -> uint +static Microsoft.OpenApi.YamlReader.YamlConverter.MaxAliasExpansionNodeCount.set -> void diff --git a/src/Microsoft.OpenApi.YamlReader/YamlConversionBudget.cs b/src/Microsoft.OpenApi.YamlReader/YamlConversionBudget.cs new file mode 100644 index 000000000..4b3ed75d9 --- /dev/null +++ b/src/Microsoft.OpenApi.YamlReader/YamlConversionBudget.cs @@ -0,0 +1,125 @@ +namespace Microsoft.OpenApi.YamlReader; + +/// +/// Tracks the resource budget consumed while materializing a single YAML document. +/// +/// +/// +/// A budget instance is scoped to one document and is not thread safe. Callers charge the budget +/// before allocating, so a document that would breach a limit is rejected without the +/// allocation ever happening. +/// +/// +/// Every limit breach throws , which the reader converts into an +/// OpenApiDiagnostic. That is the whole point of this type: hostile input produces a reportable +/// diagnostic rather than an unrecoverable process failure. +/// +/// +internal sealed class YamlConversionBudget +{ + private readonly uint _maxDepth; + private readonly uint _maxNodeCount; + private readonly uint _maxAliasExpansionNodeCount; + private uint _nodeCount; + private uint _aliasExpansionNodeCount; + + /// + /// Initializes a budget for a single document. + /// + /// Maximum nesting depth. Bounds stack and structural growth. + /// Maximum total nodes materialized from the document. + /// + /// Maximum nodes materialized specifically by expanding aliases. This is the anti-amplification + /// limit and is deliberately far smaller than : a large document is + /// legitimate, but a small document that expands into a large one is not. + /// + public YamlConversionBudget(uint maxDepth, uint maxNodeCount, uint maxAliasExpansionNodeCount) + { + _maxDepth = maxDepth; + _maxNodeCount = maxNodeCount; + _maxAliasExpansionNodeCount = maxAliasExpansionNodeCount; + } + + /// + /// Charges one node at the supplied depth. + /// + /// Zero-based nesting depth of the node being materialized. + /// The depth or total node limit would be exceeded. + public void EnterNode(uint depth) + { + ValidateDepth(depth); + AddNodes(1); + } + + /// + /// Charges the full cost of expanding an alias, against both the alias budget and the total budget. + /// + /// Zero-based nesting depth at which the alias appears. + /// Number of nodes the alias will materialize when cloned. + /// + /// Height of the subtree the alias will materialize, where a scalar has height 1. + /// + /// The depth, alias, or total node limit would be exceeded. + /// + /// Must be called before the clone is taken. Charging afterwards would allow the very allocation + /// this limit exists to prevent. + /// + public void EnterAlias(uint depth, uint expandedNodeCount, uint expandedHeight) + { + ValidateDepth(depth); + + // The alias site clears the depth check on its own, but expanding it grafts an entire + // subtree at this position. Without charging the grafted height, an anchor defined at a + // legal depth can be replayed from another legal depth to produce a tree deeper than the + // limit. The underlying YAML parser cannot catch this either, because it sees an alias as + // a single event and never re-walks the anchored content. + if (expandedHeight > _maxDepth - depth) + { + throw new OpenApiReaderException($"The YAML document expands an alias to more than the maximum supported nesting depth of {_maxDepth}."); + } + + if (expandedNodeCount > _maxAliasExpansionNodeCount - _aliasExpansionNodeCount) + { + throw new OpenApiReaderException($"The YAML document expands aliases to more than the maximum supported number of nodes ({_maxAliasExpansionNodeCount})."); + } + + _aliasExpansionNodeCount += expandedNodeCount; + AddNodes(expandedNodeCount); + } + + /// + /// Validates that a node at is within the depth limit. + /// + /// + /// is zero-based, so a node at that depth occupies level + /// depth + 1. Rejecting depth >= _maxDepth therefore admits exactly + /// _maxDepth levels, matching the limit enforced by the underlying YAML parser. + /// The comparison avoids arithmetic so it cannot overflow. + /// + private void ValidateDepth(uint depth) + { + if (depth >= _maxDepth) + { + throw new OpenApiReaderException($"The YAML document exceeds the maximum supported nesting depth of {_maxDepth}."); + } + } + + /// + /// Charges nodes against the total node budget. + /// + /// + /// The remaining headroom is compared as count > _maxNodeCount - _nodeCount rather than + /// _nodeCount + count > _maxNodeCount. Both operands are unsigned, so the latter form could + /// wrap and silently admit an over-budget document; the invariant _nodeCount <= _maxNodeCount + /// makes the subtraction used here safe from underflow. + /// + private void AddNodes(uint count) + { + if (count > _maxNodeCount - _nodeCount) + { + throw new OpenApiReaderException($"The YAML document expands to more than the maximum supported number of nodes ({_maxNodeCount}). This may indicate a YAML anchor/alias expansion attack."); + } + + _nodeCount += count; + } +} diff --git a/src/Microsoft.OpenApi.YamlReader/YamlConverter.cs b/src/Microsoft.OpenApi.YamlReader/YamlConverter.cs index 3a09ff876..aa73618f5 100644 --- a/src/Microsoft.OpenApi.YamlReader/YamlConverter.cs +++ b/src/Microsoft.OpenApi.YamlReader/YamlConverter.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Runtime.CompilerServices; using System.Text.Json; using System.Text.Json.Nodes; using SharpYaml; @@ -12,6 +13,10 @@ namespace Microsoft.OpenApi.YamlReader /// /// Provides extensions to convert YAML models to JSON models. /// + /// + /// These limits apply after a SharpYaml model exists. Use + /// for untrusted input so limits are enforced before SharpYaml model loading. + /// public static class YamlConverter { /// @@ -28,25 +33,38 @@ public static class YamlConverter /// public const uint DefaultMaxNodeCount = 5_000_000; + /// + /// Default maximum number of JSON nodes that may be materialized from YAML aliases. + /// + public const uint DefaultMaxAliasExpansionNodeCount = 5_000; + + /// + /// Maximum configurable YAML nesting depth. + /// + public const uint MaximumAllowedDepth = 256; + + /// + /// Maximum configurable number of JSON nodes that may be materialized from a single YAML document. + /// Bounds the running node totals so they cannot overflow while accumulating, which would surface as an + /// instead of a reportable diagnostic. + /// + public const uint MaximumAllowedNodeCount = 10_000_000; + private static uint _maxDepth = DefaultMaxDepth; private static uint _maxNodeCount = DefaultMaxNodeCount; + private static uint _maxAliasExpansionNodeCount = DefaultMaxAliasExpansionNodeCount; /// /// Gets or sets the maximum nesting depth allowed when converting a YAML node graph into JSON nodes. - /// Defaults to . Raise this if legitimate deeply nested documents are - /// being rejected, or lower it to fail faster when only shallow documents are expected. + /// Defaults to and cannot exceed the library's safe depth ceiling. /// - /// Thrown when set to zero. + /// Thrown when set outside the supported range. public static uint MaxDepth { get => _maxDepth; set { - if (value == 0) - { - throw new ArgumentOutOfRangeException(nameof(value), "MaxDepth must be greater than zero."); - } - + ValidateMaxDepth(value, nameof(value)); _maxDepth = value; } } @@ -55,50 +73,51 @@ public static uint MaxDepth /// Gets or sets the maximum number of JSON nodes that may be materialized from a single YAML document. /// Defaults to , guarding against YAML anchor/alias expansion /// ("billion laughs") attacks. Raise this if legitimate large documents are being rejected, or lower - /// it to fail faster when only small documents are expected. + /// it to fail faster when only small documents are expected. Cannot exceed + /// . /// - /// Thrown when set to zero. + /// Thrown when set outside the supported range. public static uint MaxNodeCount { get => _maxNodeCount; set { - if (value == 0) - { - throw new ArgumentOutOfRangeException(nameof(value), "MaxNodeCount must be greater than zero."); - } - + ValidateMaxNodeCount(value, nameof(value)); _maxNodeCount = value; } } /// - /// Tracks and enforces resource limits while converting a YAML node graph into JSON nodes, - /// failing fast when a hostile document would otherwise exhaust memory or the stack. + /// Gets or sets the maximum number of JSON nodes that may be materialized from YAML aliases. /// - private sealed class YamlConversionBudget + /// Thrown when set to zero. + public static uint MaxAliasExpansionNodeCount { - private readonly uint _maxDepth; - private readonly uint _maxNodeCount; - private uint _nodeCount; - - public YamlConversionBudget(uint maxDepth, uint maxNodeCount) + get => _maxAliasExpansionNodeCount; + set { - _maxDepth = maxDepth; - _maxNodeCount = maxNodeCount; + if (value == 0) + { + throw new ArgumentOutOfRangeException(nameof(value), "MaxAliasExpansionNodeCount must be greater than zero."); + } + + _maxAliasExpansionNodeCount = value; } + } - public void EnterNode(uint depth) + internal static void ValidateMaxDepth(uint value, string parameterName) + { + if (value == 0 || value > MaximumAllowedDepth) { - if (depth > _maxDepth) - { - throw new OpenApiReaderException($"The YAML document exceeds the maximum supported nesting depth of {_maxDepth}."); - } + throw new ArgumentOutOfRangeException(parameterName, $"MaxDepth must be between 1 and {MaximumAllowedDepth}."); + } + } - if (++_nodeCount > _maxNodeCount) - { - throw new OpenApiReaderException($"The YAML document expands to more than the maximum supported number of nodes ({_maxNodeCount}). This may indicate a YAML anchor/alias expansion (billion laughs) attack."); - } + internal static void ValidateMaxNodeCount(uint value, string parameterName) + { + if (value == 0 || value > MaximumAllowedNodeCount) + { + throw new ArgumentOutOfRangeException(parameterName, $"MaxNodeCount must be between 1 and {MaximumAllowedNodeCount}."); } } @@ -130,19 +149,7 @@ public static JsonNode ToJsonNode(this YamlDocument yaml) /// Thrown for YAML that is not compatible with JSON. public static JsonNode ToJsonNode(this YamlNode yaml) { - return yaml.ToJsonNode(new YamlConversionBudget(MaxDepth, MaxNodeCount), 0); - } - - private static JsonNode ToJsonNode(this YamlNode yaml, YamlConversionBudget budget, uint depth) - { - budget.EnterNode(depth); - return yaml switch - { - YamlMappingNode map => map.ToJsonObject(budget, depth), - YamlSequenceNode seq => seq.ToJsonArray(budget, depth), - YamlScalarNode scalar => scalar.ToJsonValue(), - _ => throw new NotSupportedException("This yaml isn't convertible to JSON") - }; + return CreateConversionContext().Convert(yaml, 0).Node; } /// @@ -173,19 +180,7 @@ public static YamlNode ToYamlNode(this JsonNode json) /// public static JsonObject ToJsonObject(this YamlMappingNode yaml) { - return yaml.ToJsonObject(new YamlConversionBudget(MaxDepth, MaxNodeCount), 0); - } - - private static JsonObject ToJsonObject(this YamlMappingNode yaml, YamlConversionBudget budget, uint depth) - { - var node = new JsonObject(); - foreach (var keyValuePair in yaml) - { - var key = ((YamlScalarNode)keyValuePair.Key).Value!; - node[key] = keyValuePair.Value.ToJsonNode(budget, depth + 1); - } - - return node; + return (JsonObject)CreateConversionContext().Convert(yaml, 0).Node; } private static YamlMappingNode ToYamlMapping(this JsonObject obj) @@ -203,18 +198,7 @@ private static YamlMappingNode ToYamlMapping(this JsonObject obj) /// public static JsonArray ToJsonArray(this YamlSequenceNode yaml) { - return yaml.ToJsonArray(new YamlConversionBudget(MaxDepth, MaxNodeCount), 0); - } - - private static JsonArray ToJsonArray(this YamlSequenceNode yaml, YamlConversionBudget budget, uint depth) - { - var node = new JsonArray(); - foreach (var value in yaml) - { - node.Add(value.ToJsonNode(budget, depth + 1)); - } - - return node; + return (JsonArray)CreateConversionContext().Convert(yaml, 0).Node; } private static YamlSequenceNode ToYamlSequence(this JsonArray arr) @@ -230,19 +214,150 @@ private static YamlSequenceNode ToYamlSequence(this JsonArray arr) "NULL" }; - private static JsonValue ToJsonValue(this YamlScalarNode yaml) + private static YamlConversionContext CreateConversionContext() { - return yaml.Style switch + var maxDepth = MaxDepth; + var maxNodeCount = MaxNodeCount; + var maxAliasExpansionNodeCount = MaxAliasExpansionNodeCount; + ValidateMaxDepth(maxDepth, nameof(MaxDepth)); + return new( + new YamlConversionBudget(maxDepth, maxNodeCount, maxAliasExpansionNodeCount)); + } + + internal static JsonValue ToJsonValue(string? value, ScalarStyle style) + { + return style switch { - ScalarStyle.Plain when decimal.TryParse(yaml.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out var d) => JsonValue.Create(d), - ScalarStyle.Plain when bool.TryParse(yaml.Value, out var b) => JsonValue.Create(b), - ScalarStyle.Plain when YamlNullRepresentations.Contains(yaml.Value) => (JsonValue)JsonNullSentinel.JsonNull.DeepClone(), - ScalarStyle.Plain => JsonValue.Create(yaml.Value), - ScalarStyle.SingleQuoted or ScalarStyle.DoubleQuoted or ScalarStyle.Literal or ScalarStyle.Folded or ScalarStyle.Any => JsonValue.Create(yaml.Value), - _ => throw new ArgumentOutOfRangeException(nameof(yaml)), + ScalarStyle.Plain when decimal.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var d) => JsonValue.Create(d), + ScalarStyle.Plain when bool.TryParse(value, out var b) => JsonValue.Create(b), + ScalarStyle.Plain when value is not null && YamlNullRepresentations.Contains(value) => (JsonValue)JsonNullSentinel.JsonNull.DeepClone(), + ScalarStyle.Plain => JsonValue.Create(value ?? string.Empty), + ScalarStyle.SingleQuoted or ScalarStyle.DoubleQuoted or ScalarStyle.Literal or ScalarStyle.Folded or ScalarStyle.Any => JsonValue.Create(value ?? string.Empty), + _ => throw new ArgumentOutOfRangeException(nameof(style)), }; } + private sealed class YamlConversionContext + { + private readonly YamlConversionBudget _budget; + private readonly Dictionary _completed = new(ReferenceEqualityComparer.Instance); + private readonly HashSet _active = new(ReferenceEqualityComparer.Instance); + + public YamlConversionContext(YamlConversionBudget budget) + { + _budget = budget; + } + + public MaterializedNode Convert(YamlNode yaml, uint depth) + { + try + { + RuntimeHelpers.EnsureSufficientExecutionStack(); + } + catch (InsufficientExecutionStackException ex) + { + throw new OpenApiReaderException("The YAML node graph is too deeply nested to convert safely.", ex); + } + + if (_active.Contains(yaml)) + { + throw new OpenApiReaderException("The YAML node graph contains a cycle."); + } + + if (_completed.TryGetValue(yaml, out var completed)) + { + _budget.EnterAlias(depth, completed.NodeCount, completed.Height); + return new(completed.Node.DeepClone(), completed.NodeCount, completed.Height); + } + + _budget.EnterNode(depth); + _active.Add(yaml); + try + { + var materialized = yaml switch + { + YamlMappingNode map => ConvertMapping(map, depth), + YamlSequenceNode sequence => ConvertSequence(sequence, depth), + YamlScalarNode scalar => new MaterializedNode(ToJsonValue(scalar.Value, scalar.Style), 1, 1), + _ => throw new NotSupportedException("This yaml isn't convertible to JSON") + }; + _completed.Add(yaml, materialized); + return materialized; + } + finally + { + _active.Remove(yaml); + } + } + + private MaterializedNode ConvertMapping(YamlMappingNode yaml, uint depth) + { + var node = new JsonObject(); + uint nodeCount = 1; + uint maxChildHeight = 0; + foreach (var keyValuePair in yaml) + { + if (keyValuePair.Key is not YamlScalarNode scalarKey || scalarKey.Value is null) + { + throw new OpenApiReaderException("YAML mapping keys must be scalar values."); + } + + if (node.ContainsKey(scalarKey.Value)) + { + throw new OpenApiReaderException($"The YAML mapping contains the duplicate key '{scalarKey.Value}'."); + } + + var child = Convert(keyValuePair.Value, depth + 1); + node.Add(scalarKey.Value, child.Node); + nodeCount = checked(nodeCount + child.NodeCount); + maxChildHeight = Math.Max(maxChildHeight, child.Height); + } + + return new(node, nodeCount, maxChildHeight + 1); + } + + private MaterializedNode ConvertSequence(YamlSequenceNode yaml, uint depth) + { + var node = new JsonArray(); + uint nodeCount = 1; + uint maxChildHeight = 0; + foreach (var value in yaml) + { + var child = Convert(value, depth + 1); + node.Add(child.Node); + nodeCount = checked(nodeCount + child.NodeCount); + maxChildHeight = Math.Max(maxChildHeight, child.Height); + } + + return new(node, nodeCount, maxChildHeight + 1); + } + } + + private sealed class MaterializedNode + { + public MaterializedNode(JsonNode node, uint nodeCount, uint height) + { + Node = node; + NodeCount = nodeCount; + Height = height; + } + + public JsonNode Node { get; } + public uint NodeCount { get; } + + /// Number of levels in this subtree, where a scalar has height 1. + public uint Height { get; } + } + + private sealed class ReferenceEqualityComparer : IEqualityComparer where T : class + { + public static ReferenceEqualityComparer Instance { get; } = new(); + + public bool Equals(T? x, T? y) => ReferenceEquals(x, y); + + public int GetHashCode(T obj) => RuntimeHelpers.GetHashCode(obj); + } + private static bool NeedsQuoting(string value) => string.IsNullOrEmpty(value) || decimal.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out _) || diff --git a/src/Microsoft.OpenApi.YamlReader/YamlJsonParser.cs b/src/Microsoft.OpenApi.YamlReader/YamlJsonParser.cs new file mode 100644 index 000000000..c7a3aab90 --- /dev/null +++ b/src/Microsoft.OpenApi.YamlReader/YamlJsonParser.cs @@ -0,0 +1,291 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text.Json.Nodes; +using System.Threading; +using SharpYaml; +using SharpYaml.Events; + +namespace Microsoft.OpenApi.YamlReader; + +/// +/// Iteratively materializes the first YAML document from parser events under resource limits. +/// +internal sealed class YamlJsonParser +{ + private const int LookAheadBufferCapacity = 8; + + private readonly YamlConversionBudget _budget; + private readonly Dictionary _anchors = new(StringComparer.Ordinal); + private readonly HashSet _activeAnchors = new(StringComparer.Ordinal); + private readonly Stack _containers = new(); + private readonly uint _maxScalarLength; + private readonly uint _maxDepth; + private JsonNode? _root; + + public YamlJsonParser(OpenApiYamlReaderSettings settings) + { + _budget = new(settings.MaxDepth, settings.MaxNodeCount, settings.MaxAliasExpansionNodeCount); + _maxScalarLength = settings.MaxScalarLength; + _maxDepth = settings.MaxDepth; + } + + public JsonNode Parse(TextReader input, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + var cancellationReader = new CancellationTokenTextReader(input, cancellationToken); + + // SharpYaml applies its own nesting limit, defaulting to 64. Passing the configured limit + // keeps the two enforcement points in agreement; leaving it unset would silently cap every + // reader at 64 regardless of MaxDepth, making values above the default a no-op. + var parser = new Parser( + new LookAheadBuffer(cancellationReader, LookAheadBufferCapacity), + (int)_maxDepth); + var documentStarted = false; + + while (true) + { + cancellationToken.ThrowIfCancellationRequested(); + if (!parser.MoveNext()) + { + break; + } + + switch (parser.Current) + { + case StreamStart: + break; + case DocumentStart: + documentStarted = true; + break; + case MappingStart mappingStart: + StartContainer(new JsonObject(), mappingStart.Anchor); + break; + case SequenceStart sequenceStart: + StartContainer(new JsonArray(), sequenceStart.Anchor); + break; + case Scalar scalar: + AddScalar(scalar, cancellationToken); + break; + case AnchorAlias alias: + AddAlias(alias, cancellationToken); + break; + case MappingEnd: + case SequenceEnd: + EndContainer(); + break; + case DocumentEnd: + return _root ?? throw new OpenApiReaderException("No content found in the YAML document."); + case StreamEnd: + if (documentStarted) + { + return _root ?? throw new OpenApiReaderException("No content found in the YAML document."); + } + + throw new OpenApiReaderException("No documents found in the YAML stream."); + default: + throw new OpenApiReaderException( + $"Unsupported YAML parser event '{parser.Current?.GetType().Name ?? ""}'."); + } + } + + throw new OpenApiReaderException("No documents found in the YAML stream."); + } + + private void StartContainer(JsonNode container, string? anchor) + { + _budget.EnterNode((uint)_containers.Count); + RegisterActiveAnchor(anchor); + _containers.Push(new(container, anchor)); + } + + private void AddScalar(Scalar scalar, CancellationToken cancellationToken) + { + if (scalar.Value is { } value && value.Length > _maxScalarLength) + { + throw new OpenApiReaderException( + $"The YAML scalar exceeds the maximum supported length of {_maxScalarLength} characters."); + } + + _budget.EnterNode((uint)_containers.Count); + cancellationToken.ThrowIfCancellationRequested(); + var materialized = new MaterializedNode( + YamlConverter.ToJsonValue(scalar.Value, scalar.Style), + 1, + 1, + scalar.Value); + + RegisterCompletedAnchor(scalar.Anchor, materialized); + AddNode(materialized); + } + + private void AddAlias(AnchorAlias alias, CancellationToken cancellationToken) + { + if (_activeAnchors.Contains(alias.Value)) + { + throw new OpenApiReaderException($"The YAML alias '*{alias.Value}' forms a cycle."); + } + + if (!_anchors.TryGetValue(alias.Value, out var anchor)) + { + throw new OpenApiReaderException($"The YAML alias '*{alias.Value}' refers to an unknown anchor."); + } + + _budget.EnterAlias((uint)_containers.Count, anchor.NodeCount, anchor.Height); + cancellationToken.ThrowIfCancellationRequested(); + AddNode(new(anchor.Node.DeepClone(), anchor.NodeCount, anchor.Height, anchor.MappingKey)); + } + + private void EndContainer() + { + if (_containers.Count == 0) + { + throw new OpenApiReaderException("The YAML document contains an unexpected container terminator."); + } + + var frame = _containers.Pop(); + if (frame.PendingKey is not null) + { + throw new OpenApiReaderException("The YAML mapping contains a key without a value."); + } + + var materialized = new MaterializedNode(frame.Container, frame.NodeCount, frame.MaxChildHeight + 1, null); + if (frame.Anchor is not null) + { + _activeAnchors.Remove(frame.Anchor); + _anchors.Add(frame.Anchor, materialized); + } + + AddNode(materialized); + } + + private void AddNode(MaterializedNode materialized) + { + if (_containers.Count == 0) + { + if (_root is not null) + { + throw new OpenApiReaderException("The YAML document contains more than one root node."); + } + + _root = materialized.Node; + return; + } + + var frame = _containers.Peek(); + switch (frame.Container) + { + case JsonArray array: + array.Add(materialized.Node); + frame.NodeCount = checked(frame.NodeCount + materialized.NodeCount); + frame.MaxChildHeight = Math.Max(frame.MaxChildHeight, materialized.Height); + break; + case JsonObject when frame.PendingKey is null: + frame.PendingKey = materialized.MappingKey + ?? throw new OpenApiReaderException("YAML mapping keys must be scalar values."); + break; + case JsonObject map: + if (map.ContainsKey(frame.PendingKey)) + { + throw new OpenApiReaderException($"The YAML mapping contains the duplicate key '{frame.PendingKey}'."); + } + + map.Add(frame.PendingKey, materialized.Node); + frame.PendingKey = null; + frame.NodeCount = checked(frame.NodeCount + materialized.NodeCount); + frame.MaxChildHeight = Math.Max(frame.MaxChildHeight, materialized.Height); + break; + } + } + + private void RegisterActiveAnchor(string? anchor) + { + if (anchor is null || anchor.Length == 0) + { + return; + } + + if (_anchors.ContainsKey(anchor) || !_activeAnchors.Add(anchor)) + { + throw new OpenApiReaderException($"The YAML document contains the duplicate anchor '&{anchor}'."); + } + } + + private void RegisterCompletedAnchor(string? anchor, MaterializedNode materialized) + { + if (anchor is null || anchor.Length == 0) + { + return; + } + + if (_anchors.ContainsKey(anchor) || _activeAnchors.Contains(anchor)) + { + throw new OpenApiReaderException($"The YAML document contains the duplicate anchor '&{anchor}'."); + } + + _anchors.Add(anchor, materialized); + } + + private sealed class ContainerFrame(JsonNode container, string? anchor) + { + public JsonNode Container { get; } = container; + public string? Anchor { get; } = anchor; + public string? PendingKey { get; set; } + public uint NodeCount { get; set; } = 1; + + /// Height of the tallest child added so far; 0 while the container is empty. + public uint MaxChildHeight { get; set; } + } + + private sealed class MaterializedNode + { + public MaterializedNode(JsonNode node, uint nodeCount, uint height, string? mappingKey) + { + Node = node; + NodeCount = nodeCount; + Height = height; + MappingKey = mappingKey; + } + + public JsonNode Node { get; } + public uint NodeCount { get; } + + /// Number of levels in this subtree, where a scalar has height 1. + public uint Height { get; } + + public string? MappingKey { get; } + } + + private sealed class CancellationTokenTextReader(TextReader innerReader, CancellationToken cancellationToken) : TextReader + { + public override int Peek() + { + cancellationToken.ThrowIfCancellationRequested(); + return innerReader.Peek(); + } + + public override int Read() + { + cancellationToken.ThrowIfCancellationRequested(); + return innerReader.Read(); + } + + public override int Read(char[] buffer, int index, int count) + { + cancellationToken.ThrowIfCancellationRequested(); + return innerReader.Read(buffer, index, count); + } + + public override string? ReadLine() + { + cancellationToken.ThrowIfCancellationRequested(); + return base.ReadLine(); + } + + public override string ReadToEnd() + { + cancellationToken.ThrowIfCancellationRequested(); + return base.ReadToEnd(); + } + } +} diff --git a/src/Microsoft.OpenApi/Expressions/CompositeExpression.cs b/src/Microsoft.OpenApi/Expressions/CompositeExpression.cs index cac554318..db2a97caf 100644 --- a/src/Microsoft.OpenApi/Expressions/CompositeExpression.cs +++ b/src/Microsoft.OpenApi/Expressions/CompositeExpression.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; @@ -10,10 +11,17 @@ namespace Microsoft.OpenApi /// /// String literal with embedded expressions /// - public class CompositeExpression : RuntimeExpression + public partial class CompositeExpression : RuntimeExpression { private readonly string template; - private readonly Regex expressionPattern = new(@"{(?\$[^}]*)"); + private const string ExpressionPattern = @"{(?\$[^}]*)"; + +#if NET8_0_OR_GREATER + [GeneratedRegex(ExpressionPattern, RegexOptions.None, matchTimeoutMilliseconds: 100)] + private static partial Regex ExpressionRegex(); +#else + private static readonly Regex ExpressionRegex = new(ExpressionPattern, RegexOptions.None, TimeSpan.FromMilliseconds(100)); +#endif /// /// Expressions embedded into string literal @@ -24,12 +32,17 @@ public class CompositeExpression : RuntimeExpression /// Create a composite expression from a string literal with an embedded expression /// /// + /// Extracting embedded expressions exceeds the regex match timeout. public CompositeExpression(string expression) { template = expression; // Extract subexpressions and convert to RuntimeExpressions - var matches = expressionPattern.Matches(expression); +#if NET8_0_OR_GREATER + var matches = ExpressionRegex().Matches(expression); +#else + var matches = ExpressionRegex.Matches(expression); +#endif foreach (var item in matches.Cast()) { diff --git a/src/Microsoft.OpenApi/Microsoft.OpenApi.csproj b/src/Microsoft.OpenApi/Microsoft.OpenApi.csproj index 81729fa20..8e1ebb555 100644 --- a/src/Microsoft.OpenApi/Microsoft.OpenApi.csproj +++ b/src/Microsoft.OpenApi/Microsoft.OpenApi.csproj @@ -24,13 +24,10 @@ true - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - + + + + diff --git a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs index 68259a618..03086600d 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiDocument.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiDocument.cs @@ -857,6 +857,34 @@ static bool AddToDictionary(IDictionary dict, string key // Register only if it was actually added to the collection return added && (Workspace?.RegisterComponentForDocument(this, componentToRegister, id) ?? false); } + + /// + /// Finds an operation in the document by its operation ID. + /// + /// The operation ID to search for. + /// The matching , or if not found. + public OpenApiOperation? GetOperationById(string operationId) + { + Utils.CheckArgumentNullOrEmpty(operationId); + + return GetOperationByIdFromPathItems(Paths, operationId) ?? + (Webhooks is not null ? + GetOperationByIdFromPathItems(Webhooks, operationId) : null); + } + + private static OpenApiOperation? GetOperationByIdFromPathItems(IDictionary pathItems, string operationId) + { + foreach (var pathItem in pathItems.Values) + { + if (pathItem.Operations is null) continue; + foreach (var operation in pathItem.Operations.Values) + { + if (string.Equals(operation.OperationId, operationId, StringComparison.Ordinal)) + return operation; + } + } + return null; + } } internal class FindSchemaReferences : OpenApiVisitorBase diff --git a/src/Microsoft.OpenApi/Models/OpenApiResponse.cs b/src/Microsoft.OpenApi/Models/OpenApiResponse.cs index 4ee39336e..6bd02af25 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiResponse.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiResponse.cs @@ -73,7 +73,7 @@ public virtual void SerializeAsV3(IOpenApiWriter writer) SerializeInternal(writer, OpenApiSpecVersion.OpenApi3_0, (writer, element) => element.SerializeAsV3(writer)); } - private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version, + private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version, Action callback) { Utils.CheckArgumentNull(writer); @@ -87,7 +87,7 @@ private void SerializeInternal(IOpenApiWriter writer, OpenApiSpecVersion version } // description - writer.WriteRequiredProperty(OpenApiConstants.Description, Description); + writer.WriteProperty(OpenApiConstants.Description, Description); // headers writer.WriteOptionalMap(OpenApiConstants.Headers, Headers, callback); @@ -120,7 +120,7 @@ public virtual void SerializeAsV2(IOpenApiWriter writer) writer.WriteStartObject(); // description - writer.WriteRequiredProperty(OpenApiConstants.Description, Description); + writer.WriteProperty(OpenApiConstants.Description, Description); var extensionsClone = Extensions is not null ? new Dictionary(Extensions) : null; @@ -177,7 +177,7 @@ public virtual void SerializeAsV2(IOpenApiWriter writer) // so remove it from the cloned collection so we don't write it again. extensionsClone?.Remove(key); } - } + } } } diff --git a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs index 34592d0e6..79bd88dd5 100644 --- a/src/Microsoft.OpenApi/Models/OpenApiSchema.cs +++ b/src/Microsoft.OpenApi/Models/OpenApiSchema.cs @@ -964,10 +964,7 @@ private void SerializeAsV2( writer.WriteOptionalObject(OpenApiConstants.ExternalDocs, ExternalDocs, (w, s) => s.SerializeAsV2(w)); // example -#pragma warning disable CS0618 writer.WriteOptionalObject(OpenApiConstants.Example, GetCompatibilityExample(), (w, e) => w.WriteAny(e)); - writer.WriteOptionalObject(OpenApiConstants.Example, Example, (w, e) => w.WriteAny(e)); -#pragma warning restore CS0618 // x-nullable extension SerializeNullable(writer, OpenApiSpecVersion.OpenApi2_0); diff --git a/src/Microsoft.OpenApi/Models/References/BaseOpenApiReferenceHolder.cs b/src/Microsoft.OpenApi/Models/References/BaseOpenApiReferenceHolder.cs index ed2936bd4..2af750476 100644 --- a/src/Microsoft.OpenApi/Models/References/BaseOpenApiReferenceHolder.cs +++ b/src/Microsoft.OpenApi/Models/References/BaseOpenApiReferenceHolder.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; namespace Microsoft.OpenApi; /// @@ -10,6 +11,11 @@ namespace Microsoft.OpenApi; /// The type for the reference holding the additional fields and annotations public abstract class BaseOpenApiReferenceHolder : IOpenApiReferenceHolder where T : class, IOpenApiReferenceable, U where U : IOpenApiReferenceable, IOpenApiSerializable where V : BaseOpenApiReference, new() { + [ThreadStatic] + private static HashSet>? t_activeReferenceAccesses; + [ThreadStatic] + private static HashSet>? t_activeTargetActions; + /// public virtual U? Target { @@ -19,28 +25,129 @@ public virtual U? Target return Reference.HostDocument.ResolveReferenceTo(Reference, this as IOpenApiSchema); } } + + /// + /// Gets a value from the resolved target while detecting cycles in delegated member access. + /// + /// The type of value to get from the target. + /// Selects the value from the resolved target. + /// The selected value, or the default value when the target cannot be resolved. + /// + /// The guard remains active while reads the target member. This covers + /// the complete delegated call chain without changing the immediate-resolution semantics of + /// or walking an acyclic chain more than once. + /// + private protected TResult GetFromTarget(Func selector) + { + Utils.CheckArgumentNull(selector); + return ExecuteWithReferenceAccessGuard(this, () => + { + return Target is { } target ? selector(target) : default!; + }); + } + + /// + /// Executes an action against the resolved target while detecting cycles in delegated access. + /// + /// The action to execute against the resolved target. + private protected void ApplyToTarget(Action action) + { + Utils.CheckArgumentNull(action); + ExecuteWithTargetActionGuard(this, () => + { + if (Target is { } target) + { + action(target); + } + return null; + }); + } + + private static TResult ExecuteWithReferenceAccessGuard( + BaseOpenApiReferenceHolder holder, + Func action) + { + return ExecuteWithReferenceGuard(ref t_activeReferenceAccesses, holder, action); + } + + private static TResult ExecuteWithTargetActionGuard( + BaseOpenApiReferenceHolder holder, + Func action) + { + return ExecuteWithReferenceGuard(ref t_activeTargetActions, holder, action); + } + + private static TResult ExecuteWithReferenceGuard( + ref HashSet>? activeReferences, + BaseOpenApiReferenceHolder holder, + Func action) + { + activeReferences ??= new HashSet>(ReferenceHolderComparer.Instance); + if (!activeReferences.Add(holder)) + { + throw new InvalidOperationException($"Circular reference detected while resolving reference: {holder.Reference.ReferenceV3}"); + } + + try + { + RuntimeHelpers.EnsureSufficientExecutionStack(); + return action(); + } + catch (InsufficientExecutionStackException ex) + { + throw new InvalidOperationException( + $"The chain of references starting at {holder.Reference.ReferenceV3} is nested too deeply to resolve.", + ex); + } + finally + { + activeReferences.Remove(holder); + if (activeReferences.Count == 0) + { + activeReferences = null; + } + } + } + /// public T? RecursiveTarget { get { - return ResolveRecursiveTarget(new HashSet>()); + var visitedReferences = new HashSet>(ReferenceHolderComparer.Instance); + BaseOpenApiReferenceHolder current = this; + + while (visitedReferences.Add(current)) + { + switch (current.Target) + { + case BaseOpenApiReferenceHolder recursiveTarget: + current = recursiveTarget; + break; + case T concrete: + return concrete; + default: + return null; + } + } + + throw new InvalidOperationException($"Circular reference detected while resolving reference: {current.Reference.ReferenceV3}"); } } - private T? ResolveRecursiveTarget(ISet> visitedReferences) + private sealed class ReferenceHolderComparer : IEqualityComparer> { - if (!visitedReferences.Add(this)) + internal static ReferenceHolderComparer Instance { get; } = new(); + + public bool Equals(BaseOpenApiReferenceHolder? x, BaseOpenApiReferenceHolder? y) { - throw new InvalidOperationException($"Circular reference detected while resolving reference: {Reference.ReferenceV3}"); + return ReferenceEquals(x, y); } - return Target switch + public int GetHashCode(BaseOpenApiReferenceHolder obj) { - BaseOpenApiReferenceHolder recursiveTarget => recursiveTarget.ResolveRecursiveTarget(visitedReferences), - T concrete => concrete, - _ => null - }; + return RuntimeHelpers.GetHashCode(obj); + } } /// /// Copy the reference as a target element with overrides. @@ -160,9 +267,6 @@ private protected void SerializeInternal(IOpenApiWriter writer, Action action) { Utils.CheckArgumentNull(writer); - if (Target is not null) - { - action(writer, Target); - } + ApplyToTarget(element => action(writer, element)); } } diff --git a/src/Microsoft.OpenApi/Models/References/OpenApiCallbackReference.cs b/src/Microsoft.OpenApi/Models/References/OpenApiCallbackReference.cs index bdf22eb03..cd6cfdeff 100644 --- a/src/Microsoft.OpenApi/Models/References/OpenApiCallbackReference.cs +++ b/src/Microsoft.OpenApi/Models/References/OpenApiCallbackReference.cs @@ -33,10 +33,10 @@ private OpenApiCallbackReference(OpenApiCallbackReference callback) : base(callb } /// - public Dictionary? PathItems { get => Target?.PathItems; } + public Dictionary? PathItems { get => GetFromTarget(static target => target.PathItems); } /// - public IDictionary? Extensions { get => Target?.Extensions; } + public IDictionary? Extensions { get => GetFromTarget(static target => target.Extensions); } /// public override IOpenApiCallback CopyReferenceAsTargetElementWithOverrides(IOpenApiCallback source) diff --git a/src/Microsoft.OpenApi/Models/References/OpenApiExampleReference.cs b/src/Microsoft.OpenApi/Models/References/OpenApiExampleReference.cs index 5834d4fb9..207271afe 100644 --- a/src/Microsoft.OpenApi/Models/References/OpenApiExampleReference.cs +++ b/src/Microsoft.OpenApi/Models/References/OpenApiExampleReference.cs @@ -35,31 +35,31 @@ private OpenApiExampleReference(OpenApiExampleReference example) : base(example) /// public string? Description { - get => string.IsNullOrEmpty(Reference.Description) ? Target?.Description : Reference.Description; + get => string.IsNullOrEmpty(Reference.Description) ? GetFromTarget(static target => target.Description) : Reference.Description; set => Reference.Description = value; } /// public string? Summary { - get => string.IsNullOrEmpty(Reference.Summary) ? Target?.Summary : Reference.Summary; + get => string.IsNullOrEmpty(Reference.Summary) ? GetFromTarget(static target => target.Summary) : Reference.Summary; set => Reference.Summary = value; } /// - public IDictionary? Extensions { get => Target?.Extensions; } + public IDictionary? Extensions { get => GetFromTarget(static target => target.Extensions); } /// - public string? ExternalValue { get => Target?.ExternalValue; } + public string? ExternalValue { get => GetFromTarget(static target => target.ExternalValue); } /// - public JsonNode? Value { get => Target?.Value; } + public JsonNode? Value { get => GetFromTarget(static target => target.Value); } /// - public JsonNode? DataValue { get => Target?.DataValue; } + public JsonNode? DataValue { get => GetFromTarget(static target => target.DataValue); } /// - public string? SerializedValue { get => Target?.SerializedValue; } + public string? SerializedValue { get => GetFromTarget(static target => target.SerializedValue); } /// public override IOpenApiExample CopyReferenceAsTargetElementWithOverrides(IOpenApiExample source) diff --git a/src/Microsoft.OpenApi/Models/References/OpenApiHeaderReference.cs b/src/Microsoft.OpenApi/Models/References/OpenApiHeaderReference.cs index d0c7fe04e..b419724f4 100644 --- a/src/Microsoft.OpenApi/Models/References/OpenApiHeaderReference.cs +++ b/src/Microsoft.OpenApi/Models/References/OpenApiHeaderReference.cs @@ -36,42 +36,42 @@ private OpenApiHeaderReference(OpenApiHeaderReference header) : base(header) /// public string? Description { - get => string.IsNullOrEmpty(Reference.Description) ? Target?.Description : Reference.Description; + get => string.IsNullOrEmpty(Reference.Description) ? GetFromTarget(static target => target.Description) : Reference.Description; set => Reference.Description = value; } /// - public bool Required { get => Target?.Required ?? default; } + public bool Required { get => GetFromTarget(static target => target.Required); } /// - public bool Deprecated { get => Target?.Deprecated ?? default; } + public bool Deprecated { get => GetFromTarget(static target => target.Deprecated); } /// - public bool AllowEmptyValue { get => Target?.AllowEmptyValue ?? default; } + public bool AllowEmptyValue { get => GetFromTarget(static target => target.AllowEmptyValue); } /// - public IOpenApiSchema? Schema { get => Target?.Schema; } + public IOpenApiSchema? Schema { get => GetFromTarget(static target => target.Schema); } /// - public ParameterStyle? Style { get => Target?.Style; } + public ParameterStyle? Style { get => GetFromTarget(static target => target.Style); } /// - public bool Explode { get => Target?.Explode ?? default; } + public bool Explode { get => GetFromTarget(static target => target.Explode); } /// - public bool AllowReserved { get => Target?.AllowReserved ?? default; } + public bool AllowReserved { get => GetFromTarget(static target => target.AllowReserved); } /// - public JsonNode? Example { get => Target?.Example; } + public JsonNode? Example { get => GetFromTarget(static target => target.Example); } /// - public IDictionary? Examples { get => Target?.Examples; } + public IDictionary? Examples { get => GetFromTarget(static target => target.Examples); } /// - public IDictionary? Content { get => Target?.Content; } + public IDictionary? Content { get => GetFromTarget(static target => target.Content); } /// - public IDictionary? Extensions { get => Target?.Extensions; } + public IDictionary? Extensions { get => GetFromTarget(static target => target.Extensions); } /// public override IOpenApiHeader CopyReferenceAsTargetElementWithOverrides(IOpenApiHeader source) diff --git a/src/Microsoft.OpenApi/Models/References/OpenApiLinkReference.cs b/src/Microsoft.OpenApi/Models/References/OpenApiLinkReference.cs index a11decf63..2e338493c 100644 --- a/src/Microsoft.OpenApi/Models/References/OpenApiLinkReference.cs +++ b/src/Microsoft.OpenApi/Models/References/OpenApiLinkReference.cs @@ -34,27 +34,27 @@ private OpenApiLinkReference(OpenApiLinkReference reference) : base(reference) /// public string? Description { - get => string.IsNullOrEmpty(Reference.Description) ? Target?.Description : Reference.Description; + get => string.IsNullOrEmpty(Reference.Description) ? GetFromTarget(static target => target.Description) : Reference.Description; set => Reference.Description = value; } /// - public string? OperationRef { get => Target?.OperationRef; } + public string? OperationRef { get => GetFromTarget(static target => target.OperationRef); } /// - public string? OperationId { get => Target?.OperationId; } + public string? OperationId { get => GetFromTarget(static target => target.OperationId); } /// - public OpenApiServer? Server { get => Target?.Server; } + public OpenApiServer? Server { get => GetFromTarget(static target => target.Server); } /// - public IDictionary? Parameters { get => Target?.Parameters; } + public IDictionary? Parameters { get => GetFromTarget(static target => target.Parameters); } /// - public RuntimeExpressionAnyWrapper? RequestBody { get => Target?.RequestBody; } + public RuntimeExpressionAnyWrapper? RequestBody { get => GetFromTarget(static target => target.RequestBody); } /// - public IDictionary? Extensions { get => Target?.Extensions; } + public IDictionary? Extensions { get => GetFromTarget(static target => target.Extensions); } /// public override void SerializeAsV2(IOpenApiWriter writer) diff --git a/src/Microsoft.OpenApi/Models/References/OpenApiMediaTypeReference.cs b/src/Microsoft.OpenApi/Models/References/OpenApiMediaTypeReference.cs index 47816ed44..4ba18ffc1 100644 --- a/src/Microsoft.OpenApi/Models/References/OpenApiMediaTypeReference.cs +++ b/src/Microsoft.OpenApi/Models/References/OpenApiMediaTypeReference.cs @@ -34,28 +34,28 @@ private OpenApiMediaTypeReference(OpenApiMediaTypeReference mediaTypeReference) } /// - public IOpenApiSchema? Schema { get => Target?.Schema; } + public IOpenApiSchema? Schema { get => GetFromTarget(static target => target.Schema); } /// - public IOpenApiSchema? ItemSchema { get => Target?.ItemSchema; } + public IOpenApiSchema? ItemSchema { get => GetFromTarget(static target => target.ItemSchema); } /// - public JsonNode? Example { get => Target?.Example; } + public JsonNode? Example { get => GetFromTarget(static target => target.Example); } /// - public IDictionary? Examples { get => Target?.Examples; } + public IDictionary? Examples { get => GetFromTarget(static target => target.Examples); } /// - public IDictionary? Encoding { get => Target?.Encoding; } + public IDictionary? Encoding { get => GetFromTarget(static target => target.Encoding); } /// - public OpenApiEncoding? ItemEncoding { get => Target?.ItemEncoding; } + public OpenApiEncoding? ItemEncoding { get => GetFromTarget(static target => target.ItemEncoding); } /// - public IList? PrefixEncoding { get => Target?.PrefixEncoding; } + public IList? PrefixEncoding { get => GetFromTarget(static target => target.PrefixEncoding); } /// - public IDictionary? Extensions { get => Target?.Extensions; } + public IDictionary? Extensions { get => GetFromTarget(static target => target.Extensions); } /// public override IOpenApiMediaType CopyReferenceAsTargetElementWithOverrides(IOpenApiMediaType source) diff --git a/src/Microsoft.OpenApi/Models/References/OpenApiParameterReference.cs b/src/Microsoft.OpenApi/Models/References/OpenApiParameterReference.cs index a4e3ea4de..a0c1a3783 100644 --- a/src/Microsoft.OpenApi/Models/References/OpenApiParameterReference.cs +++ b/src/Microsoft.OpenApi/Models/References/OpenApiParameterReference.cs @@ -36,51 +36,51 @@ private OpenApiParameterReference(OpenApiParameterReference parameter):base(para } /// - public string? Name { get => Target?.Name; } + public string? Name { get => GetFromTarget(static target => target.Name); } /// public string? Description { - get => string.IsNullOrEmpty(Reference.Description) ? Target?.Description : Reference.Description; + get => string.IsNullOrEmpty(Reference.Description) ? GetFromTarget(static target => target.Description) : Reference.Description; set => Reference.Description = value; } /// - public bool Required { get => Target?.Required ?? default; } + public bool Required { get => GetFromTarget(static target => target.Required); } /// - public bool Deprecated { get => Target?.Deprecated ?? default; } + public bool Deprecated { get => GetFromTarget(static target => target.Deprecated); } /// [Obsolete("Use of AllowEmptyValue is not recommended and it is likely to be removed in a later revision.")] - public bool AllowEmptyValue { get => Target?.AllowEmptyValue ?? default; } + public bool AllowEmptyValue { get => GetFromTarget(static target => target.AllowEmptyValue); } /// - public bool AllowReserved { get => Target?.AllowReserved ?? default; } + public bool AllowReserved { get => GetFromTarget(static target => target.AllowReserved); } /// - public IOpenApiSchema? Schema { get => Target?.Schema; } + public IOpenApiSchema? Schema { get => GetFromTarget(static target => target.Schema); } /// - public IDictionary? Examples { get => Target?.Examples; } + public IDictionary? Examples { get => GetFromTarget(static target => target.Examples); } /// - public JsonNode? Example { get => Target?.Example; } + public JsonNode? Example { get => GetFromTarget(static target => target.Example); } /// - public ParameterLocation? In { get => Target?.In; } + public ParameterLocation? In { get => GetFromTarget(static target => target.In); } /// - public ParameterStyle? Style { get => Target?.Style; } + public ParameterStyle? Style { get => GetFromTarget(static target => target.Style); } /// - public bool Explode { get => Target?.Explode ?? default; } + public bool Explode { get => GetFromTarget(static target => target.Explode); } /// - public IDictionary? Content { get => Target?.Content; } + public IDictionary? Content { get => GetFromTarget(static target => target.Content); } /// - public IDictionary? Extensions { get => Target?.Extensions; } + public IDictionary? Extensions { get => GetFromTarget(static target => target.Extensions); } /// public override IOpenApiParameter CopyReferenceAsTargetElementWithOverrides(IOpenApiParameter source) diff --git a/src/Microsoft.OpenApi/Models/References/OpenApiPathItemReference.cs b/src/Microsoft.OpenApi/Models/References/OpenApiPathItemReference.cs index 291c75308..9df1dd719 100644 --- a/src/Microsoft.OpenApi/Models/References/OpenApiPathItemReference.cs +++ b/src/Microsoft.OpenApi/Models/References/OpenApiPathItemReference.cs @@ -38,28 +38,28 @@ private OpenApiPathItemReference(OpenApiPathItemReference pathItem) : base(pathI /// public string? Summary { - get => string.IsNullOrEmpty(Reference.Summary) ? Target?.Summary : Reference.Summary; + get => string.IsNullOrEmpty(Reference.Summary) ? GetFromTarget(static target => target.Summary) : Reference.Summary; set => Reference.Summary = value; } /// public string? Description { - get => string.IsNullOrEmpty(Reference.Description) ? Target?.Description : Reference.Description; + get => string.IsNullOrEmpty(Reference.Description) ? GetFromTarget(static target => target.Description) : Reference.Description; set => Reference.Description = value; } /// - public Dictionary? Operations { get => Target?.Operations; } + public Dictionary? Operations { get => GetFromTarget(static target => target.Operations); } /// - public IList? Servers { get => Target?.Servers; } + public IList? Servers { get => GetFromTarget(static target => target.Servers); } /// - public IList? Parameters { get => Target?.Parameters; } + public IList? Parameters { get => GetFromTarget(static target => target.Parameters); } /// - public IDictionary? Extensions { get => Target?.Extensions; } + public IDictionary? Extensions { get => GetFromTarget(static target => target.Extensions); } /// public override IOpenApiPathItem CopyReferenceAsTargetElementWithOverrides(IOpenApiPathItem source) diff --git a/src/Microsoft.OpenApi/Models/References/OpenApiRequestBodyReference.cs b/src/Microsoft.OpenApi/Models/References/OpenApiRequestBodyReference.cs index f700f1bc9..66883e557 100644 --- a/src/Microsoft.OpenApi/Models/References/OpenApiRequestBodyReference.cs +++ b/src/Microsoft.OpenApi/Models/References/OpenApiRequestBodyReference.cs @@ -36,18 +36,18 @@ private OpenApiRequestBodyReference(OpenApiRequestBodyReference openApiRequestBo /// public string? Description { - get => string.IsNullOrEmpty(Reference.Description) ? Target?.Description : Reference.Description; + get => string.IsNullOrEmpty(Reference.Description) ? GetFromTarget(static target => target.Description) : Reference.Description; set => Reference.Description = value; } /// - public IDictionary? Content { get => Target?.Content; } + public IDictionary? Content { get => GetFromTarget(static target => target.Content); } /// - public bool Required { get => Target?.Required ?? false; } + public bool Required { get => GetFromTarget(static target => target.Required); } /// - public IDictionary? Extensions { get => Target?.Extensions; } + public IDictionary? Extensions { get => GetFromTarget(static target => target.Extensions); } /// public override IOpenApiRequestBody CopyReferenceAsTargetElementWithOverrides(IOpenApiRequestBody source) @@ -64,7 +64,7 @@ public override void SerializeAsV2(IOpenApiWriter writer) { if (writer.GetSettings().ShouldInlineReference(Reference)) { - return Target?.ConvertToBodyParameter(writer); + return GetFromTarget(target => target.ConvertToBodyParameter(writer)); } return Reference.Id is not null ? new OpenApiParameterReference(Reference.Id, Reference.HostDocument) : null; @@ -74,7 +74,7 @@ public override void SerializeAsV2(IOpenApiWriter writer) { if (writer.GetSettings().ShouldInlineReference(Reference)) { - return Target?.ConvertToFormDataParameters(writer); + return GetFromTarget(target => target.ConvertToFormDataParameters(writer)); } if (Content == null || !Content.Any()) diff --git a/src/Microsoft.OpenApi/Models/References/OpenApiResponseReference.cs b/src/Microsoft.OpenApi/Models/References/OpenApiResponseReference.cs index b99922e79..afd158d37 100644 --- a/src/Microsoft.OpenApi/Models/References/OpenApiResponseReference.cs +++ b/src/Microsoft.OpenApi/Models/References/OpenApiResponseReference.cs @@ -35,28 +35,28 @@ private OpenApiResponseReference(OpenApiResponseReference openApiResponseReferen /// public string? Summary { - get => string.IsNullOrEmpty(Reference.Summary) ? Target?.Summary : Reference.Summary; + get => string.IsNullOrEmpty(Reference.Summary) ? GetFromTarget(static target => target.Summary) : Reference.Summary; set => Reference.Summary = value; } /// public string? Description { - get => string.IsNullOrEmpty(Reference.Description) ? Target?.Description : Reference.Description; + get => string.IsNullOrEmpty(Reference.Description) ? GetFromTarget(static target => target.Description) : Reference.Description; set => Reference.Description = value; } /// - public IDictionary? Content { get => Target?.Content; } + public IDictionary? Content { get => GetFromTarget(static target => target.Content); } /// - public IDictionary? Headers { get => Target?.Headers; } + public IDictionary? Headers { get => GetFromTarget(static target => target.Headers); } /// - public IDictionary? Links { get => Target?.Links; } + public IDictionary? Links { get => GetFromTarget(static target => target.Links); } /// - public IDictionary? Extensions { get => Target?.Extensions; } + public IDictionary? Extensions { get => GetFromTarget(static target => target.Extensions); } /// public override IOpenApiResponse CopyReferenceAsTargetElementWithOverrides(IOpenApiResponse source) diff --git a/src/Microsoft.OpenApi/Models/References/OpenApiSchemaReference.cs b/src/Microsoft.OpenApi/Models/References/OpenApiSchemaReference.cs index 532060345..b5bbf8b6d 100644 --- a/src/Microsoft.OpenApi/Models/References/OpenApiSchemaReference.cs +++ b/src/Microsoft.OpenApi/Models/References/OpenApiSchemaReference.cs @@ -62,163 +62,163 @@ public override IOpenApiSchema? Target /// public string? Description { - get => string.IsNullOrEmpty(Reference.Description) ? Target?.Description : Reference.Description; + get => string.IsNullOrEmpty(Reference.Description) ? GetFromTarget(static target => target.Description) : Reference.Description; set => Reference.Description = value; } /// public string? Title { - get => string.IsNullOrEmpty(Reference.Title) ? Target?.Title : Reference.Title; + get => string.IsNullOrEmpty(Reference.Title) ? GetFromTarget(static target => target.Title) : Reference.Title; set => Reference.Title = value; } /// - public Uri? Schema { get => Reference.Schema ?? Target?.Schema; set => Reference.Schema = value; } + public Uri? Schema { get => Reference.Schema ?? GetFromTarget(static target => target.Schema); set => Reference.Schema = value; } /// - public string? Id { get => string.IsNullOrEmpty(Reference.SchemaId) ? Target?.Id : Reference.SchemaId; set => Reference.SchemaId = value; } + public string? Id { get => string.IsNullOrEmpty(Reference.SchemaId) ? GetFromTarget(static target => target.Id) : Reference.SchemaId; set => Reference.SchemaId = value; } /// - public string? Comment { get => string.IsNullOrEmpty(Reference.Comment) ? Target?.Comment : Reference.Comment; set => Reference.Comment = value; } + public string? Comment { get => string.IsNullOrEmpty(Reference.Comment) ? GetFromTarget(static target => target.Comment) : Reference.Comment; set => Reference.Comment = value; } /// - public IDictionary? Vocabulary { get => Reference.Vocabulary ?? Target?.Vocabulary; set => Reference.Vocabulary = value; } + public IDictionary? Vocabulary { get => Reference.Vocabulary ?? GetFromTarget(static target => target.Vocabulary); set => Reference.Vocabulary = value; } /// - public string? DynamicRef { get => string.IsNullOrEmpty(Reference.DynamicRef) ? Target?.DynamicRef : Reference.DynamicRef; set => Reference.DynamicRef = value; } + public string? DynamicRef { get => string.IsNullOrEmpty(Reference.DynamicRef) ? GetFromTarget(static target => target.DynamicRef) : Reference.DynamicRef; set => Reference.DynamicRef = value; } /// - public string? DynamicAnchor { get => string.IsNullOrEmpty(Reference.DynamicAnchor) ? Target?.DynamicAnchor : Reference.DynamicAnchor; set => Reference.DynamicAnchor = value; } + public string? DynamicAnchor { get => string.IsNullOrEmpty(Reference.DynamicAnchor) ? GetFromTarget(static target => target.DynamicAnchor) : Reference.DynamicAnchor; set => Reference.DynamicAnchor = value; } /// - public IDictionary? Definitions { get => Reference.Definitions ?? Target?.Definitions; set => Reference.Definitions = value; } + public IDictionary? Definitions { get => Reference.Definitions ?? GetFromTarget(static target => target.Definitions); set => Reference.Definitions = value; } /// - public string? Anchor { get => string.IsNullOrEmpty(Reference.Anchor) ? (Target as IOpenApiSchemaMissingProperties)?.Anchor : Reference.Anchor; set => Reference.Anchor = value; } + public string? Anchor { get => string.IsNullOrEmpty(Reference.Anchor) ? GetFromTarget(static target => (target as IOpenApiSchemaMissingProperties)?.Anchor) : Reference.Anchor; set => Reference.Anchor = value; } /// - public string? ExclusiveMaximum { get => string.IsNullOrEmpty(Reference.ExclusiveMaximum) ? Target?.ExclusiveMaximum : Reference.ExclusiveMaximum; set => Reference.ExclusiveMaximum = value; } + public string? ExclusiveMaximum { get => string.IsNullOrEmpty(Reference.ExclusiveMaximum) ? GetFromTarget(static target => target.ExclusiveMaximum) : Reference.ExclusiveMaximum; set => Reference.ExclusiveMaximum = value; } /// - public string? ExclusiveMinimum { get => string.IsNullOrEmpty(Reference.ExclusiveMinimum) ? Target?.ExclusiveMinimum : Reference.ExclusiveMinimum; set => Reference.ExclusiveMinimum = value; } + public string? ExclusiveMinimum { get => string.IsNullOrEmpty(Reference.ExclusiveMinimum) ? GetFromTarget(static target => target.ExclusiveMinimum) : Reference.ExclusiveMinimum; set => Reference.ExclusiveMinimum = value; } /// - public JsonSchemaType? Type { get => Reference.SchemaType ?? Target?.Type; set => Reference.SchemaType = value; } + public JsonSchemaType? Type { get => Reference.SchemaType ?? GetFromTarget(static target => target.Type); set => Reference.SchemaType = value; } /// - public string? Const { get => Reference.WasConstExplicitlySet ? Reference.Const : Target?.Const; set => Reference.Const = value; } + public string? Const { get => Reference.WasConstExplicitlySet ? Reference.Const : GetFromTarget(static target => target.Const); set => Reference.Const = value; } /// - public string? Format { get => string.IsNullOrEmpty(Reference.Format) ? Target?.Format : Reference.Format; set => Reference.Format = value; } + public string? Format { get => string.IsNullOrEmpty(Reference.Format) ? GetFromTarget(static target => target.Format) : Reference.Format; set => Reference.Format = value; } /// - public string? Maximum { get => string.IsNullOrEmpty(Reference.Maximum) ? Target?.Maximum : Reference.Maximum; set => Reference.Maximum = value; } + public string? Maximum { get => string.IsNullOrEmpty(Reference.Maximum) ? GetFromTarget(static target => target.Maximum) : Reference.Maximum; set => Reference.Maximum = value; } /// - public string? Minimum { get => string.IsNullOrEmpty(Reference.Minimum) ? Target?.Minimum : Reference.Minimum; set => Reference.Minimum = value; } + public string? Minimum { get => string.IsNullOrEmpty(Reference.Minimum) ? GetFromTarget(static target => target.Minimum) : Reference.Minimum; set => Reference.Minimum = value; } /// - public int? MaxLength { get => Reference.MaxLength ?? Target?.MaxLength; set => Reference.MaxLength = value; } + public int? MaxLength { get => Reference.MaxLength ?? GetFromTarget(static target => target.MaxLength); set => Reference.MaxLength = value; } /// - public int? MinLength { get => Reference.MinLength ?? Target?.MinLength; set => Reference.MinLength = value; } + public int? MinLength { get => Reference.MinLength ?? GetFromTarget(static target => target.MinLength); set => Reference.MinLength = value; } /// - public string? Pattern { get => string.IsNullOrEmpty(Reference.Pattern) ? Target?.Pattern : Reference.Pattern; set => Reference.Pattern = value; } + public string? Pattern { get => string.IsNullOrEmpty(Reference.Pattern) ? GetFromTarget(static target => target.Pattern) : Reference.Pattern; set => Reference.Pattern = value; } /// - public decimal? MultipleOf { get => Reference.MultipleOf ?? Target?.MultipleOf; set => Reference.MultipleOf = value; } + public decimal? MultipleOf { get => Reference.MultipleOf ?? GetFromTarget(static target => target.MultipleOf); set => Reference.MultipleOf = value; } /// public JsonNode? Default { - get => Reference.Default ?? Target?.Default; + get => Reference.Default ?? GetFromTarget(static target => target.Default); set => Reference.Default = value; } /// public bool ReadOnly { - get => Reference.ReadOnly ?? Target?.ReadOnly ?? false; + get => Reference.ReadOnly ?? GetFromTarget(static target => target.ReadOnly); set => Reference.ReadOnly = value; } /// public bool WriteOnly { - get => Reference.WriteOnly ?? Target?.WriteOnly ?? false; + get => Reference.WriteOnly ?? GetFromTarget(static target => target.WriteOnly); set => Reference.WriteOnly = value; } /// - public IList? AllOf { get => Reference.AllOf ?? Target?.AllOf; set => Reference.AllOf = value; } + public IList? AllOf { get => Reference.AllOf ?? GetFromTarget(static target => target.AllOf); set => Reference.AllOf = value; } /// - public IList? OneOf { get => Reference.OneOf ?? Target?.OneOf; set => Reference.OneOf = value; } + public IList? OneOf { get => Reference.OneOf ?? GetFromTarget(static target => target.OneOf); set => Reference.OneOf = value; } /// - public IList? AnyOf { get => Reference.AnyOf ?? Target?.AnyOf; set => Reference.AnyOf = value; } + public IList? AnyOf { get => Reference.AnyOf ?? GetFromTarget(static target => target.AnyOf); set => Reference.AnyOf = value; } /// - public IOpenApiSchema? Not { get => Reference.Not ?? Target?.Not; set => Reference.Not = value; } + public IOpenApiSchema? Not { get => Reference.Not ?? GetFromTarget(static target => target.Not); set => Reference.Not = value; } /// - public ISet? Required { get => Reference.Required ?? Target?.Required; set => Reference.Required = value; } + public ISet? Required { get => Reference.Required ?? GetFromTarget(static target => target.Required); set => Reference.Required = value; } /// - public IOpenApiSchema? Items { get => Reference.Items ?? Target?.Items; set => Reference.Items = value; } + public IOpenApiSchema? Items { get => Reference.Items ?? GetFromTarget(static target => target.Items); set => Reference.Items = value; } /// - public int? MaxItems { get => Reference.MaxItems ?? Target?.MaxItems; set => Reference.MaxItems = value; } + public int? MaxItems { get => Reference.MaxItems ?? GetFromTarget(static target => target.MaxItems); set => Reference.MaxItems = value; } /// - public int? MinItems { get => Reference.MinItems ?? Target?.MinItems; set => Reference.MinItems = value; } + public int? MinItems { get => Reference.MinItems ?? GetFromTarget(static target => target.MinItems); set => Reference.MinItems = value; } /// - public bool? UniqueItems { get => Reference.UniqueItems ?? Target?.UniqueItems; set => Reference.UniqueItems = value; } + public bool? UniqueItems { get => Reference.UniqueItems ?? GetFromTarget(static target => target.UniqueItems); set => Reference.UniqueItems = value; } /// - public IOpenApiSchema? Contains { get => Reference.Contains ?? (Target as IOpenApiSchemaMissingProperties)?.Contains; set => Reference.Contains = value; } + public IOpenApiSchema? Contains { get => Reference.Contains ?? GetFromTarget(static target => (target as IOpenApiSchemaMissingProperties)?.Contains); set => Reference.Contains = value; } /// - public uint? MaxContains { get => Reference.MaxContains ?? (Target as IOpenApiSchemaMissingProperties)?.MaxContains; set => Reference.MaxContains = value; } + public uint? MaxContains { get => Reference.MaxContains ?? GetFromTarget(static target => (target as IOpenApiSchemaMissingProperties)?.MaxContains); set => Reference.MaxContains = value; } /// - public uint? MinContains { get => Reference.MinContains ?? (Target as IOpenApiSchemaMissingProperties)?.MinContains; set => Reference.MinContains = value; } + public uint? MinContains { get => Reference.MinContains ?? GetFromTarget(static target => (target as IOpenApiSchemaMissingProperties)?.MinContains); set => Reference.MinContains = value; } /// - public IDictionary? Properties { get => Reference.Properties ?? Target?.Properties; set => Reference.Properties = value; } + public IDictionary? Properties { get => Reference.Properties ?? GetFromTarget(static target => target.Properties); set => Reference.Properties = value; } /// - public IDictionary? PatternProperties { get => Reference.PatternProperties ?? Target?.PatternProperties; set => Reference.PatternProperties = value; } + public IDictionary? PatternProperties { get => Reference.PatternProperties ?? GetFromTarget(static target => target.PatternProperties); set => Reference.PatternProperties = value; } /// - public int? MaxProperties { get => Reference.MaxProperties ?? Target?.MaxProperties; set => Reference.MaxProperties = value; } + public int? MaxProperties { get => Reference.MaxProperties ?? GetFromTarget(static target => target.MaxProperties); set => Reference.MaxProperties = value; } /// - public int? MinProperties { get => Reference.MinProperties ?? Target?.MinProperties; set => Reference.MinProperties = value; } + public int? MinProperties { get => Reference.MinProperties ?? GetFromTarget(static target => target.MinProperties); set => Reference.MinProperties = value; } /// - public bool AdditionalPropertiesAllowed { get => Reference.AdditionalPropertiesAllowed ?? Target?.AdditionalPropertiesAllowed ?? true; set => Reference.AdditionalPropertiesAllowed = value; } + public bool AdditionalPropertiesAllowed { get => Reference.AdditionalPropertiesAllowed ?? GetFromTarget(static target => (bool?)target.AdditionalPropertiesAllowed) ?? true; set => Reference.AdditionalPropertiesAllowed = value; } /// - public IOpenApiSchema? AdditionalProperties { get => Reference.AdditionalProperties ?? Target?.AdditionalProperties; set => Reference.AdditionalProperties = value; } + public IOpenApiSchema? AdditionalProperties { get => Reference.AdditionalProperties ?? GetFromTarget(static target => target.AdditionalProperties); set => Reference.AdditionalProperties = value; } /// - public OpenApiDiscriminator? Discriminator { get => Reference.Discriminator ?? Target?.Discriminator; set => Reference.Discriminator = value; } + public OpenApiDiscriminator? Discriminator { get => Reference.Discriminator ?? GetFromTarget(static target => target.Discriminator); set => Reference.Discriminator = value; } /// [Obsolete("Use Examples instead.")] - public JsonNode? Example { get => Reference.Example ?? Target?.Example; set => Reference.Example = value; } + public JsonNode? Example { get => Reference.Example ?? GetFromTarget(static target => target.Example); set => Reference.Example = value; } /// public IList? Examples { - get => Reference.Examples ?? Target?.Examples; + get => Reference.Examples ?? GetFromTarget(static target => target.Examples); set => Reference.Examples = value; } /// - public IList? Enum { get => Reference.Enum ?? Target?.Enum; set => Reference.Enum = value; } + public IList? Enum { get => Reference.Enum ?? GetFromTarget(static target => target.Enum); set => Reference.Enum = value; } /// - public bool UnevaluatedProperties { get => Reference.UnevaluatedProperties ?? Target?.UnevaluatedProperties ?? true; set => Reference.UnevaluatedProperties = value; } + public bool UnevaluatedProperties { get => Reference.UnevaluatedProperties ?? GetFromTarget(static target => (bool?)target.UnevaluatedProperties) ?? true; set => Reference.UnevaluatedProperties = value; } /// - public IOpenApiSchema? UnevaluatedPropertiesSchema { get => Reference.UnevaluatedPropertiesSchema ?? (Target as IOpenApiSchemaMissingProperties)?.UnevaluatedPropertiesSchema; set => Reference.UnevaluatedPropertiesSchema = value; } + public IOpenApiSchema? UnevaluatedPropertiesSchema { get => Reference.UnevaluatedPropertiesSchema ?? GetFromTarget(static target => (target as IOpenApiSchemaMissingProperties)?.UnevaluatedPropertiesSchema); set => Reference.UnevaluatedPropertiesSchema = value; } /// - public string? ContentEncoding { get => string.IsNullOrEmpty(Reference.ContentEncoding) ? (Target as IOpenApiSchemaMissingProperties)?.ContentEncoding : Reference.ContentEncoding; set => Reference.ContentEncoding = value; } + public string? ContentEncoding { get => string.IsNullOrEmpty(Reference.ContentEncoding) ? GetFromTarget(static target => (target as IOpenApiSchemaMissingProperties)?.ContentEncoding) : Reference.ContentEncoding; set => Reference.ContentEncoding = value; } /// - public string? ContentMediaType { get => string.IsNullOrEmpty(Reference.ContentMediaType) ? (Target as IOpenApiSchemaMissingProperties)?.ContentMediaType : Reference.ContentMediaType; set => Reference.ContentMediaType = value; } + public string? ContentMediaType { get => string.IsNullOrEmpty(Reference.ContentMediaType) ? GetFromTarget(static target => (target as IOpenApiSchemaMissingProperties)?.ContentMediaType) : Reference.ContentMediaType; set => Reference.ContentMediaType = value; } /// - public IOpenApiSchema? ContentSchema { get => Reference.ContentSchema ?? (Target as IOpenApiSchemaMissingProperties)?.ContentSchema; set => Reference.ContentSchema = value; } + public IOpenApiSchema? ContentSchema { get => Reference.ContentSchema ?? GetFromTarget(static target => (target as IOpenApiSchemaMissingProperties)?.ContentSchema); set => Reference.ContentSchema = value; } /// - public IOpenApiSchema? PropertyNames { get => Reference.PropertyNames ?? (Target as IOpenApiSchemaMissingProperties)?.PropertyNames; set => Reference.PropertyNames = value; } + public IOpenApiSchema? PropertyNames { get => Reference.PropertyNames ?? GetFromTarget(static target => (target as IOpenApiSchemaMissingProperties)?.PropertyNames); set => Reference.PropertyNames = value; } /// - public IDictionary? DependentSchemas { get => Reference.DependentSchemas ?? (Target as IOpenApiSchemaMissingProperties)?.DependentSchemas; set => Reference.DependentSchemas = value; } + public IDictionary? DependentSchemas { get => Reference.DependentSchemas ?? GetFromTarget(static target => (target as IOpenApiSchemaMissingProperties)?.DependentSchemas); set => Reference.DependentSchemas = value; } /// - public IOpenApiSchema? If { get => Reference.If ?? (Target as IOpenApiSchemaMissingProperties)?.If; set => Reference.If = value; } + public IOpenApiSchema? If { get => Reference.If ?? GetFromTarget(static target => (target as IOpenApiSchemaMissingProperties)?.If); set => Reference.If = value; } /// - public IOpenApiSchema? Then { get => Reference.Then ?? (Target as IOpenApiSchemaMissingProperties)?.Then; set => Reference.Then = value; } + public IOpenApiSchema? Then { get => Reference.Then ?? GetFromTarget(static target => (target as IOpenApiSchemaMissingProperties)?.Then); set => Reference.Then = value; } /// - public IOpenApiSchema? Else { get => Reference.Else ?? (Target as IOpenApiSchemaMissingProperties)?.Else; set => Reference.Else = value; } + public IOpenApiSchema? Else { get => Reference.Else ?? GetFromTarget(static target => (target as IOpenApiSchemaMissingProperties)?.Else); set => Reference.Else = value; } /// - public OpenApiExternalDocs? ExternalDocs { get => Reference.ExternalDocs ?? Target?.ExternalDocs; set => Reference.ExternalDocs = value; } + public OpenApiExternalDocs? ExternalDocs { get => Reference.ExternalDocs ?? GetFromTarget(static target => target.ExternalDocs); set => Reference.ExternalDocs = value; } /// public bool Deprecated { - get => Reference.Deprecated ?? Target?.Deprecated ?? false; + get => Reference.Deprecated ?? GetFromTarget(static target => target.Deprecated); set => Reference.Deprecated = value; } /// - public OpenApiXml? Xml { get => Reference.Xml ?? Target?.Xml; set => Reference.Xml = value; } + public OpenApiXml? Xml { get => Reference.Xml ?? GetFromTarget(static target => target.Xml); set => Reference.Xml = value; } /// public IDictionary? Extensions { - get => Reference.Extensions ?? Target?.Extensions; + get => Reference.Extensions ?? GetFromTarget(static target => target.Extensions); set => Reference.Extensions = value; } /// - public IDictionary? UnrecognizedKeywords { get => Reference.UnrecognizedKeywords ?? Target?.UnrecognizedKeywords; set => Reference.UnrecognizedKeywords = value; } + public IDictionary? UnrecognizedKeywords { get => Reference.UnrecognizedKeywords ?? GetFromTarget(static target => target.UnrecognizedKeywords); set => Reference.UnrecognizedKeywords = value; } /// - public IDictionary>? DependentRequired { get => Reference.DependentRequired ?? Target?.DependentRequired; set => Reference.DependentRequired = value; } + public IDictionary>? DependentRequired { get => Reference.DependentRequired ?? GetFromTarget(static target => target.DependentRequired); set => Reference.DependentRequired = value; } /// public override void SerializeAsV31(IOpenApiWriter writer) diff --git a/src/Microsoft.OpenApi/Models/References/OpenApiSecuritySchemeReference.cs b/src/Microsoft.OpenApi/Models/References/OpenApiSecuritySchemeReference.cs index 8fdd0b73a..7ec7bee31 100644 --- a/src/Microsoft.OpenApi/Models/References/OpenApiSecuritySchemeReference.cs +++ b/src/Microsoft.OpenApi/Models/References/OpenApiSecuritySchemeReference.cs @@ -32,39 +32,39 @@ private OpenApiSecuritySchemeReference(OpenApiSecuritySchemeReference openApiSec /// public string? Description { - get => string.IsNullOrEmpty(Reference.Description) ? Target?.Description : Reference.Description; + get => string.IsNullOrEmpty(Reference.Description) ? GetFromTarget(static target => target.Description) : Reference.Description; set => Reference.Description = value; } /// - public string? Name { get => Target?.Name; } + public string? Name { get => GetFromTarget(static target => target.Name); } /// - public ParameterLocation? In { get => Target?.In; } + public ParameterLocation? In { get => GetFromTarget(static target => target.In); } /// - public string? Scheme { get => Target?.Scheme; } + public string? Scheme { get => GetFromTarget(static target => target.Scheme); } /// - public string? BearerFormat { get => Target?.BearerFormat; } + public string? BearerFormat { get => GetFromTarget(static target => target.BearerFormat); } /// - public OpenApiOAuthFlows? Flows { get => Target?.Flows; } + public OpenApiOAuthFlows? Flows { get => GetFromTarget(static target => target.Flows); } /// - public Uri? OpenIdConnectUrl { get => Target?.OpenIdConnectUrl; } + public Uri? OpenIdConnectUrl { get => GetFromTarget(static target => target.OpenIdConnectUrl); } /// - public Uri? OAuth2MetadataUrl { get => Target is IOAuth2MetadataProvider oauth2MetadataProvider ? oauth2MetadataProvider.OAuth2MetadataUrl : null; } + public Uri? OAuth2MetadataUrl { get => GetFromTarget(static target => target is IOAuth2MetadataProvider provider ? provider.OAuth2MetadataUrl : null); } /// - public IDictionary? Extensions { get => Target?.Extensions; } + public IDictionary? Extensions { get => GetFromTarget(static target => target.Extensions); } /// - public SecuritySchemeType? Type { get => Target?.Type; } + public SecuritySchemeType? Type { get => GetFromTarget(static target => target.Type); } /// - public bool Deprecated { get => Target?.Deprecated ?? default; } + public bool Deprecated { get => GetFromTarget(static target => target.Deprecated); } /// public override IOpenApiSecurityScheme CopyReferenceAsTargetElementWithOverrides(IOpenApiSecurityScheme source) diff --git a/src/Microsoft.OpenApi/Models/References/OpenApiTagReference.cs b/src/Microsoft.OpenApi/Models/References/OpenApiTagReference.cs index 78b92638c..84f97cc24 100644 --- a/src/Microsoft.OpenApi/Models/References/OpenApiTagReference.cs +++ b/src/Microsoft.OpenApi/Models/References/OpenApiTagReference.cs @@ -48,37 +48,40 @@ private OpenApiTagReference(OpenApiTagReference openApiTagReference) : base(open /// public string? Description { - get => Target?.Description; + get => GetFromTarget(static target => target.Description); } /// - public OpenApiExternalDocs? ExternalDocs { get => Target?.ExternalDocs; } + public OpenApiExternalDocs? ExternalDocs { get => GetFromTarget(static target => target.ExternalDocs); } /// - public IDictionary? Extensions { get => Target?.Extensions; } + public IDictionary? Extensions { get => GetFromTarget(static target => target.Extensions); } /// - public string? Name { get => Target?.Name ?? Reference?.Id; } + public string? Name { get => GetFromTarget(static target => target.Name) ?? Reference?.Id; } /// - public string? Summary => Target?.Summary; + public string? Summary => GetFromTarget(static target => target.Summary); /// public OpenApiTagReference? Parent { get { - if (Target is OpenApiTagReference targetRef && (Reference.Id?.Equals(targetRef.Reference.Id, StringComparison.Ordinal) ?? false)) + return GetFromTarget(target => { - return null; - } + if (target is OpenApiTagReference targetRef && (Reference.Id?.Equals(targetRef.Reference.Id, StringComparison.Ordinal) ?? false)) + { + return null; + } - return Target?.Parent; + return target.Parent; + }); } } /// - public string? Kind => Target?.Kind; + public string? Kind => GetFromTarget(static target => target.Kind); /// public override IOpenApiTag CopyReferenceAsTargetElementWithOverrides(IOpenApiTag source) diff --git a/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt b/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt index 7dc5c5811..cc7b76897 100644 --- a/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt +++ b/src/Microsoft.OpenApi/PublicAPI.Unshipped.txt @@ -1 +1,2 @@ #nullable enable +Microsoft.OpenApi.OpenApiDocument.GetOperationById(string! operationId) -> Microsoft.OpenApi.OpenApiOperation? diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiComponentsRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiComponentsRules.cs index e3c6e1b4f..7473bce91 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiComponentsRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiComponentsRules.cs @@ -11,12 +11,19 @@ namespace Microsoft.OpenApi /// The validation rules for . /// [OpenApiRule] - public static class OpenApiComponentsRules + public static partial class OpenApiComponentsRules { /// - /// The key regex. + /// The key regex pattern. /// - internal static readonly Regex KeyRegex = new(@"^[a-zA-Z0-9\.\-_]+$", RegexOptions.None, TimeSpan.FromMilliseconds(100)); + internal const string KeyPattern = @"^[a-zA-Z0-9\.\-_]+$"; + +#if NET8_0_OR_GREATER + [GeneratedRegex(KeyPattern, RegexOptions.None, matchTimeoutMilliseconds: 100)] + private static partial Regex KeyRegex(); +#else + private static readonly Regex KeyRegex = new(KeyPattern, RegexOptions.None, TimeSpan.FromMilliseconds(100)); +#endif /// /// All the fixed fields declared above are objects @@ -54,12 +61,18 @@ private static void ValidateKeys(IValidationContext context, IEnumerable foreach (var key in keys) { - if (!KeyRegex.IsMatch(key)) +#if NET8_0_OR_GREATER + var isValidKey = KeyRegex().IsMatch(key); +#else + var isValidKey = KeyRegex.IsMatch(key); +#endif + if (!isValidKey) { context.CreateError(nameof(KeyMustBeRegularExpression), - string.Format(SRResource.Validation_ComponentsKeyMustMatchRegularExpr, key, component, KeyRegex.ToString())); + string.Format(SRResource.Validation_ComponentsKeyMustMatchRegularExpr, key, component, KeyPattern)); } } } + } } diff --git a/src/Microsoft.OpenApi/Validations/Rules/OpenApiSchemaRules.cs b/src/Microsoft.OpenApi/Validations/Rules/OpenApiSchemaRules.cs index 4f2a122a9..eff687253 100644 --- a/src/Microsoft.OpenApi/Validations/Rules/OpenApiSchemaRules.cs +++ b/src/Microsoft.OpenApi/Validations/Rules/OpenApiSchemaRules.cs @@ -8,6 +8,7 @@ namespace Microsoft.OpenApi using System; using System.ComponentModel; using System.Linq; + using System.Runtime.CompilerServices; /// /// The validation rules for . @@ -73,32 +74,17 @@ public static class OpenApiSchemaRules [Browsable(false)] public static bool ValidateChildSchemaAgainstDiscriminator(IOpenApiSchema schema, string? discriminatorName) { - if (discriminatorName is not null) + if (discriminatorName is null) { - if (schema.Required is null || !schema.Required.Contains(discriminatorName)) - { - // recursively check nested schema.OneOf, schema.AnyOf or schema.AllOf and their required fields for the discriminator - if (schema.OneOf is { Count: > 0}) - { - return TraverseSchemaElements(discriminatorName, schema.OneOf); - } - if (schema.AnyOf is { Count: > 0}) - { - return TraverseSchemaElements(discriminatorName, schema.AnyOf); - } - if (schema.AllOf is { Count: > 0}) - { - return TraverseSchemaElements(discriminatorName, schema.AllOf); - } - } - else - { - return true; - } return false; - } + } - return false; + if (schema.Required?.Contains(discriminatorName) == true) + { + return true; + } + + return TraverseSchemaElementsIterative(discriminatorName, GetSchemaCombinators(schema)); } /// @@ -111,24 +97,84 @@ public static bool ValidateChildSchemaAgainstDiscriminator(IOpenApiSchema schema [Obsolete("This method will be made private in future versions.")] [Browsable(false)] public static bool TraverseSchemaElements(string discriminatorName, IList? childSchema) + => TraverseSchemaElementsIterative(discriminatorName, childSchema); + + private static bool TraverseSchemaElementsIterative(string discriminatorName, IEnumerable? childSchemas) { - if (childSchema is null) + if (childSchemas is null) { return false; } - foreach (var childItem in childSchema) + + var schemasToVisit = new Queue(); + var visitedSchemas = new HashSet(SchemaReferenceEqualityComparer.Instance); + + EnqueueSchemas(schemasToVisit, childSchemas); + + while (schemasToVisit.Count > 0) { - if ((!childItem.Properties?.ContainsKey(discriminatorName) ?? false) && - (!childItem.Required?.Contains(discriminatorName) ?? false)) + var childItem = schemasToVisit.Dequeue(); + if (!visitedSchemas.Add(childItem)) { - return ValidateChildSchemaAgainstDiscriminator(childItem, discriminatorName); + continue; } - else + + if (childItem.Properties?.ContainsKey(discriminatorName) == true || + childItem.Required?.Contains(discriminatorName) == true) { return true; } + + EnqueueSchemas(schemasToVisit, GetSchemaCombinators(childItem)); } + return false; } + + private static IEnumerable GetSchemaCombinators(IOpenApiSchema schema) + { + if (schema.OneOf is { Count: > 0 } oneOf) + { + foreach (var childSchema in oneOf) + { + yield return childSchema; + } + } + + if (schema.AnyOf is { Count: > 0 } anyOf) + { + foreach (var childSchema in anyOf) + { + yield return childSchema; + } + } + + if (schema.AllOf is { Count: > 0 } allOf) + { + foreach (var childSchema in allOf) + { + yield return childSchema; + } + } + } + + private static void EnqueueSchemas(Queue schemasToVisit, IEnumerable childSchemas) + { + foreach (var childSchema in childSchemas.Where(childSchema => childSchema is not null)) + { + schemasToVisit.Enqueue(childSchema); + } + } + + private sealed class SchemaReferenceEqualityComparer : IEqualityComparer + { + internal static SchemaReferenceEqualityComparer Instance { get; } = new(); + + public bool Equals(IOpenApiSchema? x, IOpenApiSchema? y) + => ReferenceEquals(x, y); + + public int GetHashCode(IOpenApiSchema obj) + => RuntimeHelpers.GetHashCode(obj); + } } } diff --git a/test/Microsoft.OpenApi.Hidi.Tests/Microsoft.OpenApi.Hidi.Tests.csproj b/test/Microsoft.OpenApi.Hidi.Tests/Microsoft.OpenApi.Hidi.Tests.csproj index 3becd1bd7..c5adf7adc 100644 --- a/test/Microsoft.OpenApi.Hidi.Tests/Microsoft.OpenApi.Hidi.Tests.csproj +++ b/test/Microsoft.OpenApi.Hidi.Tests/Microsoft.OpenApi.Hidi.Tests.csproj @@ -10,15 +10,16 @@ CA2007 true ..\..\src\Microsoft.OpenApi.snk + true - - - - - - + + + + + + diff --git a/test/Microsoft.OpenApi.Hidi.Tests/Services/OpenApiFilterServiceTests.cs b/test/Microsoft.OpenApi.Hidi.Tests/Services/OpenApiFilterServiceTests.cs index 8f8aa0c8e..6883bab5a 100644 --- a/test/Microsoft.OpenApi.Hidi.Tests/Services/OpenApiFilterServiceTests.cs +++ b/test/Microsoft.OpenApi.Hidi.Tests/Services/OpenApiFilterServiceTests.cs @@ -51,7 +51,7 @@ public void ReturnFilteredOpenApiDocumentBasedOnOperationIdsAndTags(string? oper public void ReturnFilteredOpenApiDocumentBasedOnPostmanCollection() { // Arrange - var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "UtilityFiles", "postmanCollection_ver2.json"); + var filePath = Path.Join(AppDomain.CurrentDomain.BaseDirectory, "UtilityFiles", "postmanCollection_ver2.json"); var fileInput = new FileInfo(filePath); var stream = fileInput.OpenRead(); @@ -158,7 +158,7 @@ public void CreateFilteredDocumentUsingPredicateFromRequestUrl() public void ShouldParseNestedPostmanCollection() { // Arrange - var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "UtilityFiles", "postmanCollection_ver3.json"); + var filePath = Path.Join(AppDomain.CurrentDomain.BaseDirectory, "UtilityFiles", "postmanCollection_ver3.json"); var fileInput = new FileInfo(filePath); var stream = fileInput.OpenRead(); @@ -175,7 +175,7 @@ public void ShouldParseNestedPostmanCollection() public void ThrowsExceptionWhenUrlsInCollectionAreMissingFromSourceDocument() { // Arrange - var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "UtilityFiles", "postmanCollection_ver1.json"); + var filePath = Path.Join(AppDomain.CurrentDomain.BaseDirectory, "UtilityFiles", "postmanCollection_ver1.json"); var fileInput = new FileInfo(filePath); var stream = fileInput.OpenRead(); @@ -192,7 +192,7 @@ public void ThrowsExceptionWhenUrlsInCollectionAreMissingFromSourceDocument() public void ContinueProcessingWhenUrlsInCollectionAreMissingFromSourceDocument() { // Arrange - var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "UtilityFiles", "postmanCollection_ver4.json"); + var filePath = Path.Join(AppDomain.CurrentDomain.BaseDirectory, "UtilityFiles", "postmanCollection_ver4.json"); var fileInput = new FileInfo(filePath); var stream = fileInput.OpenRead(); @@ -291,14 +291,14 @@ public void CreatePredicateMatchesAbsoluteUrlsWhenSourceHasNoServers() public async Task CopiesOverAllReferencedComponentsToTheSubsetDocumentCorrectly() { // Arrange - var filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "UtilityFiles", "docWithReusableHeadersAndExamples.yaml"); + var filePath = Path.Join(AppDomain.CurrentDomain.BaseDirectory, "UtilityFiles", "docWithReusableHeadersAndExamples.yaml"); var operationIds = "getItems"; // Act using var stream = File.OpenRead(filePath); var settings = new OpenApiReaderSettings(); settings.AddYamlReader(); - var doc = (await OpenApiDocument.LoadAsync(stream, "yaml", settings)).Document; + var doc = (await OpenApiDocument.LoadAsync(stream, "yaml", settings, TestContext.Current.CancellationToken)).Document; // validated the tags are read as references var openApiOperationTags = doc?.Paths["/items"].Operations?[HttpMethod.Get].Tags?.ToArray(); @@ -344,7 +344,7 @@ public async Task CopiesOverAllReferencedComponentsToTheSubsetDocumentCorrectly( } }; subsetOpenApiDocument.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); var result = outputStringWriter.ToString(); Assert.NotEmpty(result); } diff --git a/test/Microsoft.OpenApi.Hidi.Tests/Services/OpenApiServiceTests.cs b/test/Microsoft.OpenApi.Hidi.Tests/Services/OpenApiServiceTests.cs index 6342f6703..981a00558 100644 --- a/test/Microsoft.OpenApi.Hidi.Tests/Services/OpenApiServiceTests.cs +++ b/test/Microsoft.OpenApi.Hidi.Tests/Services/OpenApiServiceTests.cs @@ -134,13 +134,13 @@ public async Task ShowCommandGeneratesMermaidMarkdownFileWithMermaidDiagramAsync // create a dummy ILogger instance for testing var options = new HidiOptions { - OpenApi = Path.Combine("UtilityFiles", "SampleOpenApi.yml"), - Output = new("sample.md") + OpenApi = Path.Join("UtilityFiles", "SampleOpenApi.yml"), + Output = new($"{nameof(ShowCommandGeneratesMermaidMarkdownFileWithMermaidDiagramAsync)}.md") }; - await OpenApiService.ShowOpenApiDocumentAsync(options, _logger); + await OpenApiService.ShowOpenApiDocumentAsync(options, _logger, TestContext.Current.CancellationToken); - var output = await File.ReadAllTextAsync(options.Output.FullName); + var output = await File.ReadAllTextAsync(options.Output.FullName, TestContext.Current.CancellationToken); Assert.Contains("graph LR", output, StringComparison.Ordinal); } @@ -149,9 +149,9 @@ public async Task ShowCommandGeneratesMermaidHtmlFileWithMermaidDiagramAsync() { var options = new HidiOptions { - OpenApi = Path.Combine("UtilityFiles", "SampleOpenApi.yml") + OpenApi = Path.Join("UtilityFiles", "SampleOpenApi.yml") }; - var filePath = await OpenApiService.ShowOpenApiDocumentAsync(options, _logger); + var filePath = await OpenApiService.ShowOpenApiDocumentAsync(options, _logger, TestContext.Current.CancellationToken); Assert.True(File.Exists(filePath)); } @@ -159,28 +159,28 @@ public async Task ShowCommandGeneratesMermaidHtmlFileWithMermaidDiagramAsync() public Task ThrowIfOpenApiUrlIsNotProvidedWhenValidatingAsync() { return Assert.ThrowsAsync(() => - OpenApiService.ValidateOpenApiDocumentAsync("", _logger)); + OpenApiService.ValidateOpenApiDocumentAsync("", _logger, TestContext.Current.CancellationToken)); } [Fact] public Task ThrowIfURLIsNotResolvableWhenValidatingAsync() { return Assert.ThrowsAsync(() => - OpenApiService.ValidateOpenApiDocumentAsync("https://example.org926F4F21-88E7-4DC5-BF88-6C529BB77844/itdoesnmatter", _logger)); + OpenApiService.ValidateOpenApiDocumentAsync("https://example.org926F4F21-88E7-4DC5-BF88-6C529BB77844/itdoesnmatter", _logger, TestContext.Current.CancellationToken)); } [Fact] public Task ThrowIfFileDoesNotExistWhenValidatingAsync() { return Assert.ThrowsAsync(() => - OpenApiService.ValidateOpenApiDocumentAsync("aFileThatBetterNotExist.fake", _logger)); + OpenApiService.ValidateOpenApiDocumentAsync("aFileThatBetterNotExist.fake", _logger, TestContext.Current.CancellationToken)); } [Fact] public async Task ValidateCommandProcessesOpenApiAsync() { // create a dummy ILogger instance for testing - await OpenApiService.ValidateOpenApiDocumentAsync(Path.Combine("UtilityFiles", "SampleOpenApi.yml"), _logger); + await OpenApiService.ValidateOpenApiDocumentAsync(Path.Join("UtilityFiles", "SampleOpenApi.yml"), _logger, TestContext.Current.CancellationToken); Assert.True(true); } @@ -188,7 +188,7 @@ public async Task ValidateCommandProcessesOpenApiAsync() [Fact] public async Task ValidFileReturnsTrueAsync() { - var isValid = await OpenApiService.ValidateOpenApiDocumentAsync(Path.Combine("UtilityFiles", "SampleOpenApi.yml"), _logger); + var isValid = await OpenApiService.ValidateOpenApiDocumentAsync(Path.Join("UtilityFiles", "SampleOpenApi.yml"), _logger, TestContext.Current.CancellationToken); Assert.True(isValid); } @@ -196,7 +196,7 @@ public async Task ValidFileReturnsTrueAsync() [Fact] public async Task InvalidFileReturnsFalseAsync() { - var isValid = await OpenApiService.ValidateOpenApiDocumentAsync(Path.Combine("UtilityFiles", "InvalidSampleOpenApi.yml"), _logger); + var isValid = await OpenApiService.ValidateOpenApiDocumentAsync(Path.Join("UtilityFiles", "InvalidSampleOpenApi.yml"), _logger, TestContext.Current.CancellationToken); Assert.False(isValid); } @@ -206,7 +206,7 @@ public async Task CancellingValidationReturnsNullAsync() { using var cts = new CancellationTokenSource(); await cts.CancelAsync(); - var isValid = await OpenApiService.ValidateOpenApiDocumentAsync(Path.Combine("UtilityFiles", "SampleOpenApi.yml"), _logger, cts.Token); + var isValid = await OpenApiService.ValidateOpenApiDocumentAsync(Path.Join("UtilityFiles", "SampleOpenApi.yml"), _logger, cts.Token); Assert.Null(isValid); } @@ -216,17 +216,17 @@ public async Task TransformCommandConvertsOpenApiAsync() { var options = new HidiOptions { - OpenApi = Path.Combine("UtilityFiles", "SampleOpenApi.yml"), - Output = new("sample.json"), + OpenApi = Path.Join("UtilityFiles", "SampleOpenApi.yml"), + Output = new($"{nameof(TransformCommandConvertsOpenApiAsync)}.json"), CleanOutput = true, TerseOutput = false, InlineLocal = false, InlineExternal = false, }; // create a dummy ILogger instance for testing - await OpenApiService.TransformOpenApiDocumentAsync(options, _logger); + await OpenApiService.TransformOpenApiDocumentAsync(options, _logger, TestContext.Current.CancellationToken); - var output = await File.ReadAllTextAsync("sample.json"); + var output = await File.ReadAllTextAsync(options.Output.FullName, TestContext.Current.CancellationToken); Assert.NotEmpty(output); } @@ -236,16 +236,17 @@ public async Task TransformCommandConvertsOpenApiWithDefaultOutputNameAsync() { var options = new HidiOptions { - OpenApi = Path.Combine("UtilityFiles", "SampleOpenApi.yml"), + OpenApi = Path.Join("UtilityFiles", "SampleOpenApi.yml"), CleanOutput = true, TerseOutput = false, InlineLocal = false, InlineExternal = false, + Output = new FileInfo($"{nameof(TransformCommandConvertsOpenApiWithDefaultOutputNameAsync)}.yml") }; // create a dummy ILogger instance for testing - await OpenApiService.TransformOpenApiDocumentAsync(options, _logger); + await OpenApiService.TransformOpenApiDocumentAsync(options, _logger, TestContext.Current.CancellationToken); - var output = await File.ReadAllTextAsync("output.yml"); + var output = await File.ReadAllTextAsync(options.Output.FullName, TestContext.Current.CancellationToken); Assert.NotEmpty(output); } @@ -254,7 +255,8 @@ public async Task TransformCommandConvertsOpenApiWithDefaultOutputNameAndSwitchF { var options = new HidiOptions { - OpenApi = Path.Combine("UtilityFiles", "SampleOpenApi.yml"), + OpenApi = Path.Join("UtilityFiles", "SampleOpenApi.yml"), + Output = new($"{nameof(TransformCommandConvertsOpenApiWithDefaultOutputNameAndSwitchFormatAsync)}.yml"), CleanOutput = true, Version = "3.0", OpenApiFormat = OpenApiConstants.Yaml, @@ -263,9 +265,9 @@ public async Task TransformCommandConvertsOpenApiWithDefaultOutputNameAndSwitchF InlineExternal = false, }; // create a dummy ILogger instance for testing - await OpenApiService.TransformOpenApiDocumentAsync(options, _logger); + await OpenApiService.TransformOpenApiDocumentAsync(options, _logger, TestContext.Current.CancellationToken); - var output = await File.ReadAllTextAsync("output.yml"); + var output = await File.ReadAllTextAsync(options.Output.FullName, TestContext.Current.CancellationToken); Assert.NotEmpty(output); } @@ -280,16 +282,17 @@ public Task ThrowTransformCommandIfOpenApiAndCsdlAreEmptyAsync() InlineExternal = false, }; return Assert.ThrowsAsync(() => - OpenApiService.TransformOpenApiDocumentAsync(options, _logger)); + OpenApiService.TransformOpenApiDocumentAsync(options, _logger, TestContext.Current.CancellationToken)); } [Fact] public async Task TransformToPowerShellCompliantOpenApiAsync() { - var settingsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "UtilityFiles", "examplepowershellsettings.json"); + var settingsPath = Path.Join(AppDomain.CurrentDomain.BaseDirectory, "UtilityFiles", "examplepowershellsettings.json"); var options = new HidiOptions { - OpenApi = Path.Combine("UtilityFiles", "SampleOpenApi.yml"), + OpenApi = Path.Join("UtilityFiles", "SampleOpenApi.yml"), + Output = new($"{nameof(TransformToPowerShellCompliantOpenApiAsync)}.yaml"), CleanOutput = true, Version = "3.0", OpenApiFormat = OpenApiConstants.Yaml, @@ -299,9 +302,9 @@ public async Task TransformToPowerShellCompliantOpenApiAsync() SettingsConfig = SettingsUtilities.GetConfiguration(settingsPath) }; // create a dummy ILogger instance for testing - await OpenApiService.TransformOpenApiDocumentAsync(options, _logger); + await OpenApiService.TransformOpenApiDocumentAsync(options, _logger, TestContext.Current.CancellationToken); - var output = await File.ReadAllTextAsync("output.yaml"); + var output = await File.ReadAllTextAsync(options.Output.FullName, TestContext.Current.CancellationToken); Assert.NotEmpty(output); } @@ -309,14 +312,15 @@ public async Task TransformToPowerShellCompliantOpenApiAsync() public async Task InvokeTransformCommandAsync() { var rootCommand = Program.CreateRootCommand(); - var openapi = Path.Combine(".", "UtilityFiles", "SampleOpenApi.yml"); - var args = new[] { "transform", "-d", openapi, "-o", "sample.json", "--co" }; + var openapi = Path.Join(".", "UtilityFiles", "SampleOpenApi.yml"); + var outputPath = $"{nameof(InvokeTransformCommandAsync)}.json"; + var args = new[] { "transform", "-d", openapi, "-o", outputPath, "--co" }; var parseResult = rootCommand.Parse(args); var handler = Assert.IsType(rootCommand.Subcommands.First(c => c.Name == "transform").Action, exactMatch: false); - await handler.InvokeAsync(parseResult); + await handler.InvokeAsync(parseResult, TestContext.Current.CancellationToken); - var output = await File.ReadAllTextAsync("sample.json"); + var output = await File.ReadAllTextAsync(outputPath, TestContext.Current.CancellationToken); Assert.NotEmpty(output); } @@ -325,14 +329,15 @@ public async Task InvokeTransformCommandAsync() public async Task InvokeShowCommandAsync() { var rootCommand = Program.CreateRootCommand(); - var openApi = Path.Combine(".", "UtilityFiles", "SampleOpenApi.yml"); - var args = new[] { "show", "-d", openApi, "-o", "sample.md" }; + var openApi = Path.Join(".", "UtilityFiles", "SampleOpenApi.yml"); + var outputPath = $"{nameof(InvokeShowCommandAsync)}.md"; + var args = new[] { "show", "-d", openApi, "-o", outputPath }; var parseResult = rootCommand.Parse(args); var handler = Assert.IsType(rootCommand.Subcommands.First(c => c.Name == "show").Action, exactMatch: false); - await handler.InvokeAsync(parseResult); + await handler.InvokeAsync(parseResult, TestContext.Current.CancellationToken); - var output = await File.ReadAllTextAsync("sample.md"); + var output = await File.ReadAllTextAsync(outputPath, TestContext.Current.CancellationToken); Assert.Contains("graph LR", output, StringComparison.Ordinal); } @@ -340,14 +345,15 @@ public async Task InvokeShowCommandAsync() public async Task InvokePluginCommandAsync() { var rootCommand = Program.CreateRootCommand(); - var manifest = Path.Combine(".", "UtilityFiles", "exampleapimanifest.json"); - var args = new[] { "plugin", "-m", manifest, "--of", AppDomain.CurrentDomain.BaseDirectory }; + var manifest = Path.Join(".", "UtilityFiles", "exampleapimanifest.json"); + var outputPath = Path.Join(AppDomain.CurrentDomain.BaseDirectory, nameof(InvokePluginCommandAsync)); + var args = new[] { "plugin", "-m", manifest, "--of", outputPath }; var parseResult = rootCommand.Parse(args); var handler = Assert.IsType(rootCommand.Subcommands.First(c => c.Name == "plugin").Action, exactMatch: false); - await handler.InvokeAsync(parseResult); + await handler.InvokeAsync(parseResult, TestContext.Current.CancellationToken); - using var jsDoc = JsonDocument.Parse(await File.ReadAllTextAsync("ai-plugin.json")); + using var jsDoc = JsonDocument.Parse(await File.ReadAllTextAsync(Path.Join(outputPath, "ai-plugin.json"), TestContext.Current.CancellationToken)); var openAiManifest = OpenAIPluginManifest.Load(jsDoc.RootElement); Assert.NotNull(openAiManifest); diff --git a/test/Microsoft.OpenApi.Readers.Tests/DefaultSettingsFixture.cs b/test/Microsoft.OpenApi.Readers.Tests/DefaultSettingsFixture.cs deleted file mode 100644 index ed8072ba6..000000000 --- a/test/Microsoft.OpenApi.Readers.Tests/DefaultSettingsFixture.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using FluentAssertions; - -namespace Microsoft.OpenApi.Readers.Tests -{ - /// - /// Fixture containing default settings for external libraries. - /// - public class DefaultSettingsFixture - { - /// - /// Initializes an instance of . - /// - public DefaultSettingsFixture() - { - // We need RespectingRuntimeTypes() to ensure equivalence test works property, - // given that there are multiple types that can be used for the declared type OpenApiAny. - // Without this option, properties specific to those types would not be compared. - AssertionOptions.AssertEquivalencyUsing( - o => o - .AllowingInfiniteRecursion() - .RespectingRuntimeTypes() - .WithStrictOrdering()); - } - } -} diff --git a/test/Microsoft.OpenApi.Readers.Tests/DefaultSettingsFixtureCollection.cs b/test/Microsoft.OpenApi.Readers.Tests/DefaultSettingsFixtureCollection.cs deleted file mode 100644 index d17520848..000000000 --- a/test/Microsoft.OpenApi.Readers.Tests/DefaultSettingsFixtureCollection.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using Xunit; - -namespace Microsoft.OpenApi.Readers.Tests -{ - /// - /// Collection dummy class for . - /// - /// - /// This class is needed in xUnit framework to define collection name - /// to be used in unit test classes. - /// - [CollectionDefinition("DefaultSettings")] - public class DefaultSettingsFixtureCollection : ICollectionFixture - { - } -} diff --git a/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj b/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj index b9b23d246..ccddceff0 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj +++ b/test/Microsoft.OpenApi.Readers.Tests/Microsoft.OpenApi.Readers.Tests.csproj @@ -4,6 +4,7 @@ false true ..\..\src\Microsoft.OpenApi.snk + true @@ -15,12 +16,11 @@ - - - - - - + + + + + diff --git a/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderSettingsExtensionsTests.cs b/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderSettingsExtensionsTests.cs index ff3f4509e..7268c5304 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderSettingsExtensionsTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderSettingsExtensionsTests.cs @@ -29,4 +29,19 @@ public void IsAvailableOnSameNamespace() var extensionsNS = typeof(OpenApiReaderSettingsExtensions).Namespace; Assert.Equal(settingsNS, extensionsNS, StringComparer.Ordinal); } + + [Fact] + public void AddsYamlReaderWithPerReaderSettings() + { + var settings = new OpenApiReaderSettings(); + var yamlSettings = new OpenApiYamlReaderSettings + { + MaxAliasExpansionNodeCount = 10, + }; + + settings.AddYamlReader(yamlSettings); + + var yamlReader = Assert.IsType(settings.GetReader(OpenApiConstants.Yaml)); + Assert.Same(yamlReader, settings.GetReader(OpenApiConstants.Yml)); + } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiDiagnosticTests.cs b/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiDiagnosticTests.cs index 86adcdd5b..bb631927b 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiDiagnosticTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiDiagnosticTests.cs @@ -16,7 +16,7 @@ public class OpenApiDiagnosticTests [Fact] public async Task DetectedSpecificationVersionShouldBeV2_0() { - var actual = await OpenApiDocument.LoadAsync("V2Tests/Samples/basic.v2.yaml", SettingsFixture.ReaderSettings); + var actual = await OpenApiDocument.LoadAsync("V2Tests/Samples/basic.v2.yaml", SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); Assert.NotNull(actual.Diagnostic); Assert.Equal(OpenApiSpecVersion.OpenApi2_0, actual.Diagnostic.SpecificationVersion); @@ -25,7 +25,7 @@ public async Task DetectedSpecificationVersionShouldBeV2_0() [Fact] public async Task DetectedSpecificationVersionShouldBeV3_0() { - var actual = await OpenApiDocument.LoadAsync("V3Tests/Samples/OpenApiDocument/minimalDocument.yaml", SettingsFixture.ReaderSettings); + var actual = await OpenApiDocument.LoadAsync("V3Tests/Samples/OpenApiDocument/minimalDocument.yaml", SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); Assert.NotNull(actual.Diagnostic); Assert.Equal(OpenApiSpecVersion.OpenApi3_0, actual.Diagnostic.SpecificationVersion); @@ -44,7 +44,7 @@ public async Task DiagnosticReportMergedForExternalReferenceAsync() settings.AddYamlReader(); ReadResult result; - result = await OpenApiDocument.LoadAsync("OpenApiReaderTests/Samples/OpenApiDiagnosticReportMerged/TodoMain.yaml", settings); + result = await OpenApiDocument.LoadAsync("OpenApiReaderTests/Samples/OpenApiDiagnosticReportMerged/TodoMain.yaml", settings, token: TestContext.Current.CancellationToken); Assert.NotNull(result); Assert.NotNull(result.Document.Workspace); diff --git a/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiStreamReaderTests.cs b/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiStreamReaderTests.cs index d2da0d7a4..61fc12e5b 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiStreamReaderTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/OpenApiStreamReaderTests.cs @@ -17,10 +17,10 @@ public class OpenApiStreamReaderTests [Fact] public async Task StreamShouldCloseIfLeaveStreamOpenSettingEqualsFalse() { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "petStore.yaml")); + using var stream = Resources.GetStream(Path.Join(SampleFolderPath, "petStore.yaml")); var settings = new OpenApiReaderSettings { LeaveStreamOpen = false }; settings.AddYamlReader(); - (_, var diagnostic) = await OpenApiDocument.LoadAsync(stream, settings: settings); + (_, var diagnostic) = await OpenApiDocument.LoadAsync(stream, settings: settings, cancellationToken: TestContext.Current.CancellationToken); Assert.False(stream.CanRead); Assert.Equal(OpenApiConstants.Yaml, diagnostic.Format); } @@ -28,10 +28,10 @@ public async Task StreamShouldCloseIfLeaveStreamOpenSettingEqualsFalse() [Fact] public async Task StreamShouldNotCloseIfLeaveStreamOpenSettingEqualsTrue() { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "petStore.yaml")); + using var stream = Resources.GetStream(Path.Join(SampleFolderPath, "petStore.yaml")); var settings = new OpenApiReaderSettings { LeaveStreamOpen = true }; settings.AddYamlReader(); - _ = await OpenApiDocument.LoadAsync(stream, settings: settings); + _ = await OpenApiDocument.LoadAsync(stream, settings: settings, cancellationToken: TestContext.Current.CancellationToken); Assert.True(stream.CanRead); } @@ -39,15 +39,15 @@ public async Task StreamShouldNotCloseIfLeaveStreamOpenSettingEqualsTrue() public async Task StreamShouldNotBeDisposedIfLeaveStreamOpenSettingIsTrueAsync() { var memoryStream = new MemoryStream(); - using var fileStream = Resources.GetStream(Path.Combine(SampleFolderPath, "petStore.yaml")); + using var fileStream = Resources.GetStream(Path.Join(SampleFolderPath, "petStore.yaml")); - await fileStream.CopyToAsync(memoryStream); + await fileStream.CopyToAsync(memoryStream, TestContext.Current.CancellationToken); memoryStream.Position = 0; var stream = memoryStream; var settings = new OpenApiReaderSettings { LeaveStreamOpen = true }; settings.AddYamlReader(); - _ = await OpenApiDocument.LoadAsync(stream, settings: settings); + _ = await OpenApiDocument.LoadAsync(stream, settings: settings, cancellationToken: TestContext.Current.CancellationToken); stream.Seek(0, SeekOrigin.Begin); // does not throw an object disposed exception Assert.True(stream.CanRead); } @@ -60,12 +60,12 @@ public async Task StreamShouldReadWhenInitializedAsync() BaseAddress = new Uri("https://raw.githubusercontent.com/OAI/OpenAPI-Specification/") }; - var stream = await httpClient.GetStreamAsync("20fe7a7b720a0e48e5842d002ac418b12a8201df/tests/v3.0/pass/petstore.yaml"); + var stream = await httpClient.GetStreamAsync("20fe7a7b720a0e48e5842d002ac418b12a8201df/tests/v3.0/pass/petstore.yaml", TestContext.Current.CancellationToken); // Read V3 as YAML var settings = new OpenApiReaderSettings(); settings.AddYamlReader(); - var result = await OpenApiDocument.LoadAsync(stream, settings: settings); + var result = await OpenApiDocument.LoadAsync(stream, settings: settings, cancellationToken: TestContext.Current.CancellationToken); Assert.NotNull(result.Document); } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/UnsupportedSpecVersionTests.cs b/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/UnsupportedSpecVersionTests.cs index 6c66b9d00..e695c25a4 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/UnsupportedSpecVersionTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/OpenApiReaderTests/UnsupportedSpecVersionTests.cs @@ -14,7 +14,7 @@ public async Task ThrowOpenApiUnsupportedSpecVersionException() { try { - _ = await OpenApiDocument.LoadAsync("OpenApiReaderTests/Samples/unsupported.v1.yaml", SettingsFixture.ReaderSettings); + _ = await OpenApiDocument.LoadAsync("OpenApiReaderTests/Samples/unsupported.v1.yaml", SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); } catch (OpenApiUnsupportedSpecVersionException exception) { diff --git a/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs b/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs index 6d5530e3a..34f4f2d45 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/OpenApiWorkspaceTests/OpenApiWorkspaceStreamTests.cs @@ -35,10 +35,10 @@ public async Task LoadingDocumentWithResolveAllReferencesShouldLoadDocumentIntoW """; var wr = new StreamWriter(stream); await wr.WriteAsync(doc); - await wr.FlushAsync(); + await wr.FlushAsync(TestContext.Current.CancellationToken); stream.Position = 0; - var result = await OpenApiDocument.LoadAsync(stream, OpenApiConstants.Yaml, settings: settings); + var result = await OpenApiDocument.LoadAsync(stream, OpenApiConstants.Yaml, settings: settings, cancellationToken: TestContext.Current.CancellationToken); Assert.NotNull(result.Document.Workspace); } @@ -56,7 +56,7 @@ public async Task LoadDocumentWithExternalReferenceShouldLoadBothDocumentsIntoWo settings.AddYamlReader(); ReadResult result; - result = await OpenApiDocument.LoadAsync("V3Tests/Samples/OpenApiWorkspace/TodoMain.yaml", settings); + result = await OpenApiDocument.LoadAsync("V3Tests/Samples/OpenApiWorkspace/TodoMain.yaml", settings, token: TestContext.Current.CancellationToken); var externalDocBaseUri = result.Document.Workspace.GetDocumentId("./TodoComponents.yaml"); var schemasPath = "#/components/schemas/"; @@ -82,14 +82,14 @@ public async Task LoadDocumentWithExternalReferencesInSubDirectories() settings.AddYamlReader(); // Act - var result = await OpenApiDocument.LoadAsync($"{sampleFolderPath}/Root.yaml", settings); + var result = await OpenApiDocument.LoadAsync($"{sampleFolderPath}/Root.yaml", settings, token: TestContext.Current.CancellationToken); var document = result.Document; var workspace = result.Document.Workspace; // Assert - Assert.True(workspace.Contains($"{Path.Combine(referenceBaseUri, "Directory", "PetsPage.yaml")}#/components/schemas/PetsPage")); - Assert.True(workspace.Contains($"{Path.Combine(referenceBaseUri, "Directory", "Pets.yaml")}#/components/schemas/Pets")); - Assert.True(workspace.Contains($"{Path.Combine(referenceBaseUri, "Directory", "Pets.yaml")}#/components/schemas/Pet")); + Assert.True(workspace.Contains($"{Path.Join(referenceBaseUri, "Directory", "PetsPage.yaml")}#/components/schemas/PetsPage")); + Assert.True(workspace.Contains($"{Path.Join(referenceBaseUri, "Directory", "Pets.yaml")}#/components/schemas/Pets")); + Assert.True(workspace.Contains($"{Path.Join(referenceBaseUri, "Directory", "Pets.yaml")}#/components/schemas/Pet")); var operationResponseSchema = document.Paths["/pets"].Operations[HttpMethod.Get].Responses["200"].Content["application/json"].Schema; Assert.IsType(operationResponseSchema); diff --git a/test/Microsoft.OpenApi.Readers.Tests/OpenApiYamlReaderTests.cs b/test/Microsoft.OpenApi.Readers.Tests/OpenApiYamlReaderTests.cs index ea0ef0fd9..60376caa9 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/OpenApiYamlReaderTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/OpenApiYamlReaderTests.cs @@ -1,5 +1,6 @@ -using System; +using System; using System.IO; +using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -28,20 +29,69 @@ public async Task ReadAsyncParsesDocumentsFromNonMemoryStreams() var result = await reader.ReadAsync(stream, DocumentLocation, SettingsFixture.ReaderSettings, CancellationToken.None); - Assert.NotNull(result.Document); + Assert.True(result.Document is not null, string.Join(Environment.NewLine, result.Diagnostic.Errors.Select(static error => error.Message))); Assert.Equal("Sample API", result.Document.Info.Title); Assert.Equal(OpenApiConstants.Yaml, result.Diagnostic.Format); } [Fact] - public void ReadThrowsWhenYamlDoesNotContainADocument() + public async Task ReadAsyncHonorsCancellationForMemoryStreams() + { + var reader = new OpenApiYamlReader(); + await using var stream = CreateStream( + """ + openapi: 3.0.1 + info: + title: Sample API + version: 1.0.0 + paths: {} + """); + using var cancellationSource = new CancellationTokenSource(); + cancellationSource.Cancel(); + + await Assert.ThrowsAnyAsync( + () => reader.ReadAsync( + stream, + DocumentLocation, + SettingsFixture.ReaderSettings, + cancellationSource.Token)); + } + + [Fact] + public async Task ReadAsyncPropagatesCancellationDuringYamlScanning() + { + var reader = new OpenApiYamlReader(); + using var cancellationSource = new CancellationTokenSource(); + await using var stream = new CancelingMemoryStream( + Encoding.UTF8.GetBytes( + """ + openapi: 3.0.1 + info: + title: Sample API + version: 1.0.0 + paths: {} + """), + cancellationSource); + + await Assert.ThrowsAnyAsync( + () => reader.ReadAsync( + stream, + DocumentLocation, + SettingsFixture.ReaderSettings, + cancellationSource.Token)); + } + + [Fact] + public void ReadReturnsDiagnosticWhenYamlDoesNotContainADocument() { var reader = new OpenApiYamlReader(); using var stream = CreateStream(string.Empty); - var exception = Assert.Throws(() => reader.Read(stream, DocumentLocation, SettingsFixture.ReaderSettings)); + var result = reader.Read(stream, DocumentLocation, SettingsFixture.ReaderSettings); - Assert.Equal("No documents found in the YAML stream.", exception.Message); + Assert.Null(result.Document); + Assert.Single(result.Diagnostic.Errors); + Assert.Equal(OpenApiConstants.Yaml, result.Diagnostic.Format); } [Fact] @@ -75,6 +125,19 @@ public void ReadThrowsWhenSettingsIsNull() Assert.Throws(() => reader.Read(stream, DocumentLocation, null!)); } + [Fact] + public async Task ReadAsyncThrowsWhenSettingsIsNullWithoutConsumingTheStream() + { + var reader = new OpenApiYamlReader(); + var inner = CreateStream("openapi: 3.0.1"); + await using var stream = new NonMemoryStream(inner); + + await Assert.ThrowsAsync( + () => reader.ReadAsync(stream, DocumentLocation, null!, CancellationToken.None)); + + Assert.Equal(0, inner.Position); + } + [Fact] public void ReadReturnsDiagnosticErrorForExponentialAliasExpansion() { @@ -101,6 +164,359 @@ public void ReadReturnsDiagnosticErrorForExponentialAliasExpansion() Assert.Equal(OpenApiConstants.Yaml, result.Diagnostic.Format); } + [Fact] + public void ReadRejectsAliasBombBelowTotalNodeLimit() + { + const int fanOut = 21; + var yaml = new StringBuilder(); + yaml.AppendLine($"a: &a [{string.Join(",", Enumerable.Repeat("\"x\"", fanOut))}]"); + var previousAnchor = 'a'; + for (var anchor = 'b'; anchor <= 'e'; anchor++) + { + yaml.AppendLine($"{anchor}: &{anchor} [{string.Join(",", Enumerable.Repeat($"*{previousAnchor}", fanOut))}]"); + previousAnchor = anchor; + } + + var reader = new OpenApiYamlReader(); + using var stream = CreateStream(yaml.ToString()); + + var result = reader.Read(stream, DocumentLocation, SettingsFixture.ReaderSettings); + + Assert.Null(result.Document); + Assert.Contains(result.Diagnostic.Errors, error => error.Message.Contains("expands aliases", StringComparison.Ordinal)); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void ReadReturnsDiagnosticForDeepNestingBeforeYamlDomComposition(bool flowStyle) + { + const int depth = 5_000; + var yaml = flowStyle + ? new string('[', depth) + new string(']', depth) + : string.Concat(Enumerable.Repeat("- ", depth)) + "value"; + var reader = new OpenApiYamlReader(); + using var stream = CreateStream(yaml); + + var result = reader.Read(stream, DocumentLocation, SettingsFixture.ReaderSettings); + + Assert.Null(result.Document); + Assert.Contains(result.Diagnostic.Errors, error => error.Message.Contains("maximum nesting depth", StringComparison.Ordinal)); + } + + [Fact] + public void ReadHonoursMaxDepthAboveTheUnderlyingParserDefault() + { + // SharpYaml applies its own nesting limit, defaulting to 64. Unless the reader forwards + // MaxDepth to it, every configured value above that default silently has no effect. + const int depth = 100; + var yaml = new string('[', depth) + new string(']', depth); + var reader = new OpenApiYamlReader(new OpenApiYamlReaderSettings { MaxDepth = 200 }); + using var stream = CreateStream(yaml); + + var result = reader.Read(stream, DocumentLocation, SettingsFixture.ReaderSettings); + + Assert.DoesNotContain(result.Diagnostic.Errors, error => error.Message.Contains("nesting depth", StringComparison.Ordinal)); + } + + [Fact] + public void ReadHonoursMaxDepthBelowTheUnderlyingParserDefault() + { + const int depth = 40; + var yaml = new string('[', depth) + new string(']', depth); + var reader = new OpenApiYamlReader(new OpenApiYamlReaderSettings { MaxDepth = 32 }); + using var stream = CreateStream(yaml); + + var result = reader.Read(stream, DocumentLocation, SettingsFixture.ReaderSettings); + + Assert.Null(result.Document); + Assert.Contains(result.Diagnostic.Errors, error => error.Message.Contains("nesting depth of 32", StringComparison.Ordinal)); + } + + [Fact] + public void ReadRejectsAliasExpansionThatExceedsMaxDepth() + { + // The anchor and the alias each sit within the depth limit, but expanding the alias grafts + // the anchored subtree onto an equally deep position, producing a tree twice as deep. The + // YAML parser cannot catch this because it sees the alias as a single event. + const int half = 50; + var yaml = + $"a: &d {new string('[', half)}{new string(']', half)}\n" + + $"b: {new string('[', half)}*d{new string(']', half)}\n"; + var reader = new OpenApiYamlReader(); + using var stream = CreateStream(yaml); + + var result = reader.Read(stream, DocumentLocation, SettingsFixture.ReaderSettings); + + Assert.Null(result.Document); + Assert.Contains(result.Diagnostic.Errors, error => error.Message.Contains("expands an alias", StringComparison.Ordinal)); + } + + [Fact] + public void ReadStopsAfterFirstYamlDocument() + { + const int depth = 5_000; + var yaml = + """ + openapi: 3.0.1 + info: + title: First document + version: 1.0.0 + paths: {} + --- + """ + + Environment.NewLine + + new string('[', depth) + + new string(']', depth); + var reader = new OpenApiYamlReader(); + using var stream = CreateStream(yaml); + + var result = reader.Read(stream, DocumentLocation, SettingsFixture.ReaderSettings); + + Assert.True(result.Document is not null, string.Join(Environment.NewLine, result.Diagnostic.Errors.Select(static error => error.Message))); + Assert.Equal("First document", result.Document.Info.Title); + Assert.Empty(result.Diagnostic.Errors); + } + + [Fact] + public void ReadReturnsDiagnosticForCyclicAlias() + { + var reader = new OpenApiYamlReader(); + using var stream = CreateStream("x-cycle: &cycle [*cycle]"); + + var result = reader.Read(stream, DocumentLocation, SettingsFixture.ReaderSettings); + + Assert.Null(result.Document); + Assert.Contains(result.Diagnostic.Errors, error => error.Message.Contains("forms a cycle", StringComparison.Ordinal)); + } + + [Fact] + public void ReadFragmentReturnsDiagnosticForAliasExpansion() + { + var reader = new OpenApiYamlReader(new() + { + MaxAliasExpansionNodeCount = 1, + }); + using var stream = CreateStream( + """ + type: &value string + title: *value + description: *value + """); + + var schema = reader.ReadFragment( + stream, + OpenApiSpecVersion.OpenApi3_0, + new OpenApiDocument(), + out var diagnostic); + + Assert.Null(schema); + Assert.Single(diagnostic.Errors); + Assert.Equal(OpenApiConstants.Yaml, diagnostic.Format); + } + + [Fact] + public void ReadFragmentReturnsDiagnosticWhenInputExceedsByteLimit() + { + var reader = new OpenApiYamlReader(new() + { + MaxInputByteCount = 10, + }); + using var stream = CreateStream("type: string"); + + var schema = reader.ReadFragment( + stream, + OpenApiSpecVersion.OpenApi3_0, + new OpenApiDocument(), + out var diagnostic); + + Assert.Null(schema); + Assert.Contains(diagnostic.Errors, error => error.Message.Contains("maximum supported size", StringComparison.Ordinal)); + Assert.Equal(OpenApiConstants.Yaml, diagnostic.Format); + } + + [Fact] + public void ReadMeasuresOnlyTheRemainingBytesOfAPositionedStream() + { + const string padding = "##################################################"; + const string yaml = + """ + openapi: 3.0.1 + info: + title: Sample API + version: 1.0.0 + paths: {} + """; + var reader = new OpenApiYamlReader(new() + { + MaxInputByteCount = (uint)Encoding.UTF8.GetByteCount(yaml), + }); + using var stream = CreateStream(padding + yaml); + stream.Position = padding.Length; + + var result = reader.Read(stream, DocumentLocation, SettingsFixture.ReaderSettings); + + Assert.True(result.Document is not null, string.Join(Environment.NewLine, result.Diagnostic.Errors.Select(static error => error.Message))); + Assert.Equal("Sample API", result.Document.Info.Title); + } + + [Fact] + public void ReadReturnsDiagnosticWhenInputExceedsByteLimit() + { + var reader = new OpenApiYamlReader(new() + { + MaxInputByteCount = 10, + }); + using var stream = CreateStream("openapi: 3.0.1"); + + var result = reader.Read(stream, DocumentLocation, SettingsFixture.ReaderSettings); + + Assert.Null(result.Document); + Assert.Contains(result.Diagnostic.Errors, error => error.Message.Contains("maximum supported size", StringComparison.Ordinal)); + } + + [Fact] + public async Task ReadAsyncReturnsDiagnosticWhenNonMemoryStreamExceedsByteLimit() + { + var reader = new OpenApiYamlReader(new() + { + MaxInputByteCount = 10, + }); + await using var stream = new NonMemoryStream(CreateStream("openapi: 3.0.1")); + + var result = await reader.ReadAsync( + stream, + DocumentLocation, + SettingsFixture.ReaderSettings, + CancellationToken.None); + + Assert.Null(result.Document); + Assert.Contains(result.Diagnostic.Errors, error => error.Message.Contains("maximum supported size", StringComparison.Ordinal)); + } + + [Fact] + public void ReadReturnsDiagnosticWhenScalarExceedsLengthLimit() + { + var reader = new OpenApiYamlReader(new() + { + MaxScalarLength = 3, + }); + using var stream = CreateStream("value: 1234"); + + var result = reader.Read(stream, DocumentLocation, SettingsFixture.ReaderSettings); + + Assert.Null(result.Document); + Assert.Contains(result.Diagnostic.Errors, error => error.Message.Contains("maximum supported length", StringComparison.Ordinal)); + } + + [Theory] + [InlineData("a: 1\na: 2")] + [InlineData("? [1, 2]: value")] + [InlineData("[")] + [InlineData("# comment only")] + public void ReadReturnsDiagnosticForInvalidYaml(string yaml) + { + var reader = new OpenApiYamlReader(); + using var stream = CreateStream(yaml); + + var result = reader.Read(stream, DocumentLocation, SettingsFixture.ReaderSettings); + + Assert.Null(result.Document); + Assert.NotEmpty(result.Diagnostic.Errors); + Assert.Equal(OpenApiConstants.Yaml, result.Diagnostic.Format); + } + + [Fact] + public void ReaderCopiesPerInstanceSettings() + { + var yamlSettings = new OpenApiYamlReaderSettings + { + MaxAliasExpansionNodeCount = 1, + }; + var reader = new OpenApiYamlReader(yamlSettings); + yamlSettings.MaxAliasExpansionNodeCount = 100; + using var stream = CreateStream( + """ + type: &value string + title: *value + description: *value + """); + + var schema = reader.ReadFragment( + stream, + OpenApiSpecVersion.OpenApi3_0, + new OpenApiDocument(), + out var diagnostic); + + Assert.Null(schema); + Assert.Single(diagnostic.Errors); + } + + [Theory] + [InlineData(0u, 5_000_000u, 5_000u, OpenApiYamlReaderSettings.DefaultMaxInputByteCount, OpenApiYamlReaderSettings.DefaultMaxScalarLength)] + [InlineData(YamlConverter.MaximumAllowedDepth + 1, 5_000_000u, 5_000u, OpenApiYamlReaderSettings.DefaultMaxInputByteCount, OpenApiYamlReaderSettings.DefaultMaxScalarLength)] + [InlineData(64u, 0u, 5_000u, OpenApiYamlReaderSettings.DefaultMaxInputByteCount, OpenApiYamlReaderSettings.DefaultMaxScalarLength)] + [InlineData(64u, YamlConverter.MaximumAllowedNodeCount + 1, 5_000u, OpenApiYamlReaderSettings.DefaultMaxInputByteCount, OpenApiYamlReaderSettings.DefaultMaxScalarLength)] + [InlineData(64u, 5_000_000u, 0u, OpenApiYamlReaderSettings.DefaultMaxInputByteCount, OpenApiYamlReaderSettings.DefaultMaxScalarLength)] + [InlineData(64u, 5_000_000u, 5_000u, 0u, OpenApiYamlReaderSettings.DefaultMaxScalarLength)] + [InlineData(64u, 5_000_000u, 5_000u, OpenApiYamlReaderSettings.DefaultMaxInputByteCount, 0u)] + public void ReaderRejectsInvalidResourceLimits( + uint maxDepth, + uint maxNodeCount, + uint maxAliasExpansionNodeCount, + uint maxInputByteCount, + uint maxScalarLength) + { + var settings = new OpenApiYamlReaderSettings + { + MaxDepth = maxDepth, + MaxNodeCount = maxNodeCount, + MaxAliasExpansionNodeCount = maxAliasExpansionNodeCount, + MaxInputByteCount = maxInputByteCount, + MaxScalarLength = maxScalarLength, + }; + + Assert.Throws(() => new OpenApiYamlReader(settings)); + } + + [Fact] + public void ReaderLimitsDefaultToDocumentedValues() + { + Assert.Equal(134_217_728u, OpenApiYamlReaderSettings.DefaultMaxInputByteCount); + Assert.Equal(65_536u, OpenApiYamlReaderSettings.DefaultMaxScalarLength); + + var settings = new OpenApiYamlReaderSettings(); + + Assert.Equal(YamlConverter.DefaultMaxDepth, settings.MaxDepth); + Assert.Equal(YamlConverter.DefaultMaxNodeCount, settings.MaxNodeCount); + Assert.Equal(YamlConverter.DefaultMaxAliasExpansionNodeCount, settings.MaxAliasExpansionNodeCount); + Assert.Equal(OpenApiYamlReaderSettings.DefaultMaxInputByteCount, settings.MaxInputByteCount); + Assert.Equal(OpenApiYamlReaderSettings.DefaultMaxScalarLength, settings.MaxScalarLength); + } + + [Fact] + public void ReaderAcceptsMaxNodeCountAtTheSafeCeiling() + { + var settings = new OpenApiYamlReaderSettings + { + MaxNodeCount = YamlConverter.MaximumAllowedNodeCount, + }; + var reader = new OpenApiYamlReader(settings); + using var stream = CreateStream( + """ + openapi: 3.0.1 + info: + title: Sample API + version: 1.0.0 + paths: {} + """); + + var result = reader.Read(stream, DocumentLocation, SettingsFixture.ReaderSettings); + + Assert.True(result.Document is not null, string.Join(Environment.NewLine, result.Diagnostic.Errors.Select(static error => error.Message))); + Assert.Equal("Sample API", result.Document.Info.Title); + } + private static MemoryStream CreateStream(string yaml) { return new MemoryStream(Encoding.UTF8.GetBytes(yaml)); @@ -134,4 +550,32 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); } } + + private sealed class CancelingMemoryStream(byte[] buffer, CancellationTokenSource cancellationSource) : MemoryStream(buffer) + { + private bool _canceled; + + public override int Read(byte[] buffer, int offset, int count) + { + var bytesRead = base.Read(buffer, offset, count); + CancelAfterFirstRead(); + return bytesRead; + } + + public override int Read(Span buffer) + { + var bytesRead = base.Read(buffer); + CancelAfterFirstRead(); + return bytesRead; + } + + private void CancelAfterFirstRead() + { + if (!_canceled) + { + _canceled = true; + cancellationSource.Cancel(); + } + } + } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/TryLoadReferenceV2Tests.cs b/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/TryLoadReferenceV2Tests.cs index de41cd81c..b175d333d 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/TryLoadReferenceV2Tests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/ReferenceService/TryLoadReferenceV2Tests.cs @@ -16,7 +16,7 @@ public class TryLoadReferenceV2Tests public async Task LoadParameterReference() { // Arrange - var result = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "multipleReferences.v2.yaml"), SettingsFixture.ReaderSettings); + var result = await OpenApiDocument.LoadAsync(Path.Join(SampleFolderPath, "multipleReferences.v2.yaml"), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var reference = new OpenApiParameterReference("skipParam", result.Document); // Assert @@ -41,7 +41,7 @@ public async Task LoadParameterReference() [Fact] public async Task LoadSecuritySchemeReference() { - var result = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "multipleReferences.v2.yaml"), SettingsFixture.ReaderSettings); + var result = await OpenApiDocument.LoadAsync(Path.Join(SampleFolderPath, "multipleReferences.v2.yaml"), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var reference = new OpenApiSecuritySchemeReference("api_key_sample", result.Document); @@ -59,7 +59,7 @@ public async Task LoadSecuritySchemeReference() [Fact] public async Task LoadResponseReference() { - var result = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "multipleReferences.v2.yaml"), SettingsFixture.ReaderSettings); + var result = await OpenApiDocument.LoadAsync(Path.Join(SampleFolderPath, "multipleReferences.v2.yaml"), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var reference = new OpenApiResponseReference("NotFound", result.Document); @@ -80,7 +80,7 @@ public async Task LoadResponseReference() [Fact] public async Task LoadResponseAndSchemaReference() { - var result = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "multipleReferences.v2.yaml"), SettingsFixture.ReaderSettings); + var result = await OpenApiDocument.LoadAsync(Path.Join(SampleFolderPath, "multipleReferences.v2.yaml"), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var reference = new OpenApiResponseReference("GeneralError", result.Document); var expected = new OpenApiResponse diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/ComparisonTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/ComparisonTests.cs index 32b8a4475..db63cbe9c 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/ComparisonTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/ComparisonTests.cs @@ -3,8 +3,8 @@ using System.IO; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.OpenApi.Reader; +using Microsoft.OpenApi.Tests; using Xunit; namespace Microsoft.OpenApi.Readers.Tests.V2Tests @@ -22,13 +22,12 @@ public async Task EquivalentV2AndV3DocumentsShouldProduceEquivalentObjects(strin { var settings = new OpenApiReaderSettings(); settings.AddYamlReader(); - using var streamV2 = Resources.GetStream(Path.Combine(SampleFolderPath, $"{fileName}.v2.yaml")); - using var streamV3 = Resources.GetStream(Path.Combine(SampleFolderPath, $"{fileName}.v3.yaml")); - var result1 = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, $"{fileName}.v2.yaml"), SettingsFixture.ReaderSettings); - var result2 = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, $"{fileName}.v3.yaml"), SettingsFixture.ReaderSettings); + using var streamV2 = Resources.GetStream(Path.Join(SampleFolderPath, $"{fileName}.v2.yaml")); + using var streamV3 = Resources.GetStream(Path.Join(SampleFolderPath, $"{fileName}.v3.yaml")); + var result1 = await OpenApiDocument.LoadAsync(Path.Join(SampleFolderPath, $"{fileName}.v2.yaml"), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); + var result2 = await OpenApiDocument.LoadAsync(Path.Join(SampleFolderPath, $"{fileName}.v3.yaml"), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); - result2.Document.Should().BeEquivalentTo(result1.Document, - options => options.Excluding(x => x.Workspace).Excluding(y => y.BaseUri)); + OpenApiTestAssert.Equivalent(result1.Document, result2.Document, nameof(OpenApiDocument.Workspace), nameof(OpenApiDocument.BaseUri)); Assert.Equivalent(result2.Diagnostic.Errors, result1.Diagnostic.Errors); } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs index d7368a83a..9a85d6898 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiDocumentTests.cs @@ -7,8 +7,8 @@ using System.Net.Http; using System.Threading; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.OpenApi.Reader; +using Microsoft.OpenApi.Tests; using Xunit; namespace Microsoft.OpenApi.Readers.Tests.V2Tests @@ -67,7 +67,7 @@ public void ParseDocumentWithDifferentCultureShouldSucceed(string culture) [Fact] public async Task ShouldParseProducesInAnyOrder() { - var result = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "twoResponses.json")); + var result = await OpenApiDocument.LoadAsync(Path.Join(SampleFolderPath, "twoResponses.json"), token: TestContext.Current.CancellationToken); var okSchema = new OpenApiSchema { @@ -119,7 +119,7 @@ public async Task ShouldParseProducesInAnyOrder() Schema = new OpenApiSchemaReference("Error", result.Document) }; - result.Document.Should().BeEquivalentTo(new OpenApiDocument + OpenApiTestAssert.Equivalent(new OpenApiDocument { Info = new() { @@ -220,14 +220,14 @@ public async Task ShouldParseProducesInAnyOrder() ["Error"] = errorSchema } } - }, options => options.Excluding(x => x.BaseUri)); + }, result.Document, nameof(OpenApiDocument.BaseUri)); } [Fact] public async Task ShouldAssignSchemaToAllResponses() { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "multipleProduces.json")); - var result = await OpenApiDocument.LoadAsync(stream, OpenApiConstants.Json); + using var stream = Resources.GetStream(Path.Join(SampleFolderPath, "multipleProduces.json")); + var result = await OpenApiDocument.LoadAsync(stream, OpenApiConstants.Json, cancellationToken: TestContext.Current.CancellationToken); Assert.Equal(OpenApiSpecVersion.OpenApi2_0, result.Diagnostic.SpecificationVersion); @@ -257,7 +257,7 @@ public async Task ShouldAssignSchemaToAllResponses() public async Task ShouldAllowComponentsThatJustContainAReference() { // Act - var actual = (await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "ComponentRootReference.json"))).Document; + var actual = (await OpenApiDocument.LoadAsync(Path.Join(SampleFolderPath, "ComponentRootReference.json"), token: TestContext.Current.CancellationToken)).Document; var schema1 = actual.Components.Schemas["AllPets"]; var schema1Reference = Assert.IsType(schema1); Assert.False(schema1Reference.UnresolvedReference); @@ -279,7 +279,7 @@ public async Task ParseDocumentWithDefaultContentTypeSettingShouldSucceed() }; settings.AddYamlReader(); - var actual = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "docWithEmptyProduces.yaml"), settings); + var actual = await OpenApiDocument.LoadAsync(Path.Join(SampleFolderPath, "docWithEmptyProduces.yaml"), settings, token: TestContext.Current.CancellationToken); var mediaType = actual.Document.Paths["/example"].Operations[HttpMethod.Get].Responses["200"].Content; Assert.Contains("application/json", mediaType); } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiHeaderTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiHeaderTests.cs index b35344a55..965ea9c7e 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiHeaderTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiHeaderTests.cs @@ -3,10 +3,9 @@ using System.Text.Json.Nodes; using System.IO; -using FluentAssertions; -using FluentAssertions.Equivalency; using Microsoft.OpenApi.Reader; using Microsoft.OpenApi.Reader.V2; +using Microsoft.OpenApi.Tests; using Xunit; namespace Microsoft.OpenApi.Readers.Tests.V2Tests @@ -21,7 +20,7 @@ public void ParseHeaderWithDefaultShouldSucceed() { // Arrange JsonNode node; - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "headerWithDefault.yaml"))) + using (var stream = Resources.GetStream(Path.Join(SampleFolderPath, "headerWithDefault.yaml"))) { node = TestHelper.CreateYamlJsonNode(stream); } @@ -30,7 +29,7 @@ public void ParseHeaderWithDefaultShouldSucceed() var header = OpenApiV2Deserializer.LoadHeader(node, new(), new ParsingContext(new())); // Assert - header.Should().BeEquivalentTo( + OpenApiTestAssert.Equivalent( new OpenApiHeader { Schema = new OpenApiSchema() @@ -40,9 +39,8 @@ public void ParseHeaderWithDefaultShouldSucceed() Default = new JsonNodeExtension(5).Node } }, - options => options - .IgnoringCyclicReferences() - .Excluding(x => x.Schema.Default.Parent)); + header, + nameof(JsonNode.Parent)); } [Fact] @@ -50,7 +48,7 @@ public void ParseHeaderWithEnumShouldSucceed() { // Arrange JsonNode node; - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "headerWithEnum.yaml"))) + using (var stream = Resources.GetStream(Path.Join(SampleFolderPath, "headerWithEnum.yaml"))) { node = TestHelper.CreateYamlJsonNode(stream); } @@ -59,7 +57,7 @@ public void ParseHeaderWithEnumShouldSucceed() var header = OpenApiV2Deserializer.LoadHeader(node, new(), new ParsingContext(new())); // Assert - header.Should().BeEquivalentTo( + OpenApiTestAssert.Equivalent( new OpenApiHeader { Schema = new OpenApiSchema() @@ -73,9 +71,7 @@ public void ParseHeaderWithEnumShouldSucceed() new JsonNodeExtension(9).Node ] } - }, options => options.IgnoringCyclicReferences() - .Excluding((IMemberInfo memberInfo) => - memberInfo.Path.EndsWith("Parent"))); + }, header, nameof(JsonNode.Parent)); } } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiOperationTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiOperationTests.cs index 92d50c42e..81ddafd0c 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiOperationTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiOperationTests.cs @@ -7,7 +7,6 @@ using System.Text; using System.Text.Json.Nodes; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.OpenApi.Reader; using Microsoft.OpenApi.Reader.V2; using Microsoft.OpenApi.Reader.V3; @@ -121,7 +120,7 @@ public void ParseBasicOperationShouldSucceed() { // Arrange JsonNode node; - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "basicOperation.yaml"))) + using (var stream = Resources.GetStream(Path.Join(SampleFolderPath, "basicOperation.yaml"))) { node = TestHelper.CreateYamlJsonNode(stream); } @@ -139,7 +138,7 @@ public async Task ParseBasicOperationTwiceShouldYieldSameObject() // Arrange JsonNode node; using (var stream = new MemoryStream( - Encoding.Default.GetBytes(await _basicOperation.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi2_0)))) + Encoding.Default.GetBytes(await _basicOperation.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi2_0, TestContext.Current.CancellationToken)))) { node = TestHelper.CreateYamlJsonNode(stream); } @@ -156,7 +155,7 @@ public void ParseOperationWithBodyShouldSucceed() { // Arrange JsonNode node; - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "operationWithBody.yaml"))) + using (var stream = Resources.GetStream(Path.Join(SampleFolderPath, "operationWithBody.yaml"))) { node = TestHelper.CreateYamlJsonNode(stream); } @@ -165,7 +164,7 @@ public void ParseOperationWithBodyShouldSucceed() var operation = OpenApiV2Deserializer.LoadOperation(node, new(), new ParsingContext(new())); // Assert - operation.Should().BeEquivalentTo(_operationWithBody, options => options.IgnoringCyclicReferences()); + OpenApiTestAssert.Equivalent(_operationWithBody, operation); } [Fact] @@ -174,7 +173,7 @@ public async Task ParseOperationWithBodyTwiceShouldYieldSameObject() // Arrange JsonNode node; using (var stream = new MemoryStream( - Encoding.Default.GetBytes(await _operationWithBody.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi2_0)))) + Encoding.Default.GetBytes(await _operationWithBody.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi2_0, TestContext.Current.CancellationToken)))) { node = TestHelper.CreateYamlJsonNode(stream); } @@ -183,7 +182,7 @@ public async Task ParseOperationWithBodyTwiceShouldYieldSameObject() var operation = OpenApiV2Deserializer.LoadOperation(node, new(), new ParsingContext(new())); // Assert - operation.Should().BeEquivalentTo(_operationWithBody, options => options.IgnoringCyclicReferences()); + OpenApiTestAssert.Equivalent(_operationWithBody, operation); } [Fact] @@ -191,7 +190,7 @@ public void ParseOperationWithResponseExamplesShouldSucceed() { // Arrange JsonNode node; - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "operationWithResponseExamples.yaml"))) + using (var stream = Resources.GetStream(Path.Join(SampleFolderPath, "operationWithResponseExamples.yaml"))) { node = TestHelper.CreateYamlJsonNode(stream); } @@ -200,7 +199,7 @@ public void ParseOperationWithResponseExamplesShouldSucceed() var operation = OpenApiV2Deserializer.LoadOperation(node, new(), new ParsingContext(new())); // Assert - operation.Should().BeEquivalentTo( + OpenApiTestAssert.Equivalent( new OpenApiOperation() { Responses = new OpenApiResponses() @@ -243,13 +242,7 @@ public void ParseOperationWithResponseExamplesShouldSucceed() } }} } - }, options => options.IgnoringCyclicReferences() - .Excluding(o => o.Responses["200"].Content["application/json"].Example[0].Parent) - .Excluding(o => o.Responses["200"].Content["application/json"].Example[0].Root) - .Excluding(o => o.Responses["200"].Content["application/json"].Example[1].Parent) - .Excluding(o => o.Responses["200"].Content["application/json"].Example[1].Root) - .Excluding(o => o.Responses["200"].Content["application/json"].Example[2].Parent) - .Excluding(o => o.Responses["200"].Content["application/json"].Example[2].Root)); + }, operation, nameof(JsonNode.Parent), nameof(JsonNode.Root)); } [Fact] @@ -257,7 +250,7 @@ public void ParseOperationWithEmptyProducesArraySetsResponseSchemaIfExists() { // Arrange JsonNode node; - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "operationWithEmptyProducesArrayInResponse.json")); + using var stream = Resources.GetStream(Path.Join(SampleFolderPath, "operationWithEmptyProducesArrayInResponse.json")); node = TestHelper.CreateYamlJsonNode(stream); // Act @@ -293,14 +286,14 @@ public void ParseOperationWithBodyAndEmptyConsumesSetsRequestBodySchemaIfExists( { // Arrange JsonNode node; - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "operationWithBodyAndEmptyConsumes.yaml")); + using var stream = Resources.GetStream(Path.Join(SampleFolderPath, "operationWithBodyAndEmptyConsumes.yaml")); node = TestHelper.CreateYamlJsonNode(stream); // Act var operation = OpenApiV2Deserializer.LoadOperation(node, new(), new ParsingContext(new())); // Assert - operation.Should().BeEquivalentTo(_operationWithBody, options => options.IgnoringCyclicReferences()); + OpenApiTestAssert.Equivalent(_operationWithBody, operation); } [Fact] @@ -308,14 +301,14 @@ public async Task ParseV2ResponseWithExamplesExtensionWorks() { // Arrange JsonNode node; - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "opWithResponseExamplesExtension.yaml"))) + using (var stream = Resources.GetStream(Path.Join(SampleFolderPath, "opWithResponseExamplesExtension.yaml"))) { node = TestHelper.CreateYamlJsonNode(stream); } // Act var operation = OpenApiV2Deserializer.LoadOperation(node, new(), new ParsingContext(new())); - var actual = await operation.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await operation.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert var expected = @"summary: Get all pets @@ -358,14 +351,14 @@ public async Task LoadV3ExamplesInResponseAsExtensionsWorks() { // Arrange JsonNode node; - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "v3OperationWithResponseExamples.yaml"))) + using (var stream = Resources.GetStream(Path.Join(SampleFolderPath, "v3OperationWithResponseExamples.yaml"))) { node = TestHelper.CreateYamlJsonNode(stream); } // Act var operation = OpenApiV3Deserializer.LoadOperation(node, new(), new ParsingContext(new())); - var actual = await operation.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi2_0); + var actual = await operation.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi2_0, TestContext.Current.CancellationToken); // Assert var expected = @"summary: Get all pets @@ -408,14 +401,14 @@ public async Task LoadV2OperationWithBodyParameterExamplesWorks() { // Arrange JsonNode node; - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "opWithBodyParameterExamples.yaml"))) + using (var stream = Resources.GetStream(Path.Join(SampleFolderPath, "opWithBodyParameterExamples.yaml"))) { node = TestHelper.CreateYamlJsonNode(stream); } // Act var operation = OpenApiV2Deserializer.LoadOperation(node, new(), new ParsingContext(new())); - var actual = await operation.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await operation.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert var expected = @"summary: Get all pets @@ -459,14 +452,14 @@ public async Task LoadV3ExamplesInRequestBodyParameterAsExtensionsWorks() { // Arrange JsonNode node; - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "v3OperationWithBodyParameterExamples.yaml"))) + using (var stream = Resources.GetStream(Path.Join(SampleFolderPath, "v3OperationWithBodyParameterExamples.yaml"))) { node = TestHelper.CreateYamlJsonNode(stream); } // Act var operation = OpenApiV3Deserializer.LoadOperation(node, new(), new ParsingContext(new())); - var actual = await operation.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi2_0); + var actual = await operation.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi2_0, TestContext.Current.CancellationToken); // Assert var expected = @"summary: Get all pets @@ -551,7 +544,7 @@ public async Task SerializesBodyReferencesWorks() } }); - var actual = await openApiDocument.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0); + var actual = await openApiDocument.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0, TestContext.Current.CancellationToken); var expected = """ { diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiParameterTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiParameterTests.cs index 43eefc5c0..7e7f15405 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiParameterTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiParameterTests.cs @@ -3,8 +3,6 @@ // Licensed under the MIT license. using System.IO; -using FluentAssertions; -using FluentAssertions.Equivalency; using Microsoft.OpenApi.Reader; using Microsoft.OpenApi.Reader.V2; using Microsoft.OpenApi.Tests; @@ -22,7 +20,7 @@ public void ParseBodyParameterShouldSucceed() { // Arrange JsonNode node; - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "bodyParameter.yaml"))) + using (var stream = Resources.GetStream(Path.Join(SampleFolderPath, "bodyParameter.yaml"))) { node = TestHelper.CreateYamlJsonNode(stream); } @@ -41,7 +39,7 @@ public void ParsePathParameterShouldSucceed() { // Arrange JsonNode node; - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "pathParameter.yaml"))) + using (var stream = Resources.GetStream(Path.Join(SampleFolderPath, "pathParameter.yaml"))) { node = TestHelper.CreateYamlJsonNode(stream); } @@ -70,7 +68,7 @@ public void ParseQueryParameterShouldSucceed() { // Arrange JsonNode node; - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "queryParameter.yaml"))) + using (var stream = Resources.GetStream(Path.Join(SampleFolderPath, "queryParameter.yaml"))) { node = TestHelper.CreateYamlJsonNode(stream); } @@ -105,7 +103,7 @@ public void ParseParameterWithNullLocationShouldSucceed() { // Arrange JsonNode node; - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "parameterWithNullLocation.yaml"))) + using (var stream = Resources.GetStream(Path.Join(SampleFolderPath, "parameterWithNullLocation.yaml"))) { node = TestHelper.CreateYamlJsonNode(stream); } @@ -134,7 +132,7 @@ public void ParseParameterWithNoLocationShouldSucceed() { // Arrange JsonNode node; - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "parameterWithNoLocation.yaml"))) + using (var stream = Resources.GetStream(Path.Join(SampleFolderPath, "parameterWithNoLocation.yaml"))) { node = TestHelper.CreateYamlJsonNode(stream); } @@ -163,7 +161,7 @@ public void ParseParameterWithNoSchemaShouldSucceed() { // Arrange JsonNode node; - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "parameterWithNoSchema.yaml"))) + using (var stream = Resources.GetStream(Path.Join(SampleFolderPath, "parameterWithNoSchema.yaml"))) { node = TestHelper.CreateYamlJsonNode(stream); } @@ -188,7 +186,7 @@ public void ParseParameterWithUnknownLocationShouldSucceed() { // Arrange JsonNode node; - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "parameterWithUnknownLocation.yaml"))) + using (var stream = Resources.GetStream(Path.Join(SampleFolderPath, "parameterWithUnknownLocation.yaml"))) { node = TestHelper.CreateYamlJsonNode(stream); } @@ -217,7 +215,7 @@ public void ParseParameterWithDefaultShouldSucceed() { // Arrange JsonNode node; - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "parameterWithDefault.yaml"))) + using (var stream = Resources.GetStream(Path.Join(SampleFolderPath, "parameterWithDefault.yaml"))) { node = TestHelper.CreateYamlJsonNode(stream); } @@ -226,7 +224,7 @@ public void ParseParameterWithDefaultShouldSucceed() var parameter = OpenApiV2Deserializer.LoadParameter(node, new(), new ParsingContext(new())); // Assert - parameter.Should().BeEquivalentTo( + OpenApiTestAssert.Equivalent( new OpenApiParameter { In = ParameterLocation.Path, @@ -239,7 +237,7 @@ public void ParseParameterWithDefaultShouldSucceed() Format = "float", Default = new JsonNodeExtension(5).Node } - }, options => options.IgnoringCyclicReferences().Excluding(x => x.Schema.Default.Parent)); + }, parameter, nameof(JsonNode.Parent)); } [Fact] @@ -247,7 +245,7 @@ public void ParseParameterWithEnumShouldSucceed() { // Arrange JsonNode node; - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "parameterWithEnum.yaml"))) + using (var stream = Resources.GetStream(Path.Join(SampleFolderPath, "parameterWithEnum.yaml"))) { node = TestHelper.CreateYamlJsonNode(stream); } @@ -274,10 +272,7 @@ public void ParseParameterWithEnumShouldSucceed() }; // Assert - parameter.Should().BeEquivalentTo(expected, options => options - .IgnoringCyclicReferences() - .Excluding((IMemberInfo memberInfo) => - memberInfo.Path.EndsWith("Parent"))); + OpenApiTestAssert.Equivalent(expected, parameter, nameof(JsonNode.Parent)); } [Fact] @@ -290,7 +285,7 @@ public void ParseFormDataParameterShouldSucceed() ""format"": ""binary"" }"; JsonNode node; - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "formDataParameter.json"))) + using (var stream = Resources.GetStream(Path.Join(SampleFolderPath, "formDataParameter.json"))) { node = TestHelper.CreateYamlJsonNode(stream); } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiPathItemTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiPathItemTests.cs index e8b340bbb..cf9e43272 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiPathItemTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiPathItemTests.cs @@ -250,7 +250,7 @@ public class OpenApiPathItemTests public void ParseBasicPathItemWithFormDataShouldSucceed() { // Arrange - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "basicPathItemWithFormData.yaml")); + using var stream = Resources.GetStream(Path.Join(SampleFolderPath, "basicPathItemWithFormData.yaml")); var node = TestHelper.CreateYamlJsonNode(stream); // Act @@ -265,7 +265,7 @@ public void ParsePathItemWithFormDataPathParameterShouldSucceed() { // Arrange JsonNode node; - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "pathItemWithFormDataPathParameter.yaml"))) + using (var stream = Resources.GetStream(Path.Join(SampleFolderPath, "pathItemWithFormDataPathParameter.yaml"))) { node = TestHelper.CreateYamlJsonNode(stream); } @@ -284,7 +284,7 @@ public void ParsePathItemBodyDataPathParameterShouldSucceed() { // Arrange JsonNode node; - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "pathItemWithBodyPathParameter.yaml"))) + using (var stream = Resources.GetStream(Path.Join(SampleFolderPath, "pathItemWithBodyPathParameter.yaml"))) { node = TestHelper.CreateYamlJsonNode(stream); } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiSchemaTests.cs index 5afea3cb4..cbbde4b70 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiSchemaTests.cs @@ -5,8 +5,6 @@ using System.IO; using System.Text.Json.Nodes; using System.Threading.Tasks; -using FluentAssertions; -using FluentAssertions.Equivalency; using Microsoft.OpenApi.Reader; using Microsoft.OpenApi.Reader.V2; using Microsoft.OpenApi.Tests; @@ -26,7 +24,7 @@ public void ParseSchemaWithDefaultShouldSucceed() { // Arrange JsonNode node; - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "schemaWithDefault.yaml"))) + using (var stream = Resources.GetStream(Path.Join(SampleFolderPath, "schemaWithDefault.yaml"))) { node = TestHelper.CreateYamlJsonNode(stream); } @@ -35,12 +33,12 @@ public void ParseSchemaWithDefaultShouldSucceed() var schema = OpenApiV2Deserializer.LoadSchema(node, new(), new ParsingContext(new())); // Assert - schema.Should().BeEquivalentTo(new OpenApiSchema + OpenApiTestAssert.Equivalent(new OpenApiSchema { Type = JsonSchemaType.Number, Format = "float", Default = 5 - }, options => options.IgnoringCyclicReferences().Excluding(x => x.Default.Parent)); + }, schema, nameof(JsonNode.Parent)); } [Fact] @@ -48,7 +46,7 @@ public void ParseSchemaWithExampleShouldSucceed() { // Arrange JsonNode node; - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "schemaWithExample.yaml"))) + using (var stream = Resources.GetStream(Path.Join(SampleFolderPath, "schemaWithExample.yaml"))) { node = TestHelper.CreateYamlJsonNode(stream); } @@ -57,13 +55,13 @@ public void ParseSchemaWithExampleShouldSucceed() var schema = OpenApiV2Deserializer.LoadSchema(node, new(), new ParsingContext(new())); // Assert - schema.Should().BeEquivalentTo( + OpenApiTestAssert.Equivalent( new OpenApiSchema { Type = JsonSchemaType.Number, Format = "float", Example = 5 - }, options => options.IgnoringCyclicReferences().Excluding(x => x.Example.Parent)); + }, schema, nameof(JsonNode.Parent)); } [Fact] @@ -71,7 +69,7 @@ public void ParseSchemaWithEnumShouldSucceed() { // Arrange JsonNode node; - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "schemaWithEnum.yaml"))) + using (var stream = Resources.GetStream(Path.Join(SampleFolderPath, "schemaWithEnum.yaml"))) { node = TestHelper.CreateYamlJsonNode(stream); } @@ -92,10 +90,7 @@ public void ParseSchemaWithEnumShouldSucceed() ] }; - schema.Should().BeEquivalentTo(expected, options => - options.IgnoringCyclicReferences() - .Excluding((IMemberInfo memberInfo) => - memberInfo.Path.EndsWith("Parent"))); + OpenApiTestAssert.Equivalent(expected, schema, nameof(JsonNode.Parent)); } [Fact] @@ -160,10 +155,10 @@ public async Task SerializeSchemaWithNullableShouldSucceed() var expected = @"type: string x-nullable: true"; - var path = Path.Combine(SampleFolderPath, "schemaWithNullableExtension.yaml"); + var path = Path.Join(SampleFolderPath, "schemaWithNullableExtension.yaml"); // Act - var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi2_0, new(), SettingsFixture.ReaderSettings); + var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi2_0, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var writer = new StringWriter(); schema.SerializeAsV2(new OpenApiYamlWriter(writer)); @@ -180,10 +175,10 @@ public async Task SerializeSchemaWithOnlyNullableShouldSucceed() // Arrange var expected = @"{ }"; - var path = Path.Combine(SampleFolderPath, "schemaWithOnlyNullableExtension.yaml"); + var path = Path.Join(SampleFolderPath, "schemaWithOnlyNullableExtension.yaml"); // Act - var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi2_0, new(), SettingsFixture.ReaderSettings); + var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi2_0, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var writer = new StringWriter(); schema.SerializeAsV2(new OpenApiYamlWriter(writer)); diff --git a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiSecuritySchemeTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiSecuritySchemeTests.cs index 14787a086..7289f272e 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiSecuritySchemeTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V2Tests/OpenApiSecuritySchemeTests.cs @@ -22,7 +22,7 @@ public class OpenApiSecuritySchemeTests public void ParseHttpSecuritySchemeShouldSucceed() { // Arrange - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "basicSecurityScheme.yaml")); + using var stream = Resources.GetStream(Path.Join(SampleFolderPath, "basicSecurityScheme.yaml")); var document = LoadYamlDocument(stream); var diagnostic = new OpenApiDiagnostic(); @@ -47,7 +47,7 @@ public void ParseHttpSecuritySchemeShouldSucceed() public void ParseApiKeySecuritySchemeShouldSucceed() { // Arrange - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "apiKeySecurityScheme.yaml")); + using var stream = Resources.GetStream(Path.Join(SampleFolderPath, "apiKeySecurityScheme.yaml")); var document = LoadYamlDocument(stream); var diagnostic = new OpenApiDiagnostic(); var context = new ParsingContext(diagnostic); @@ -72,7 +72,7 @@ public void ParseApiKeySecuritySchemeShouldSucceed() [Fact] public void ParseOAuth2ImplicitSecuritySchemeShouldSucceed() { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "oauth2ImplicitSecurityScheme.yaml")); + using var stream = Resources.GetStream(Path.Join(SampleFolderPath, "oauth2ImplicitSecurityScheme.yaml")); var document = LoadYamlDocument(stream); var diagnostic = new OpenApiDiagnostic(); var context = new ParsingContext(diagnostic); @@ -108,7 +108,7 @@ public void ParseOAuth2ImplicitSecuritySchemeShouldSucceed() public void ParseOAuth2PasswordSecuritySchemeShouldSucceed() { // Arrange - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "oauth2PasswordSecurityScheme.yaml")); + using var stream = Resources.GetStream(Path.Join(SampleFolderPath, "oauth2PasswordSecurityScheme.yaml")); var document = LoadYamlDocument(stream); var diagnostic = new OpenApiDiagnostic(); var context = new ParsingContext(diagnostic); @@ -143,7 +143,7 @@ public void ParseOAuth2PasswordSecuritySchemeShouldSucceed() public void ParseOAuth2ApplicationSecuritySchemeShouldSucceed() { // Arrange - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "oauth2ApplicationSecurityScheme.yaml")); + using var stream = Resources.GetStream(Path.Join(SampleFolderPath, "oauth2ApplicationSecurityScheme.yaml")); var document = LoadYamlDocument(stream); var diagnostic = new OpenApiDiagnostic(); var context = new ParsingContext(diagnostic); @@ -178,7 +178,7 @@ public void ParseOAuth2ApplicationSecuritySchemeShouldSucceed() public void ParseOAuth2AccessCodeSecuritySchemeShouldSucceed() { // Arrange - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "oauth2AccessCodeSecurityScheme.yaml")); + using var stream = Resources.GetStream(Path.Join(SampleFolderPath, "oauth2AccessCodeSecurityScheme.yaml")); var document = LoadYamlDocument(stream); var diagnostic = new OpenApiDiagnostic(); diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDiscriminatorTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDiscriminatorTests.cs index 1abb7f7a9..854afad9e 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDiscriminatorTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDiscriminatorTests.cs @@ -18,10 +18,10 @@ public class OpenApiDiscriminatorTests public async Task ParseBasicDiscriminatorShouldSucceed() { // Arrange - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "basicDiscriminator.yaml")); + using var stream = Resources.GetStream(Path.Join(SampleFolderPath, "basicDiscriminator.yaml")); // Copy stream to MemoryStream using var memoryStream = new MemoryStream(); - await stream.CopyToAsync(memoryStream); + await stream.CopyToAsync(memoryStream, TestContext.Current.CancellationToken); memoryStream.Position = 0; // Act diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentSerializationTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentSerializationTests.cs index 0e2155de3..afb27144c 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentSerializationTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentSerializationTests.cs @@ -21,8 +21,8 @@ public class OpenApiDocumentSerializationTests public async Task Serialize_DoesNotMutateDom(OpenApiSpecVersion version) { // Arrange - var filePath = Path.Combine(SampleFolderPath, "docWith31properties.json"); - var (doc, _) = await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings); + var filePath = Path.Join(SampleFolderPath, "docWith31properties.json"); + var (doc, _) = await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Act: Serialize using System.Text.Json var options = new JsonSerializerOptions diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs index d5f3565c8..412037d52 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDocumentTests.cs @@ -2,7 +2,6 @@ using System.Globalization; using System.IO; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.OpenApi.Reader; using Microsoft.OpenApi.Tests; using Xunit; @@ -56,7 +55,7 @@ public void ParseDocumentWithMalformedReferenceShouldYieldExpectedDiagnostic() public async Task ParseDocumentWithWebhooksShouldSucceed() { // Arrange and Act - var actual = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "documentWithWebhooks.yaml"), SettingsFixture.ReaderSettings); + var actual = await OpenApiDocument.LoadAsync(Path.Join(SampleFolderPath, "documentWithWebhooks.yaml"), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var petSchema = new OpenApiSchemaReference("petSchema", actual.Document); var newPetSchema = new OpenApiSchemaReference("newPetSchema", actual.Document); @@ -244,14 +243,14 @@ public async Task ParseDocumentWithWebhooksShouldSucceed() // Assert Assert.Equivalent(new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_1, Format = OpenApiConstants.Yaml }, actual.Diagnostic); - actual.Document.Should().BeEquivalentTo(expected, options => options.Excluding(x => x.Workspace).Excluding(y => y.BaseUri)); + OpenApiTestAssert.Equivalent(expected, actual.Document, nameof(OpenApiDocument.Workspace), nameof(OpenApiDocument.BaseUri)); } [Fact] public async Task ParseDocumentsWithReusablePathItemInWebhooksSucceeds() { // Arrange && Act - var actual = await OpenApiDocument.LoadAsync("V31Tests/Samples/OpenApiDocument/documentWithReusablePaths.yaml", SettingsFixture.ReaderSettings); + var actual = await OpenApiDocument.LoadAsync("V31Tests/Samples/OpenApiDocument/documentWithReusablePaths.yaml", SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var components = new OpenApiComponents { @@ -446,9 +445,7 @@ public async Task ParseDocumentsWithReusablePathItemInWebhooksSucceeds() }; // Assert - actual.Document.Should().BeEquivalentTo(expected, options => options - .Excluding(x => x.Workspace) - .Excluding(y => y.BaseUri)); + OpenApiTestAssert.Equivalent(expected, actual.Document, nameof(OpenApiDocument.Workspace), nameof(OpenApiDocument.BaseUri)); Assert.Equivalent( new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_1, Format = OpenApiConstants.Yaml }, actual.Diagnostic); } @@ -461,7 +458,7 @@ public async Task ParseDocumentWithExampleInSchemaShouldSucceed() var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = false }); // Act - var actual = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "docWithExample.yaml"), SettingsFixture.ReaderSettings); + var actual = await OpenApiDocument.LoadAsync(Path.Join(SampleFolderPath, "docWithExample.yaml"), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); actual.Document.SerializeAsV31(writer); // Assert @@ -472,7 +469,7 @@ public async Task ParseDocumentWithExampleInSchemaShouldSucceed() public async Task ParseDocumentWithPatternPropertiesInSchemaWorks() { // Arrange and Act - var result = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "docWithPatternPropertiesInSchema.yaml"), SettingsFixture.ReaderSettings); + var result = await OpenApiDocument.LoadAsync(Path.Join(SampleFolderPath, "docWithPatternPropertiesInSchema.yaml"), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var actualSchema = result.Document.Paths["/example"].Operations[HttpMethod.Get].Responses["200"].Content["application/json"].Schema; var expectedSchema = new OpenApiSchema @@ -518,7 +515,7 @@ public async Task ParseDocumentWithPatternPropertiesInSchemaWorks() prop3: type: string"; - var actualMediaType = await mediaType.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_1); + var actualMediaType = await mediaType.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_1, TestContext.Current.CancellationToken); // Assert Assert.Equivalent(expectedSchema, actualSchema); @@ -529,7 +526,7 @@ public async Task ParseDocumentWithPatternPropertiesInSchemaWorks() public async Task ParseDocumentWithReferenceByIdGetsResolved() { // Arrange and Act - var result = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "docWithReferenceById.yaml"), SettingsFixture.ReaderSettings); + var result = await OpenApiDocument.LoadAsync(Path.Join(SampleFolderPath, "docWithReferenceById.yaml"), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var responseSchema = result.Document.Paths["/resource"].Operations[HttpMethod.Get].Responses["200"].Content["application/json"].Schema; var requestBodySchema = result.Document.Paths["/resource"].Operations[HttpMethod.Post].RequestBody.Content["application/json"].Schema; @@ -546,7 +543,7 @@ public async Task ExternalDocumentDereferenceToOpenApiDocumentUsingJsonPointerWo { // Arrange var documentName = "externalRefByJsonPointer.yaml"; - var path = Path.Combine(Directory.GetCurrentDirectory(), SampleFolderPath, documentName); + var path = Path.Join(Directory.GetCurrentDirectory(), SampleFolderPath, documentName); var settings = new OpenApiReaderSettings { @@ -556,13 +553,13 @@ public async Task ExternalDocumentDereferenceToOpenApiDocumentUsingJsonPointerWo settings.AddYamlReader(); // Act - var result = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, documentName), settings); + var result = await OpenApiDocument.LoadAsync(Path.Join(SampleFolderPath, documentName), settings, token: TestContext.Current.CancellationToken); var responseSchema = result.Document.Paths["/resource"].Operations[HttpMethod.Get].Responses["200"].Content["application/json"].Schema; // Assert var externalResourceUri = new Uri( "file://" + - Path.Combine(Path.GetFullPath(SampleFolderPath), + Path.Join(Path.GetFullPath(SampleFolderPath), "externalResource.yaml#/components/schemas/todo")).AbsoluteUri; Assert.True(result.Document.Workspace.Contains(externalResourceUri)); @@ -573,7 +570,7 @@ public async Task ExternalDocumentDereferenceToOpenApiDocumentUsingJsonPointerWo public async Task ParseExternalDocumentDereferenceToOpenApiDocumentByIdWorks() { // Arrange - var path = Path.Combine(Directory.GetCurrentDirectory(), SampleFolderPath); + var path = Path.Join(Directory.GetCurrentDirectory(), SampleFolderPath); var settings = new OpenApiReaderSettings { @@ -583,8 +580,8 @@ public async Task ParseExternalDocumentDereferenceToOpenApiDocumentByIdWorks() settings.AddYamlReader(); // Act - var result = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "externalRefById.yaml"), settings); - var doc2 = (await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "externalResource.yaml"), SettingsFixture.ReaderSettings)).Document; + var result = await OpenApiDocument.LoadAsync(Path.Join(SampleFolderPath, "externalRefById.yaml"), settings, token: TestContext.Current.CancellationToken); + var doc2 = (await OpenApiDocument.LoadAsync(Path.Join(SampleFolderPath, "externalResource.yaml"), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken)).Document; var requestBodySchema = result.Document.Paths["/resource"].Operations[HttpMethod.Get].Parameters[0].Schema; result.Document.Workspace.RegisterComponents(doc2); @@ -596,8 +593,8 @@ public async Task ParseExternalDocumentDereferenceToOpenApiDocumentByIdWorks() [Fact] public async Task ParseDocumentWith31PropertiesWorks() { - var path = Path.Combine(SampleFolderPath, "documentWith31Properties.yaml"); - var doc = (await OpenApiDocument.LoadAsync(path, SettingsFixture.ReaderSettings)).Document; + var path = Path.Join(SampleFolderPath, "documentWith31Properties.yaml"); + var doc = (await OpenApiDocument.LoadAsync(path, SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken)).Document; var outputStringWriter = new StringWriter(); doc.SerializeAsV31(new OpenApiYamlWriter(outputStringWriter)); await outputStringWriter.FlushAsync(); @@ -610,16 +607,16 @@ public async Task ParseDocumentWith31PropertiesWorks() [Fact] public async Task ParseDocumentWithEmptyTagsWorks() { - var path = Path.Combine(SampleFolderPath, "documentWithEmptyTags.json"); - var doc = (await OpenApiDocument.LoadAsync(path, SettingsFixture.ReaderSettings)).Document; + var path = Path.Join(SampleFolderPath, "documentWithEmptyTags.json"); + var doc = (await OpenApiDocument.LoadAsync(path, SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken)).Document; - doc.Paths["/groups"].Operations[HttpMethod.Get].Tags.Should().BeNull("Empty tags are ignored, so we should not have any tags"); + Assert.Null(doc.Paths["/groups"].Operations[HttpMethod.Get].Tags); } [Fact] public async Task DocumentWithSchemaResultsInWarning() { - var path = Path.Combine(SampleFolderPath, "documentWithSchema.json"); - var (doc, diag) = await OpenApiDocument.LoadAsync(path, SettingsFixture.ReaderSettings); + var path = Path.Join(SampleFolderPath, "documentWithSchema.json"); + var (doc, diag) = await OpenApiDocument.LoadAsync(path, SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); Assert.NotNull(doc); Assert.NotNull(diag); Assert.Empty(diag.Errors); @@ -637,8 +634,8 @@ public void ParseEmptyMemoryStreamThrowsAnArgumentException() public async Task ValidateReferencedExampleInSchemaWorks() { // Arrange && Act - var path = Path.Combine(SampleFolderPath, "docWithReferencedExampleInSchemaWorks.yaml"); - var result = await OpenApiDocument.LoadAsync(path, SettingsFixture.ReaderSettings); + var path = Path.Join(SampleFolderPath, "docWithReferencedExampleInSchemaWorks.yaml"); + var result = await OpenApiDocument.LoadAsync(path, SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var actualSchemaExample = result.Document.Components.Schemas["DiffCreatedEvent"].Properties["updatedAt"].Example; var targetSchemaExample = result.Document.Components.Schemas["Timestamp"].Example; @@ -652,8 +649,8 @@ public async Task ValidateReferencedExampleInSchemaWorks() public async Task ParseDocumentWithSelfExtensionWorks() { // Arrange && Act - var path = Path.Combine(SampleFolderPath, "documentWithSelfExtension.yaml"); - var result = await OpenApiDocument.LoadAsync(path, SettingsFixture.ReaderSettings); + var path = Path.Join(SampleFolderPath, "documentWithSelfExtension.yaml"); + var result = await OpenApiDocument.LoadAsync(path, SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.NotNull(result.Document); diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDynamicRefTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDynamicRefTests.cs index 49921034c..a862c971b 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDynamicRefTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiDynamicRefTests.cs @@ -626,8 +626,8 @@ public async Task AbsoluteDynamicRefResolvesAcrossDocuments() using var streamA = new MemoryStream(Encoding.UTF8.GetBytes(yamlA)); using var streamB = new MemoryStream(Encoding.UTF8.GetBytes(yamlB)); - var resultA = await OpenApiDocument.LoadAsync(streamA, "yaml", SettingsFixture.ReaderSettings); - var resultB = await OpenApiDocument.LoadAsync(streamB, "yaml", SettingsFixture.ReaderSettings); + var resultA = await OpenApiDocument.LoadAsync(streamA, "yaml", SettingsFixture.ReaderSettings, cancellationToken: TestContext.Current.CancellationToken); + var resultB = await OpenApiDocument.LoadAsync(streamB, "yaml", SettingsFixture.ReaderSettings, cancellationToken: TestContext.Current.CancellationToken); var docA = resultA.Document; var docB = resultB.Document; @@ -1485,8 +1485,8 @@ public async Task ExternalDynamicRefResolvesToPlainAnchorInExternalDocument() using var streamA = new MemoryStream(Encoding.UTF8.GetBytes(yamlA)); using var streamB = new MemoryStream(Encoding.UTF8.GetBytes(yamlB)); - var resultA = await OpenApiDocument.LoadAsync(streamA, "yaml", SettingsFixture.ReaderSettings); - var resultB = await OpenApiDocument.LoadAsync(streamB, "yaml", SettingsFixture.ReaderSettings); + var resultA = await OpenApiDocument.LoadAsync(streamA, "yaml", SettingsFixture.ReaderSettings, cancellationToken: TestContext.Current.CancellationToken); + var resultB = await OpenApiDocument.LoadAsync(streamB, "yaml", SettingsFixture.ReaderSettings, cancellationToken: TestContext.Current.CancellationToken); var docA = resultA.Document; var docB = resultB.Document; @@ -1545,8 +1545,8 @@ public async Task ExternalDynamicRefWithRelativeUriResolvesAcrossDocuments() using var streamA = new MemoryStream(Encoding.UTF8.GetBytes(yamlA)); using var streamB = new MemoryStream(Encoding.UTF8.GetBytes(yamlB)); - var resultA = await OpenApiDocument.LoadAsync(streamA, "yaml", SettingsFixture.ReaderSettings); - var resultB = await OpenApiDocument.LoadAsync(streamB, "yaml", SettingsFixture.ReaderSettings); + var resultA = await OpenApiDocument.LoadAsync(streamA, "yaml", SettingsFixture.ReaderSettings, cancellationToken: TestContext.Current.CancellationToken); + var resultB = await OpenApiDocument.LoadAsync(streamB, "yaml", SettingsFixture.ReaderSettings, cancellationToken: TestContext.Current.CancellationToken); var docA = resultA.Document; var docB = resultB.Document; diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiEncodingTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiEncodingTests.cs index 6a03ea015..9ec9f9199 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiEncodingTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiEncodingTests.cs @@ -17,7 +17,7 @@ public class OpenApiEncodingTests public async Task ParseEncodingWithAllowReservedShouldSucceed() { // Act - var encoding = await OpenApiModelFactory.LoadAsync(Path.Combine(SampleFolderPath, "encodingWithAllowReserved.yaml"), OpenApiSpecVersion.OpenApi3_1, new(), SettingsFixture.ReaderSettings); + var encoding = await OpenApiModelFactory.LoadAsync(Path.Join(SampleFolderPath, "encodingWithAllowReserved.yaml"), OpenApiSpecVersion.OpenApi3_1, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.Equivalent( diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiExampleTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiExampleTests.cs index 1f74aa4b3..7a2baacdb 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiExampleTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiExampleTests.cs @@ -19,10 +19,10 @@ public async Task ParseExampleWithDataValueExtensionShouldSucceed() { // Arrange & Act var example = await OpenApiModelFactory.LoadAsync( - Path.Combine(SampleFolderPath, "exampleWithDataValue.yaml"), + Path.Join(SampleFolderPath, "exampleWithDataValue.yaml"), OpenApiSpecVersion.OpenApi3_1, new(), - SettingsFixture.ReaderSettings); + SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.NotNull(example); @@ -37,10 +37,10 @@ public async Task ParseExampleWithSerializedValueExtensionShouldSucceed() { // Arrange & Act var example = await OpenApiModelFactory.LoadAsync( - Path.Combine(SampleFolderPath, "exampleWithSerializedValue.yaml"), + Path.Join(SampleFolderPath, "exampleWithSerializedValue.yaml"), OpenApiSpecVersion.OpenApi3_1, new(), - SettingsFixture.ReaderSettings); + SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.NotNull(example); diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiHeaderTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiHeaderTests.cs index a15059ab5..41d65f610 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiHeaderTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiHeaderTests.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.IO; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.OpenApi.Reader; using Xunit; @@ -19,10 +18,10 @@ public class OpenApiHeaderTests public async Task ParseBasicHeaderShouldSucceed() { // Arrange - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "basicHeader.yaml")); + using var stream = Resources.GetStream(Path.Join(SampleFolderPath, "basicHeader.yaml")); // Act - var header = await OpenApiModelFactory.LoadAsync(stream, OpenApiSpecVersion.OpenApi3_1, new(), settings: SettingsFixture.ReaderSettings); + var header = await OpenApiModelFactory.LoadAsync(stream, OpenApiSpecVersion.OpenApi3_1, new(), settings: SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.Equivalent( @@ -40,10 +39,10 @@ public async Task ParseBasicHeaderShouldSucceed() public async Task ParseHeaderWithContentShouldSucceed() { // Arrange - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "headerWithContent.yaml")); + using var stream = Resources.GetStream(Path.Join(SampleFolderPath, "headerWithContent.yaml")); // Act - var header = await OpenApiModelFactory.LoadAsync(stream, OpenApiSpecVersion.OpenApi3_1, new(), settings: SettingsFixture.ReaderSettings); + var header = await OpenApiModelFactory.LoadAsync(stream, OpenApiSpecVersion.OpenApi3_1, new(), settings: SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.Equivalent( @@ -79,10 +78,10 @@ public async Task ParseHeaderWithContentShouldSucceed() public async Task ParseHeaderWithMultipleContentTypesShouldSucceed() { // Arrange - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "headerWithMultipleContentTypes.yaml")); + using var stream = Resources.GetStream(Path.Join(SampleFolderPath, "headerWithMultipleContentTypes.yaml")); // Act - var header = await OpenApiModelFactory.LoadAsync(stream, OpenApiSpecVersion.OpenApi3_1, new(), settings: SettingsFixture.ReaderSettings); + var header = await OpenApiModelFactory.LoadAsync(stream, OpenApiSpecVersion.OpenApi3_1, new(), settings: SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.Equivalent( @@ -120,10 +119,10 @@ public async Task ParseHeaderWithMultipleContentTypesShouldSucceed() public async Task ParseHeaderWithStyleAndContentShouldPreferContent() { // Arrange - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "headerWithStyleAndContent.yaml")); + using var stream = Resources.GetStream(Path.Join(SampleFolderPath, "headerWithStyleAndContent.yaml")); // Act - var header = await OpenApiModelFactory.LoadAsync(stream, OpenApiSpecVersion.OpenApi3_1, new(), settings: SettingsFixture.ReaderSettings); + var header = await OpenApiModelFactory.LoadAsync(stream, OpenApiSpecVersion.OpenApi3_1, new(), settings: SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert // Both content and style can be present, content takes precedence for serialization behavior diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiInfoTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiInfoTests.cs index 1af2aca13..a1bcea365 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiInfoTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiInfoTests.cs @@ -15,7 +15,7 @@ public class OpenApiInfoTests [Fact] public void ParseBasicInfoShouldSucceed() { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "basicInfo.yaml")); + using var stream = Resources.GetStream(Path.Join(SampleFolderPath, "basicInfo.yaml")); var yamlStream = new YamlStream(); yamlStream.Load(new StreamReader(stream)); var yamlNode = yamlStream.Documents[0].RootNode; diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiLicenseTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiLicenseTests.cs index 26d841811..33df2da48 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiLicenseTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiLicenseTests.cs @@ -18,7 +18,7 @@ public class OpenApiLicenseTests [Fact] public void ParseLicenseWithSpdxIdentifierShouldSucceed() { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "licenseWithSpdxIdentifier.yaml")); + using var stream = Resources.GetStream(Path.Join(SampleFolderPath, "licenseWithSpdxIdentifier.yaml")); var yamlStream = new YamlStream(); yamlStream.Load(new StreamReader(stream)); var yamlNode = yamlStream.Documents[0].RootNode; diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiMediaTypeTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiMediaTypeTests.cs index 39ea5420d..096627e08 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiMediaTypeTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiMediaTypeTests.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.IO; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.OpenApi.Reader; using Microsoft.OpenApi.Reader.V31; using Microsoft.OpenApi.Tests; @@ -22,10 +21,10 @@ public class OpenApiMediaTypeTests public async Task ParseMediaTypeWithExampleShouldSucceed() { // Act - var mediaType = await OpenApiModelFactory.LoadAsync(Path.Combine(SampleFolderPath, "mediaTypeWithExample.yaml"), OpenApiSpecVersion.OpenApi3_1, new(), SettingsFixture.ReaderSettings); + var mediaType = await OpenApiModelFactory.LoadAsync(Path.Join(SampleFolderPath, "mediaTypeWithExample.yaml"), OpenApiSpecVersion.OpenApi3_1, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert - mediaType.Should().BeEquivalentTo( + OpenApiTestAssert.Equivalent( new OpenApiMediaType { Example = 5, @@ -34,19 +33,17 @@ public async Task ParseMediaTypeWithExampleShouldSucceed() Type = JsonSchemaType.Number, Format = "float" } - }, options => options.IgnoringCyclicReferences() - .Excluding(m => m.Example.Parent) - ); + }, mediaType, nameof(JsonNode.Parent)); } [Fact] public async Task ParseMediaTypeWithExamplesShouldSucceed() { // Act - var mediaType = await OpenApiModelFactory.LoadAsync(Path.Combine(SampleFolderPath, "mediaTypeWithExamples.yaml"), OpenApiSpecVersion.OpenApi3_1, new(), SettingsFixture.ReaderSettings); + var mediaType = await OpenApiModelFactory.LoadAsync(Path.Join(SampleFolderPath, "mediaTypeWithExamples.yaml"), OpenApiSpecVersion.OpenApi3_1, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert - mediaType.Should().BeEquivalentTo( + OpenApiTestAssert.Equivalent( new OpenApiMediaType { Examples = new Dictionary @@ -65,9 +62,7 @@ public async Task ParseMediaTypeWithExamplesShouldSucceed() Type = JsonSchemaType.Number, Format = "float" } - }, options => options.IgnoringCyclicReferences() - .Excluding(m => m.Examples["example1"].Value.Parent) - .Excluding(m => m.Examples["example2"].Value.Parent)); + }, mediaType, nameof(JsonNode.Parent)); } [Fact] @@ -103,14 +98,14 @@ public async Task ParseMediaTypeWithEmptyArrayInExamplesWorks() } "; JsonNode node; - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "examplesWithEmptyArray.json"))) + using (var stream = Resources.GetStream(Path.Join(SampleFolderPath, "examplesWithEmptyArray.json"))) { node = TestHelper.CreateYamlJsonNode(stream); } // Act var mediaType = OpenApiV31Deserializer.LoadMediaType(node, new(), new ParsingContext(new())); - var serialized = await mediaType.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1); + var serialized = await mediaType.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1, TestContext.Current.CancellationToken); // Assert Assert.Equal(expected.MakeLineBreaksEnvironmentNeutral(), serialized.MakeLineBreaksEnvironmentNeutral()); @@ -121,10 +116,10 @@ public async Task ParseMediaTypeWithXOaiItemSchemaShouldSucceed() { // Act var mediaType = await OpenApiModelFactory.LoadAsync( - Path.Combine(SampleFolderPath, "mediaTypeWithXOaiItemSchema.yaml"), + Path.Join(SampleFolderPath, "mediaTypeWithXOaiItemSchema.yaml"), OpenApiSpecVersion.OpenApi3_1, new(), - SettingsFixture.ReaderSettings); + SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert var expected = new OpenApiMediaType @@ -147,10 +142,10 @@ public async Task ParseMediaTypeWithXOaiItemEncodingShouldSucceed() { // Act var mediaType = await OpenApiModelFactory.LoadAsync( - Path.Combine(SampleFolderPath, "mediaTypeWithXOaiItemEncoding.yaml"), + Path.Join(SampleFolderPath, "mediaTypeWithXOaiItemEncoding.yaml"), OpenApiSpecVersion.OpenApi3_1, new(), - SettingsFixture.ReaderSettings); + SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.NotNull(mediaType); @@ -166,10 +161,10 @@ public async Task ParseMediaTypeWithXOaiPrefixEncodingShouldSucceed() { // Act var mediaType = await OpenApiModelFactory.LoadAsync( - Path.Combine(SampleFolderPath, "mediaTypeWithXOaiPrefixEncoding.yaml"), + Path.Join(SampleFolderPath, "mediaTypeWithXOaiPrefixEncoding.yaml"), OpenApiSpecVersion.OpenApi3_1, new(), - SettingsFixture.ReaderSettings); + SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.NotNull(mediaType); diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiPathItemDeserializerTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiPathItemDeserializerTests.cs index 48de4d075..0257418a5 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiPathItemDeserializerTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiPathItemDeserializerTests.cs @@ -17,7 +17,7 @@ public class OpenApiPathItemDeserializerTests public async Task ExtraneousOperationsAreParsedAsExtensionsIn31() { // Arrange & Act - var result = await OpenApiDocument.LoadAsync($"{SampleFolderPath}pathItemWithQueryAndAdditionalOperations.yaml", SettingsFixture.ReaderSettings); + var result = await OpenApiDocument.LoadAsync($"{SampleFolderPath}pathItemWithQueryAndAdditionalOperations.yaml", SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var pathItem = result.Document.Paths["/pets"]; // Assert diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaTests.cs index fe4c74ff5..caffb3e3e 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSchemaTests.cs @@ -8,8 +8,6 @@ using System.Linq; using System.Text.Json.Nodes; using System.Threading.Tasks; -using FluentAssertions; -using FluentAssertions.Equivalency; using Microsoft.OpenApi.Reader; using Microsoft.OpenApi.Tests; using Xunit; @@ -25,7 +23,7 @@ public class OpenApiSchemaTests public static MemoryStream GetMemoryStream(string fileName) { - var filePath = Path.Combine(SampleFolderPath, fileName); + var filePath = Path.Join(SampleFolderPath, fileName); var fileBytes = File.ReadAllBytes(filePath); return new MemoryStream(fileBytes); } @@ -108,7 +106,7 @@ public async Task ParseBasicV31SchemaShouldSucceed() // Act var schema = await OpenApiModelFactory.LoadAsync( - Path.Combine(SampleFolderPath, "jsonSchema.json"), OpenApiSpecVersion.OpenApi3_1, new(), SettingsFixture.ReaderSettings); + Path.Join(SampleFolderPath, "jsonSchema.json"), OpenApiSpecVersion.OpenApi3_1, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.Equivalent(expectedObject, schema); @@ -192,10 +190,10 @@ public void TestSchemaCopyConstructorWithTypeArrayWorks() [Fact] public async Task ParseV31SchemaShouldSucceed() { - var path = Path.Combine(SampleFolderPath, "schema.yaml"); + var path = Path.Join(SampleFolderPath, "schema.yaml"); // Act - var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi3_1, new(), SettingsFixture.ReaderSettings); + var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi3_1, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var expectedSchema = new OpenApiSchema { Type = JsonSchemaType.Object, @@ -217,8 +215,8 @@ public async Task ParseV31SchemaShouldSucceed() public async Task ParseAdvancedV31SchemaShouldSucceed() { // Arrange and Act - var path = Path.Combine(SampleFolderPath, "advancedSchema.yaml"); - var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi3_1, new(), SettingsFixture.ReaderSettings); + var path = Path.Join(SampleFolderPath, "advancedSchema.yaml"); + var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi3_1, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var expectedSchema = new OpenApiSchema { @@ -292,10 +290,7 @@ public async Task ParseAdvancedV31SchemaShouldSucceed() }; // Assert - schema.Should().BeEquivalentTo(expectedSchema, options => options - .IgnoringCyclicReferences() - .Excluding((IMemberInfo memberInfo) => - memberInfo.Path.EndsWith("Parent"))); + OpenApiTestAssert.Equivalent(expectedSchema, schema, nameof(JsonNode.Parent)); } [Fact] @@ -442,10 +437,10 @@ public async Task SerializeV31SchemaWithMultipleTypesAsV3Works() var expected = @"type: string nullable: true"; - var path = Path.Combine(SampleFolderPath, "schemaWithTypeArray.yaml"); + var path = Path.Join(SampleFolderPath, "schemaWithTypeArray.yaml"); // Act - var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi3_1, new(), SettingsFixture.ReaderSettings); + var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi3_1, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var writer = new StringWriter(); schema.SerializeAsV3(new OpenApiYamlWriter(writer)); @@ -461,10 +456,10 @@ public async Task SerializeV31SchemaWithMultipleTypesAsV2Works() var expected = @"type: string x-nullable: true"; - var path = Path.Combine(SampleFolderPath, "schemaWithTypeArray.yaml"); + var path = Path.Join(SampleFolderPath, "schemaWithTypeArray.yaml"); // Act - var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi3_1, new(), SettingsFixture.ReaderSettings); + var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi3_1, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var writer = new StringWriter(); schema.SerializeAsV2(new OpenApiYamlWriter(writer)); @@ -481,10 +476,10 @@ public async Task SerializeV3SchemaWithNullableAsV31Works() - 'null' - string"; - var path = Path.Combine(SampleFolderPath, "schemaWithNullable.yaml"); + var path = Path.Join(SampleFolderPath, "schemaWithNullable.yaml"); // Act - var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi3_0, new(), SettingsFixture.ReaderSettings); + var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi3_0, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var writer = new StringWriter(); schema.SerializeAsV31(new OpenApiYamlWriter(writer)); @@ -501,10 +496,10 @@ public async Task SerializeV2SchemaWithNullableExtensionAsV31Works() - 'null' - string"; - var path = Path.Combine(SampleFolderPath, "schemaWithNullableExtension.yaml"); + var path = Path.Join(SampleFolderPath, "schemaWithNullableExtension.yaml"); // Act - var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi2_0, new(), SettingsFixture.ReaderSettings); + var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi2_0, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var writer = new StringWriter(); schema.SerializeAsV31(new OpenApiYamlWriter(writer)); @@ -540,10 +535,10 @@ public async Task LoadSchemaWithNullableExtensionAsV31ShouldNotWork(string fileP // "nullable" is only for 3.0. // and "x-nullable" is only for 2.0. // Arrange - var path = Path.Combine(SampleFolderPath, filePath); + var path = Path.Join(SampleFolderPath, filePath); // Act - var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi3_1, new(), SettingsFixture.ReaderSettings); + var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi3_1, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.Equal(JsonSchemaType.String, schema.Type); @@ -580,10 +575,10 @@ public async Task SerializeSchemaWithJsonSchemaKeywordsWorks() $dynamicRef: '#addressDef' description: Schema for a person object "; - var path = Path.Combine(SampleFolderPath, "schemaWithJsonSchemaKeywords.yaml"); + var path = Path.Join(SampleFolderPath, "schemaWithJsonSchemaKeywords.yaml"); // Act - var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi3_1, new(), SettingsFixture.ReaderSettings); + var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi3_1, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // serialization var writer = new StringWriter(); @@ -624,10 +619,10 @@ public async Task ParseSchemaWithConstWorks() } }"; - var path = Path.Combine(SampleFolderPath, "schemaWithConst.json"); + var path = Path.Join(SampleFolderPath, "schemaWithConst.json"); // Act - var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi3_1, new(), SettingsFixture.ReaderSettings); + var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi3_1, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); Assert.Equal("active", schema.Properties["status"].Const); Assert.Equal("admin", schema.Properties["user"].Properties["role"].Const); @@ -667,10 +662,10 @@ public async Task ParseSchemaWithConstNullWorks() } }"; - var path = Path.Combine(SampleFolderPath, "schemaWithConstNull.json"); + var path = Path.Join(SampleFolderPath, "schemaWithConstNull.json"); // Act - var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi3_1, new(), SettingsFixture.ReaderSettings); + var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi3_1, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var statusSchema = Assert.IsType(schema.Properties["status"]); Assert.Null(statusSchema.Const); @@ -1020,7 +1015,7 @@ public async Task ParseSchemaReferencePreservesJsonSchema2020KeywordSiblings() // Act using var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(yaml)); - var result = await OpenApiDocument.LoadAsync(stream, "yaml", SettingsFixture.ReaderSettings); + var result = await OpenApiDocument.LoadAsync(stream, "yaml", SettingsFixture.ReaderSettings, cancellationToken: TestContext.Current.CancellationToken); Assert.NotNull(result.Document.Components); Assert.NotNull(result.Document.Components.Schemas); var referencing = result.Document.Components.Schemas["Referencing"]; @@ -1062,14 +1057,14 @@ public async Task SerializeSchemaReferencePreservesJsonSchema2020KeywordSiblings // Act — parse then serialize back using var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(yaml)); - var result = await OpenApiDocument.LoadAsync(stream, "yaml", SettingsFixture.ReaderSettings); + var result = await OpenApiDocument.LoadAsync(stream, "yaml", SettingsFixture.ReaderSettings, cancellationToken: TestContext.Current.CancellationToken); var writer = new StringWriter(); result.Document.SerializeAsV31(new OpenApiYamlWriter(writer)); var output = writer.ToString(); // Assert — round-trip preserves $dynamicAnchor and $defs alongside $ref using var roundTripStream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(output)); - var roundTripResult = await OpenApiDocument.LoadAsync(roundTripStream, "yaml", SettingsFixture.ReaderSettings); + var roundTripResult = await OpenApiDocument.LoadAsync(roundTripStream, "yaml", SettingsFixture.ReaderSettings, cancellationToken: TestContext.Current.CancellationToken); Assert.NotNull(roundTripResult.Document.Components); Assert.NotNull(roundTripResult.Document.Components.Schemas); var referencing = roundTripResult.Document.Components.Schemas["Referencing"]; @@ -1106,7 +1101,7 @@ public async Task ParseSchemaReferencePreservesScalarKeywordSiblings() // Act using var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(yaml)); - var result = await OpenApiDocument.LoadAsync(stream, "yaml", SettingsFixture.ReaderSettings); + var result = await OpenApiDocument.LoadAsync(stream, "yaml", SettingsFixture.ReaderSettings, cancellationToken: TestContext.Current.CancellationToken); Assert.NotNull(result.Document.Components); Assert.NotNull(result.Document.Components.Schemas); var referencing = result.Document.Components.Schemas["Referencing"]; @@ -1145,14 +1140,14 @@ public async Task SerializeSchemaReferencePreservesScalarKeywordSiblings() // Act — parse then serialize back using var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(yaml)); - var result = await OpenApiDocument.LoadAsync(stream, "yaml", SettingsFixture.ReaderSettings); + var result = await OpenApiDocument.LoadAsync(stream, "yaml", SettingsFixture.ReaderSettings, cancellationToken: TestContext.Current.CancellationToken); var writer = new StringWriter(); result.Document.SerializeAsV31(new OpenApiYamlWriter(writer)); var output = writer.ToString(); // Assert — round-trip preserves scalar siblings alongside $ref using var roundTripStream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(output)); - var roundTripResult = await OpenApiDocument.LoadAsync(roundTripStream, "yaml", SettingsFixture.ReaderSettings); + var roundTripResult = await OpenApiDocument.LoadAsync(roundTripStream, "yaml", SettingsFixture.ReaderSettings, cancellationToken: TestContext.Current.CancellationToken); Assert.NotNull(roundTripResult.Document.Components); Assert.NotNull(roundTripResult.Document.Components.Schemas); var referencing = roundTripResult.Document.Components.Schemas["Referencing"]; @@ -1188,7 +1183,7 @@ public async Task ParseSchemaReferencePreservesVocabularySibling() // Act using var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(yaml)); - var result = await OpenApiDocument.LoadAsync(stream, "yaml", SettingsFixture.ReaderSettings); + var result = await OpenApiDocument.LoadAsync(stream, "yaml", SettingsFixture.ReaderSettings, cancellationToken: TestContext.Current.CancellationToken); Assert.NotNull(result.Document.Components); Assert.NotNull(result.Document.Components.Schemas); var referencing = result.Document.Components.Schemas["Referencing"]; @@ -1237,7 +1232,7 @@ public async Task ParseSchemaReferencePreservesDynamicAnchorInsideDefsInAllOf() // Act using var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(yaml)); - var result = await OpenApiDocument.LoadAsync(stream, "yaml", SettingsFixture.ReaderSettings); + var result = await OpenApiDocument.LoadAsync(stream, "yaml", SettingsFixture.ReaderSettings, cancellationToken: TestContext.Current.CancellationToken); Assert.NotNull(result.Document.Components); Assert.NotNull(result.Document.Components.Schemas); var assetPaged = result.Document.Components.Schemas["AssetPaged"]; @@ -1284,7 +1279,7 @@ public async Task EmptySiblingCollectionsFallThroughToTarget() // Act using var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(yaml)); - var result = await OpenApiDocument.LoadAsync(stream, "yaml", SettingsFixture.ReaderSettings); + var result = await OpenApiDocument.LoadAsync(stream, "yaml", SettingsFixture.ReaderSettings, cancellationToken: TestContext.Current.CancellationToken); Assert.NotNull(result.Document.Components); Assert.NotNull(result.Document.Components.Schemas); var referencing = result.Document.Components.Schemas["Referencing"]; @@ -1326,7 +1321,7 @@ public async Task SiblingsOnRefAreDroppedForOpenApi30() // Act using var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(yaml)); - var result = await OpenApiDocument.LoadAsync(stream, "yaml", SettingsFixture.ReaderSettings); + var result = await OpenApiDocument.LoadAsync(stream, "yaml", SettingsFixture.ReaderSettings, cancellationToken: TestContext.Current.CancellationToken); Assert.NotNull(result.Document.Components); Assert.NotNull(result.Document.Components.Schemas); var referencing = result.Document.Components.Schemas["Referencing"]; @@ -1360,14 +1355,14 @@ public async Task SerializeSchemaReferencePreservesVocabularySibling() // Act — parse then serialize back using var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(yaml)); - var result = await OpenApiDocument.LoadAsync(stream, "yaml", SettingsFixture.ReaderSettings); + var result = await OpenApiDocument.LoadAsync(stream, "yaml", SettingsFixture.ReaderSettings, cancellationToken: TestContext.Current.CancellationToken); var writer = new StringWriter(); result.Document.SerializeAsV31(new OpenApiYamlWriter(writer)); var output = writer.ToString(); // Assert — round-trip preserves $vocabulary alongside $ref using var roundTripStream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(output)); - var roundTripResult = await OpenApiDocument.LoadAsync(roundTripStream, "yaml", SettingsFixture.ReaderSettings); + var roundTripResult = await OpenApiDocument.LoadAsync(roundTripStream, "yaml", SettingsFixture.ReaderSettings, cancellationToken: TestContext.Current.CancellationToken); Assert.NotNull(roundTripResult.Document.Components); Assert.NotNull(roundTripResult.Document.Components.Schemas); var referencing = roundTripResult.Document.Components.Schemas["Referencing"]; @@ -1404,7 +1399,7 @@ public async Task CreateShallowCopyPreservesKeywordSiblings() // Act using var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(yaml)); - var result = await OpenApiDocument.LoadAsync(stream, "yaml", SettingsFixture.ReaderSettings); + var result = await OpenApiDocument.LoadAsync(stream, "yaml", SettingsFixture.ReaderSettings, cancellationToken: TestContext.Current.CancellationToken); Assert.NotNull(result.Document.Components); Assert.NotNull(result.Document.Components.Schemas); var referencing = result.Document.Components.Schemas["Referencing"]; diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSecuritySchemeTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSecuritySchemeTests.cs index 75d204097..49720fe3f 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSecuritySchemeTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/OpenApiSecuritySchemeTests.cs @@ -17,7 +17,7 @@ public class OpenApiSecuritySchemeTests public async Task ParseHttpSecuritySchemeShouldSucceed() { // Act - var securityScheme = await OpenApiModelFactory.LoadAsync(Path.Combine(SampleFolderPath, "httpSecurityScheme.yaml"), OpenApiSpecVersion.OpenApi3_1, new(), SettingsFixture.ReaderSettings); + var securityScheme = await OpenApiModelFactory.LoadAsync(Path.Join(SampleFolderPath, "httpSecurityScheme.yaml"), OpenApiSpecVersion.OpenApi3_1, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.Equivalent( @@ -32,7 +32,7 @@ public async Task ParseHttpSecuritySchemeShouldSucceed() public async Task ParseApiKeySecuritySchemeShouldSucceed() { // Act - var securityScheme = await OpenApiModelFactory.LoadAsync(Path.Combine(SampleFolderPath, "apiKeySecurityScheme.yaml"), OpenApiSpecVersion.OpenApi3_1, new(), SettingsFixture.ReaderSettings); + var securityScheme = await OpenApiModelFactory.LoadAsync(Path.Join(SampleFolderPath, "apiKeySecurityScheme.yaml"), OpenApiSpecVersion.OpenApi3_1, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.Equivalent( @@ -48,7 +48,7 @@ public async Task ParseApiKeySecuritySchemeShouldSucceed() public async Task ParseBearerSecuritySchemeShouldSucceed() { // Act - var securityScheme = await OpenApiModelFactory.LoadAsync(Path.Combine(SampleFolderPath, "bearerSecurityScheme.yaml"), OpenApiSpecVersion.OpenApi3_1, new(), SettingsFixture.ReaderSettings); + var securityScheme = await OpenApiModelFactory.LoadAsync(Path.Join(SampleFolderPath, "bearerSecurityScheme.yaml"), OpenApiSpecVersion.OpenApi3_1, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.Equivalent( @@ -64,7 +64,7 @@ public async Task ParseBearerSecuritySchemeShouldSucceed() public async Task ParseOAuth2SecuritySchemeShouldSucceed() { // Act - var securityScheme = await OpenApiModelFactory.LoadAsync(Path.Combine(SampleFolderPath, "oauth2SecurityScheme.yaml"), OpenApiSpecVersion.OpenApi3_1, new(), SettingsFixture.ReaderSettings); + var securityScheme = await OpenApiModelFactory.LoadAsync(Path.Join(SampleFolderPath, "oauth2SecurityScheme.yaml"), OpenApiSpecVersion.OpenApi3_1, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.Equivalent( @@ -90,7 +90,7 @@ public async Task ParseOAuth2SecuritySchemeShouldSucceed() public async Task ParseOpenIdConnectSecuritySchemeShouldSucceed() { // Act - var securityScheme = await OpenApiModelFactory.LoadAsync(Path.Combine(SampleFolderPath, "openIdConnectSecurityScheme.yaml"), OpenApiSpecVersion.OpenApi3_1, new(), SettingsFixture.ReaderSettings); + var securityScheme = await OpenApiModelFactory.LoadAsync(Path.Join(SampleFolderPath, "openIdConnectSecurityScheme.yaml"), OpenApiSpecVersion.OpenApi3_1, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.Equivalent( @@ -107,10 +107,10 @@ public async Task ParseOAuth2SecuritySchemeWithDeviceAuthorizationUrlShouldSucce { // Act var securityScheme = await OpenApiModelFactory.LoadAsync( - Path.Combine(SampleFolderPath, "oauth2SecuritySchemeWithDeviceUrl.yaml"), + Path.Join(SampleFolderPath, "oauth2SecuritySchemeWithDeviceUrl.yaml"), OpenApiSpecVersion.OpenApi3_1, new(), - SettingsFixture.ReaderSettings); + SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.NotNull(securityScheme); diff --git a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/RelativeReferenceTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/RelativeReferenceTests.cs index 4eebac2f5..d829c4d5a 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V31Tests/RelativeReferenceTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V31Tests/RelativeReferenceTests.cs @@ -17,10 +17,10 @@ public class RelativeReferenceTests public async Task ParseInlineLocalReferenceWorks() { // Arrange - var filePath = Path.Combine(SampleFolderPath, "inlineLocalReference.yaml"); + var filePath = Path.Join(SampleFolderPath, "inlineLocalReference.yaml"); // Act - var actual = (await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings)).Document; + var actual = (await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken)).Document; var schemaType = actual.Paths["/item"].Operations[HttpMethod.Get].Parameters[0].Schema.Type; // Assert @@ -40,7 +40,7 @@ public async Task ParseInlineExternalReferenceWorks() } }; - var path = Path.Combine(Directory.GetCurrentDirectory(), SampleFolderPath); + var path = Path.Join(Directory.GetCurrentDirectory(), SampleFolderPath); var settings = new OpenApiReaderSettings { @@ -50,7 +50,7 @@ public async Task ParseInlineExternalReferenceWorks() settings.AddYamlReader(); // Act - var actual = (await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "inlineExternalReference.yaml"), settings)).Document; + var actual = (await OpenApiDocument.LoadAsync(Path.Join(SampleFolderPath, "inlineExternalReference.yaml"), settings, token: TestContext.Current.CancellationToken)).Document; var exampleValue = actual.Paths["/items"].Operations[HttpMethod.Get].Responses["200"].Content["application/json"].Examples["item-list"].Value; // Assert @@ -63,7 +63,7 @@ public async Task ParseInlineExternalReferenceWorks() public async Task ParseComponentExternalReferenceWorks() { // Arrange - var path = Path.Combine(Directory.GetCurrentDirectory(), SampleFolderPath); + var path = Path.Join(Directory.GetCurrentDirectory(), SampleFolderPath); var settings = new OpenApiReaderSettings { LoadExternalRefs = true, @@ -72,7 +72,7 @@ public async Task ParseComponentExternalReferenceWorks() settings.AddYamlReader(); // Act - var actual = (await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "componentExternalReference.yaml"), settings)).Document; + var actual = (await OpenApiDocument.LoadAsync(Path.Join(SampleFolderPath, "componentExternalReference.yaml"), settings, token: TestContext.Current.CancellationToken)).Document; var securitySchemeValue = actual.Components.SecuritySchemes["customapikey"]; // Assert @@ -83,10 +83,10 @@ public async Task ParseComponentExternalReferenceWorks() public async Task ParseRootInlineJsonSchemaReferenceWorks() { // Arrange - var filePath = Path.Combine(SampleFolderPath, "rootInlineSchemaReference.yaml"); + var filePath = Path.Join(SampleFolderPath, "rootInlineSchemaReference.yaml"); // Act - var actual = (await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings)).Document; + var actual = (await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken)).Document; var schema = actual.Paths["/item"].Operations[HttpMethod.Get].Responses["200"].Content["application/json"].Schema; // Assert @@ -97,10 +97,10 @@ public async Task ParseRootInlineJsonSchemaReferenceWorks() public async Task ParseSubschemaInlineJsonSchemaReferenceWorks() { // Arrange - var filePath = Path.Combine(SampleFolderPath, "subschemaInlineSchemaReference.yaml"); + var filePath = Path.Join(SampleFolderPath, "subschemaInlineSchemaReference.yaml"); // Act - var actual = (await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings)).Document; + var actual = (await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken)).Document; var schema = actual.Paths["/items"].Operations[HttpMethod.Get].Responses["200"].Content["application/json"].Schema.Items; // Assert @@ -111,10 +111,10 @@ public async Task ParseSubschemaInlineJsonSchemaReferenceWorks() public async Task ParseRootComponentJsonSchemaReferenceWorks() { // Arrange - var filePath = Path.Combine(SampleFolderPath, "rootComponentSchemaReference.yaml"); + var filePath = Path.Join(SampleFolderPath, "rootComponentSchemaReference.yaml"); // Act - var actual = (await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings)).Document; + var actual = (await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken)).Document; var schema = actual.Components.Schemas["specialitem"]; // Assert @@ -126,10 +126,10 @@ public async Task ParseRootComponentJsonSchemaReferenceWorks() public async Task ParseSubschemaComponentJsonSchemaReferenceWorks() { // Arrange - var filePath = Path.Combine(SampleFolderPath, "subschemaComponentSchemaReference.yaml"); + var filePath = Path.Join(SampleFolderPath, "subschemaComponentSchemaReference.yaml"); // Act - var actual = (await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings)).Document; + var actual = (await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken)).Document; var schema = actual.Components.Schemas["items"].Items; // Assert @@ -140,10 +140,10 @@ public async Task ParseSubschemaComponentJsonSchemaReferenceWorks() public async Task ParseInternalComponentSubschemaJsonSchemaReferenceWorks() { // Arrange - var filePath = Path.Combine(SampleFolderPath, "internalComponentsSubschemaReference.yaml"); + var filePath = Path.Join(SampleFolderPath, "internalComponentsSubschemaReference.yaml"); // Act - var actual = (await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings)).Document; + var actual = (await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken)).Document; var addressSchema = actual.Paths["/person/{id}/address"].Operations[HttpMethod.Get].Responses["200"].Content["application/json"].Schema; var itemsSchema = actual.Paths["/human"].Operations[HttpMethod.Get].Responses["200"].Content["application/json"].Schema; @@ -156,7 +156,7 @@ public async Task ParseInternalComponentSubschemaJsonSchemaReferenceWorks() public async Task ParseExternalComponentSubschemaJsonSchemaReferenceWorks() { // Arrange - var path = Path.Combine(Directory.GetCurrentDirectory(), SampleFolderPath); + var path = Path.Join(Directory.GetCurrentDirectory(), SampleFolderPath); var settings = new OpenApiReaderSettings { LoadExternalRefs = true, @@ -165,7 +165,7 @@ public async Task ParseExternalComponentSubschemaJsonSchemaReferenceWorks() settings.AddYamlReader(); // Act - var actual = (await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "externalComponentSubschemaReference.yaml"), settings)).Document; + var actual = (await OpenApiDocument.LoadAsync(Path.Join(SampleFolderPath, "externalComponentSubschemaReference.yaml"), settings, token: TestContext.Current.CancellationToken)).Document; var schema = actual.Paths["/person/{id}"].Operations[HttpMethod.Get].Responses["200"].Content["application/json"].Schema; // Assert @@ -196,7 +196,7 @@ await File.WriteAllTextAsync(rootPath, application/json: schema: $ref: 'OAS-schemas.yaml#address' -"); +", TestContext.Current.CancellationToken); await File.WriteAllTextAsync(externalPath, @" openapi: 3.1.0 @@ -211,7 +211,7 @@ await File.WriteAllTextAsync(externalPath, address: $anchor: address type: object -"); +", TestContext.Current.CancellationToken); try { @@ -222,7 +222,7 @@ await File.WriteAllTextAsync(externalPath, }; settings.AddYamlReader(); - var result = await OpenApiDocument.LoadAsync(rootPath, settings); + var result = await OpenApiDocument.LoadAsync(rootPath, settings, token: TestContext.Current.CancellationToken); var schema = result.Document.Paths["/person/{id}"].Operations[HttpMethod.Get].Responses["200"].Content["application/json"].Schema; Assert.Equal(JsonSchemaType.Object, schema.Type); @@ -257,7 +257,7 @@ await File.WriteAllTextAsync(rootPath, application/json: schema: $ref: 'OAS-schemas.yaml#/person/properties/address' -"); +", TestContext.Current.CancellationToken); await File.WriteAllTextAsync(externalPath, @" person: @@ -265,7 +265,7 @@ await File.WriteAllTextAsync(externalPath, properties: address: type: object -"); +", TestContext.Current.CancellationToken); try { @@ -276,7 +276,7 @@ await File.WriteAllTextAsync(externalPath, }; settings.AddYamlReader(); - var result = await OpenApiDocument.LoadAsync(rootPath, settings); + var result = await OpenApiDocument.LoadAsync(rootPath, settings, token: TestContext.Current.CancellationToken); var schema = result.Document.Paths["/person/{id}"].Operations[HttpMethod.Get].Responses["200"].Content["application/json"].Schema; var schemaReference = Assert.IsType(schema); @@ -293,10 +293,10 @@ await File.WriteAllTextAsync(externalPath, public async Task ParseReferenceToInternalComponentUsingDollarIdWorks() { // Arrange - var filePath = Path.Combine(SampleFolderPath, "internalComponentReferenceUsingId.yaml"); + var filePath = Path.Join(SampleFolderPath, "internalComponentReferenceUsingId.yaml"); // Act - var actual = (await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings)).Document; + var actual = (await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken)).Document; var schema = actual.Paths["/person/{id}"].Operations[HttpMethod.Get].Responses["200"].Content["application/json"].Schema; // Assert @@ -338,11 +338,11 @@ await File.WriteAllTextAsync(rootPath, properties: street: type: string -"); +", TestContext.Current.CancellationToken); try { - var result = await OpenApiDocument.LoadAsync(rootPath, SettingsFixture.ReaderSettings); + var result = await OpenApiDocument.LoadAsync(rootPath, SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var schema = result.Document.Paths["/person/{id}/address"].Operations[HttpMethod.Get].Responses["200"].Content["application/json"].Schema; Assert.Equal(JsonSchemaType.Object, schema.Type); @@ -358,12 +358,12 @@ await File.WriteAllTextAsync(rootPath, public async Task ParseLocalReferenceToJsonSchemaResourceWorks() { // Arrange - var filePath = Path.Combine(SampleFolderPath, "localReferenceToJsonSchemaResource.yaml"); + var filePath = Path.Join(SampleFolderPath, "localReferenceToJsonSchemaResource.yaml"); var stringWriter = new StringWriter(); var writer = new OpenApiYamlWriter(stringWriter); // Act - var actual = (await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings)).Document; + var actual = (await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken)).Document; var schema = actual.Components.Schemas["a"].Properties["b"].Properties["c"].Properties["b"]; schema.SerializeAsV31(writer); @@ -402,7 +402,7 @@ await File.WriteAllTextAsync(rootPath, schemas: Leaf: $ref: './shared.yaml#/Leaf' -"); +", TestContext.Current.CancellationToken); await File.WriteAllTextAsync(sharedPath, @"Leaf: @@ -412,7 +412,7 @@ await File.WriteAllTextAsync(sharedPath, type: string y: type: integer -"); +", TestContext.Current.CancellationToken); try { @@ -423,7 +423,7 @@ await File.WriteAllTextAsync(sharedPath, }; settings.AddYamlReader(); - var result = await OpenApiDocument.LoadAsync(rootPath, settings); + var result = await OpenApiDocument.LoadAsync(rootPath, settings, token: TestContext.Current.CancellationToken); var responseSchema = result.Document.Paths["/a"].Operations[HttpMethod.Get].Responses["200"].Content["application/json"].Schema; var metaSchema = responseSchema.Properties["meta"]; var leafSchema = result.Document.Components.Schemas["Leaf"]; @@ -521,10 +521,10 @@ public void ResolveSubSchema_ShouldRecurseIntoAllOfComposition() public async Task ShouldResolveRelativeSubReference() { // Arrange - var filePath = Path.Combine(SampleFolderPath, "relativeSubschemaReference.json"); + var filePath = Path.Join(SampleFolderPath, "relativeSubschemaReference.json"); // Act - var (actual, _) = await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings); + var (actual, _) = await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var fooComponentSchema = actual.Components.Schemas["Foo"]; var seq1Property = fooComponentSchema.Properties["seq1"]; @@ -538,9 +538,9 @@ public async Task ShouldResolveRelativeSubReference() public async Task ShouldResolveRelativeSubReferenceUsingParsingContext() { // Arrange - var filePath = Path.Combine(SampleFolderPath, "relativeSubschemaReference.json"); + var filePath = Path.Join(SampleFolderPath, "relativeSubschemaReference.json"); using var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read); - var jsonNode = await JsonNode.ParseAsync(fs); + var jsonNode = await JsonNode.ParseAsync(fs, cancellationToken: TestContext.Current.CancellationToken); var schemaJsonNode = jsonNode["components"]?["schemas"]?["Foo"]; Assert.NotNull(schemaJsonNode); var diagnostic = new OpenApiDiagnostic(); @@ -631,10 +631,10 @@ public void ShouldResolveAbsoluteSubReferenceFromTheObjectModel() public async Task ShouldResolveRecursiveRelativeSubReference() { // Arrange - var filePath = Path.Combine(SampleFolderPath, "recursiveRelativeSubschemaReference.json"); + var filePath = Path.Join(SampleFolderPath, "recursiveRelativeSubschemaReference.json"); // Act - var (actual, _) = await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings); + var (actual, _) = await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var fooComponentSchema = actual.Components.Schemas["Foo"]; var fooSchemaParentProperty = fooComponentSchema.Properties["parent"]; @@ -652,9 +652,9 @@ public async Task ShouldResolveRecursiveRelativeSubReference() [Fact] public async Task ShouldResolveReferencesInSchemasFromSystemTextJson() { - var filePath = Path.Combine(SampleFolderPath, "STJSchema.json"); + var filePath = Path.Join(SampleFolderPath, "STJSchema.json"); using var fs = File.OpenRead(filePath); - var jsonNode = await JsonNode.ParseAsync(fs); + var jsonNode = await JsonNode.ParseAsync(fs, cancellationToken: TestContext.Current.CancellationToken); var parsingContext = new ParsingContext(new OpenApiDiagnostic()); var document = new OpenApiDocument(); diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDiscriminatorTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDiscriminatorTests.cs index 4b359546e..c571b0ce6 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDiscriminatorTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDiscriminatorTests.cs @@ -18,10 +18,10 @@ public class OpenApiDiscriminatorTests public async Task ParseBasicDiscriminatorShouldSucceed() { // Arrange - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "basicDiscriminator.yaml")); + using var stream = Resources.GetStream(Path.Join(SampleFolderPath, "basicDiscriminator.yaml")); // Copy stream to MemoryStream using var memoryStream = new MemoryStream(); - await stream.CopyToAsync(memoryStream); + await stream.CopyToAsync(memoryStream, TestContext.Current.CancellationToken); memoryStream.Position = 0; // Act diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDocumentSerializationTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDocumentSerializationTests.cs index b95a25af2..d97cb2bf1 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDocumentSerializationTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDocumentSerializationTests.cs @@ -22,8 +22,8 @@ public class OpenApiDocumentSerializationTests public async Task Serialize_DoesNotMutateDom(OpenApiSpecVersion version) { // Arrange - var filePath = Path.Combine(SampleFolderPath, "docWith32properties.json"); - var (doc, _) = await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings); + var filePath = Path.Join(SampleFolderPath, "docWith32properties.json"); + var (doc, _) = await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Act: Serialize using System.Text.Json var options = new JsonSerializerOptions diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDocumentTests.ParseDocumentWith32PropertiesWorks.verified.txt b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDocumentTests.ParseDocumentWith32PropertiesWorks.verified.txt index a9a5d5e1b..6dac0f30e 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDocumentTests.ParseDocumentWith32PropertiesWorks.verified.txt +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDocumentTests.ParseDocumentWith32PropertiesWorks.verified.txt @@ -113,4 +113,4 @@ webhooks: required: true responses: '200': - description: Webhook processed successfully + description: Webhook processed successfully \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDocumentTests.cs index 1c374a8d1..28e213466 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDocumentTests.cs @@ -2,7 +2,6 @@ using System.Globalization; using System.IO; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.OpenApi.Reader; using Microsoft.OpenApi.Tests; using Xunit; @@ -56,7 +55,7 @@ public void ParseDocumentWithMalformedReferenceShouldYieldExpectedDiagnostic() public async Task ParseDocumentWithWebhooksShouldSucceed() { // Arrange and Act - var actual = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "documentWithWebhooks.yaml"), SettingsFixture.ReaderSettings); + var actual = await OpenApiDocument.LoadAsync(Path.Join(SampleFolderPath, "documentWithWebhooks.yaml"), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var petSchema = new OpenApiSchemaReference("petSchema", actual.Document); var newPetSchema = new OpenApiSchemaReference("newPetSchema", actual.Document); @@ -244,14 +243,14 @@ public async Task ParseDocumentWithWebhooksShouldSucceed() // Assert Assert.Equivalent(new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_2, Format = OpenApiConstants.Yaml }, actual.Diagnostic); - actual.Document.Should().BeEquivalentTo(expected, options => options.Excluding(x => x.Workspace).Excluding(y => y.BaseUri)); + OpenApiTestAssert.Equivalent(expected, actual.Document, nameof(OpenApiDocument.Workspace), nameof(OpenApiDocument.BaseUri)); } [Fact] public async Task ParseDocumentsWithReusablePathItemInWebhooksSucceeds() { // Arrange && Act - var actual = await OpenApiDocument.LoadAsync("V32Tests/Samples/OpenApiDocument/documentWithReusablePaths.yaml", SettingsFixture.ReaderSettings); + var actual = await OpenApiDocument.LoadAsync("V32Tests/Samples/OpenApiDocument/documentWithReusablePaths.yaml", SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var components = new OpenApiComponents { @@ -446,9 +445,7 @@ public async Task ParseDocumentsWithReusablePathItemInWebhooksSucceeds() }; // Assert - actual.Document.Should().BeEquivalentTo(expected, options => options - .Excluding(x => x.Workspace) - .Excluding(y => y.BaseUri)); + OpenApiTestAssert.Equivalent(expected, actual.Document, nameof(OpenApiDocument.Workspace), nameof(OpenApiDocument.BaseUri)); Assert.Equivalent( new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_2, Format = OpenApiConstants.Yaml }, actual.Diagnostic); } @@ -461,7 +458,7 @@ public async Task ParseDocumentWithExampleInSchemaShouldSucceed() var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = false }); // Act - var actual = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "docWithExample.yaml"), SettingsFixture.ReaderSettings); + var actual = await OpenApiDocument.LoadAsync(Path.Join(SampleFolderPath, "docWithExample.yaml"), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); actual.Document.SerializeAsV32(writer); // Assert @@ -472,7 +469,7 @@ public async Task ParseDocumentWithExampleInSchemaShouldSucceed() public async Task ParseDocumentWithPatternPropertiesInSchemaWorks() { // Arrange and Act - var result = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "docWithPatternPropertiesInSchema.yaml"), SettingsFixture.ReaderSettings); + var result = await OpenApiDocument.LoadAsync(Path.Join(SampleFolderPath, "docWithPatternPropertiesInSchema.yaml"), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var actualSchema = result.Document.Paths["/example"].Operations[HttpMethod.Get].Responses["200"].Content["application/json"].Schema; var expectedSchema = new OpenApiSchema @@ -518,7 +515,7 @@ public async Task ParseDocumentWithPatternPropertiesInSchemaWorks() prop3: type: string"; - var actualMediaType = await mediaType.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_2); + var actualMediaType = await mediaType.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_2, TestContext.Current.CancellationToken); // Assert Assert.Equivalent(expectedSchema, actualSchema); @@ -529,7 +526,7 @@ public async Task ParseDocumentWithPatternPropertiesInSchemaWorks() public async Task ParseDocumentWithReferenceByIdGetsResolved() { // Arrange and Act - var result = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "docWithReferenceById.yaml"), SettingsFixture.ReaderSettings); + var result = await OpenApiDocument.LoadAsync(Path.Join(SampleFolderPath, "docWithReferenceById.yaml"), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var responseSchema = result.Document.Paths["/resource"].Operations[HttpMethod.Get].Responses["200"].Content["application/json"].Schema; var requestBodySchema = result.Document.Paths["/resource"].Operations[HttpMethod.Post].RequestBody.Content["application/json"].Schema; @@ -546,7 +543,7 @@ public async Task ExternalDocumentDereferenceToOpenApiDocumentUsingJsonPointerWo { // Arrange var documentName = "externalRefByJsonPointer.yaml"; - var path = Path.Combine(Directory.GetCurrentDirectory(), SampleFolderPath, documentName); + var path = Path.Join(Directory.GetCurrentDirectory(), SampleFolderPath, documentName); var settings = new OpenApiReaderSettings { @@ -556,13 +553,13 @@ public async Task ExternalDocumentDereferenceToOpenApiDocumentUsingJsonPointerWo settings.AddYamlReader(); // Act - var result = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, documentName), settings); + var result = await OpenApiDocument.LoadAsync(Path.Join(SampleFolderPath, documentName), settings, token: TestContext.Current.CancellationToken); var responseSchema = result.Document.Paths["/resource"].Operations[HttpMethod.Get].Responses["200"].Content["application/json"].Schema; // Assert var externalResourceUri = new Uri( "file://" + - Path.Combine(Path.GetFullPath(SampleFolderPath), + Path.Join(Path.GetFullPath(SampleFolderPath), "externalResource.yaml#/components/schemas/todo")).AbsoluteUri; Assert.True(result.Document.Workspace.Contains(externalResourceUri)); @@ -573,7 +570,7 @@ public async Task ExternalDocumentDereferenceToOpenApiDocumentUsingJsonPointerWo public async Task ParseExternalDocumentDereferenceToOpenApiDocumentByIdWorks() { // Arrange - var path = Path.Combine(Directory.GetCurrentDirectory(), SampleFolderPath); + var path = Path.Join(Directory.GetCurrentDirectory(), SampleFolderPath); var settings = new OpenApiReaderSettings { @@ -583,8 +580,8 @@ public async Task ParseExternalDocumentDereferenceToOpenApiDocumentByIdWorks() settings.AddYamlReader(); // Act - var result = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "externalRefById.yaml"), settings); - var doc2 = (await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "externalResource.yaml"), SettingsFixture.ReaderSettings)).Document; + var result = await OpenApiDocument.LoadAsync(Path.Join(SampleFolderPath, "externalRefById.yaml"), settings, token: TestContext.Current.CancellationToken); + var doc2 = (await OpenApiDocument.LoadAsync(Path.Join(SampleFolderPath, "externalResource.yaml"), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken)).Document; var requestBodySchema = result.Document.Paths["/resource"].Operations[HttpMethod.Get].Parameters[0].Schema; result.Document.Workspace.RegisterComponents(doc2); @@ -596,8 +593,8 @@ public async Task ParseExternalDocumentDereferenceToOpenApiDocumentByIdWorks() [Fact] public async Task ParseDocumentWith32PropertiesWorks() { - var path = Path.Combine(SampleFolderPath, "documentWith32Properties.yaml"); - var doc = (await OpenApiDocument.LoadAsync(path, SettingsFixture.ReaderSettings)).Document; + var path = Path.Join(SampleFolderPath, "documentWith32Properties.yaml"); + var doc = (await OpenApiDocument.LoadAsync(path, SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken)).Document; var outputStringWriter = new StringWriter(); doc.SerializeAsV32(new OpenApiYamlWriter(outputStringWriter)); await outputStringWriter.FlushAsync(); @@ -610,16 +607,16 @@ public async Task ParseDocumentWith32PropertiesWorks() [Fact] public async Task ParseDocumentWithEmptyTagsWorks() { - var path = Path.Combine(SampleFolderPath, "documentWithEmptyTags.json"); - var doc = (await OpenApiDocument.LoadAsync(path, SettingsFixture.ReaderSettings)).Document; + var path = Path.Join(SampleFolderPath, "documentWithEmptyTags.json"); + var doc = (await OpenApiDocument.LoadAsync(path, SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken)).Document; - doc.Paths["/groups"].Operations[HttpMethod.Get].Tags.Should().BeNull("Empty tags are ignored, so we should not have any tags"); + Assert.Null(doc.Paths["/groups"].Operations[HttpMethod.Get].Tags); } [Fact] public async Task DocumentWithSchemaResultsInWarning() { - var path = Path.Combine(SampleFolderPath, "documentWithSchema.json"); - var (doc, diag) = await OpenApiDocument.LoadAsync(path, SettingsFixture.ReaderSettings); + var path = Path.Join(SampleFolderPath, "documentWithSchema.json"); + var (doc, diag) = await OpenApiDocument.LoadAsync(path, SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); Assert.NotNull(doc); Assert.NotNull(diag); Assert.Empty(diag.Errors); @@ -637,8 +634,8 @@ public void ParseEmptyMemoryStreamThrowsAnArgumentException() public async Task ValidateReferencedExampleInSchemaWorks() { // Arrange && Act - var path = Path.Combine(SampleFolderPath, "docWithReferencedExampleInSchemaWorks.yaml"); - var result = await OpenApiDocument.LoadAsync(path, SettingsFixture.ReaderSettings); + var path = Path.Join(SampleFolderPath, "docWithReferencedExampleInSchemaWorks.yaml"); + var result = await OpenApiDocument.LoadAsync(path, SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var actualSchemaExample = result.Document.Components.Schemas["DiffCreatedEvent"].Properties["updatedAt"].Example; var targetSchemaExample = result.Document.Components.Schemas["Timestamp"].Example; @@ -652,8 +649,8 @@ public async Task ValidateReferencedExampleInSchemaWorks() public async Task ParseDocumentWithSelfPropertyFromYamlWorks() { // Arrange && Act - var path = Path.Combine(SampleFolderPath, "documentWithSelfProperty.yaml"); - var result = await OpenApiDocument.LoadAsync(path, SettingsFixture.ReaderSettings); + var path = Path.Join(SampleFolderPath, "documentWithSelfProperty.yaml"); + var result = await OpenApiDocument.LoadAsync(path, SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.NotNull(result.Document); @@ -669,8 +666,8 @@ public async Task ParseDocumentWithSelfPropertyFromYamlWorks() public async Task ParseDocumentWithSelfPropertyFromJsonWorks() { // Arrange && Act - var path = Path.Combine(SampleFolderPath, "documentWithSelfProperty.json"); - var result = await OpenApiDocument.LoadAsync(path, SettingsFixture.ReaderSettings); + var path = Path.Join(SampleFolderPath, "documentWithSelfProperty.json"); + var result = await OpenApiDocument.LoadAsync(path, SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.NotNull(result.Document); diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDynamicRefTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDynamicRefTests.cs index e35b501dc..1e3140931 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDynamicRefTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiDynamicRefTests.cs @@ -165,8 +165,8 @@ public async Task ExternalDynamicRefWithRelativeUriResolvesAcrossDocuments() using var streamA = new MemoryStream(Encoding.UTF8.GetBytes(yamlA)); using var streamB = new MemoryStream(Encoding.UTF8.GetBytes(yamlB)); - var resultA = await OpenApiDocument.LoadAsync(streamA, "yaml", SettingsFixture.ReaderSettings); - var resultB = await OpenApiDocument.LoadAsync(streamB, "yaml", SettingsFixture.ReaderSettings); + var resultA = await OpenApiDocument.LoadAsync(streamA, "yaml", SettingsFixture.ReaderSettings, cancellationToken: TestContext.Current.CancellationToken); + var resultB = await OpenApiDocument.LoadAsync(streamB, "yaml", SettingsFixture.ReaderSettings, cancellationToken: TestContext.Current.CancellationToken); var docA = resultA.Document; var docB = resultB.Document; @@ -1488,8 +1488,8 @@ public async Task AbsoluteDynamicRefResolvesAcrossDocuments() using var streamA = new MemoryStream(Encoding.UTF8.GetBytes(yamlA)); using var streamB = new MemoryStream(Encoding.UTF8.GetBytes(yamlB)); - var resultA = await OpenApiDocument.LoadAsync(streamA, "yaml", SettingsFixture.ReaderSettings); - var resultB = await OpenApiDocument.LoadAsync(streamB, "yaml", SettingsFixture.ReaderSettings); + var resultA = await OpenApiDocument.LoadAsync(streamA, "yaml", SettingsFixture.ReaderSettings, cancellationToken: TestContext.Current.CancellationToken); + var resultB = await OpenApiDocument.LoadAsync(streamB, "yaml", SettingsFixture.ReaderSettings, cancellationToken: TestContext.Current.CancellationToken); var docA = resultA.Document; var docB = resultB.Document; @@ -1840,8 +1840,8 @@ public async Task ExternalDynamicRefResolvesToPlainAnchorInExternalDocument() using var streamA = new MemoryStream(Encoding.UTF8.GetBytes(yamlA)); using var streamB = new MemoryStream(Encoding.UTF8.GetBytes(yamlB)); - var resultA = await OpenApiDocument.LoadAsync(streamA, "yaml", SettingsFixture.ReaderSettings); - var resultB = await OpenApiDocument.LoadAsync(streamB, "yaml", SettingsFixture.ReaderSettings); + var resultA = await OpenApiDocument.LoadAsync(streamA, "yaml", SettingsFixture.ReaderSettings, cancellationToken: TestContext.Current.CancellationToken); + var resultB = await OpenApiDocument.LoadAsync(streamB, "yaml", SettingsFixture.ReaderSettings, cancellationToken: TestContext.Current.CancellationToken); var docA = resultA.Document; var docB = resultB.Document; diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiEncodingTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiEncodingTests.cs index 166f6fd12..6f8574421 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiEncodingTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiEncodingTests.cs @@ -18,7 +18,7 @@ public class OpenApiEncodingTests public async Task ParseEncodingWithAllowReservedShouldSucceed() { // Act - var encoding = await OpenApiModelFactory.LoadAsync(Path.Combine(SampleFolderPath, "encodingWithAllowReserved.yaml"), OpenApiSpecVersion.OpenApi3_2, new(), SettingsFixture.ReaderSettings); + var encoding = await OpenApiModelFactory.LoadAsync(Path.Join(SampleFolderPath, "encodingWithAllowReserved.yaml"), OpenApiSpecVersion.OpenApi3_2, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.Equivalent( @@ -35,7 +35,7 @@ public async Task ParseEncodingWithAllowReservedShouldSucceed() public async Task ParseEncodingWithNestedEncodingShouldSucceed() { // Act - var encoding = await OpenApiModelFactory.LoadAsync(Path.Combine(SampleFolderPath, "encodingWithNestedEncoding.yaml"), OpenApiSpecVersion.OpenApi3_2, new(), SettingsFixture.ReaderSettings); + var encoding = await OpenApiModelFactory.LoadAsync(Path.Join(SampleFolderPath, "encodingWithNestedEncoding.yaml"), OpenApiSpecVersion.OpenApi3_2, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.NotNull(encoding); @@ -56,7 +56,7 @@ public async Task ParseEncodingWithNestedEncodingShouldSucceed() public async Task ParseEncodingWithItemAndPrefixEncodingShouldSucceed() { // Act - var encoding = await OpenApiModelFactory.LoadAsync(Path.Combine(SampleFolderPath, "encodingWithItemAndPrefixEncoding.yaml"), OpenApiSpecVersion.OpenApi3_2, new(), SettingsFixture.ReaderSettings); + var encoding = await OpenApiModelFactory.LoadAsync(Path.Join(SampleFolderPath, "encodingWithItemAndPrefixEncoding.yaml"), OpenApiSpecVersion.OpenApi3_2, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.NotNull(encoding); diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiExampleTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiExampleTests.cs index 5d586f469..9a9b5f3ea 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiExampleTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiExampleTests.cs @@ -19,10 +19,10 @@ public async Task ParseExampleWithDataValueShouldSucceed() { // Arrange & Act var example = await OpenApiModelFactory.LoadAsync( - Path.Combine(SampleFolderPath, "exampleWithDataValue.yaml"), + Path.Join(SampleFolderPath, "exampleWithDataValue.yaml"), OpenApiSpecVersion.OpenApi3_2, new(), - SettingsFixture.ReaderSettings); + SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.NotNull(example); @@ -37,10 +37,10 @@ public async Task ParseExampleWithSerializedValueShouldSucceed() { // Arrange & Act var example = await OpenApiModelFactory.LoadAsync( - Path.Combine(SampleFolderPath, "exampleWithSerializedValue.yaml"), + Path.Join(SampleFolderPath, "exampleWithSerializedValue.yaml"), OpenApiSpecVersion.OpenApi3_2, new(), - SettingsFixture.ReaderSettings); + SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.NotNull(example); diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiHeaderTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiHeaderTests.cs index f1d28eced..3e6b63314 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiHeaderTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiHeaderTests.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.IO; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.OpenApi.Reader; using Xunit; @@ -19,10 +18,10 @@ public class OpenApiHeaderTests public async Task ParseBasicHeaderShouldSucceed() { // Arrange - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "basicHeader.yaml")); + using var stream = Resources.GetStream(Path.Join(SampleFolderPath, "basicHeader.yaml")); // Act - var header = await OpenApiModelFactory.LoadAsync(stream, OpenApiSpecVersion.OpenApi3_2, new(), settings: SettingsFixture.ReaderSettings); + var header = await OpenApiModelFactory.LoadAsync(stream, OpenApiSpecVersion.OpenApi3_2, new(), settings: SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.Equivalent( @@ -40,10 +39,10 @@ public async Task ParseBasicHeaderShouldSucceed() public async Task ParseHeaderWithContentShouldSucceed() { // Arrange - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "headerWithContent.yaml")); + using var stream = Resources.GetStream(Path.Join(SampleFolderPath, "headerWithContent.yaml")); // Act - var header = await OpenApiModelFactory.LoadAsync(stream, OpenApiSpecVersion.OpenApi3_2, new(), settings: SettingsFixture.ReaderSettings); + var header = await OpenApiModelFactory.LoadAsync(stream, OpenApiSpecVersion.OpenApi3_2, new(), settings: SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.Equivalent( @@ -79,10 +78,10 @@ public async Task ParseHeaderWithContentShouldSucceed() public async Task ParseHeaderWithMultipleContentTypesShouldSucceed() { // Arrange - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "headerWithMultipleContentTypes.yaml")); + using var stream = Resources.GetStream(Path.Join(SampleFolderPath, "headerWithMultipleContentTypes.yaml")); // Act - var header = await OpenApiModelFactory.LoadAsync(stream, OpenApiSpecVersion.OpenApi3_2, new(), settings: SettingsFixture.ReaderSettings); + var header = await OpenApiModelFactory.LoadAsync(stream, OpenApiSpecVersion.OpenApi3_2, new(), settings: SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.Equivalent( @@ -120,10 +119,10 @@ public async Task ParseHeaderWithMultipleContentTypesShouldSucceed() public async Task ParseHeaderWithStyleAndContentShouldPreferContent() { // Arrange - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "headerWithStyleAndContent.yaml")); + using var stream = Resources.GetStream(Path.Join(SampleFolderPath, "headerWithStyleAndContent.yaml")); // Act - var header = await OpenApiModelFactory.LoadAsync(stream, OpenApiSpecVersion.OpenApi3_2, new(), settings: SettingsFixture.ReaderSettings); + var header = await OpenApiModelFactory.LoadAsync(stream, OpenApiSpecVersion.OpenApi3_2, new(), settings: SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert // Both content and style can be present, content takes precedence for serialization behavior diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiInfoTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiInfoTests.cs index 6b21cac41..111ba07a0 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiInfoTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiInfoTests.cs @@ -15,7 +15,7 @@ public class OpenApiInfoTests [Fact] public void ParseBasicInfoShouldSucceed() { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "basicInfo.yaml")); + using var stream = Resources.GetStream(Path.Join(SampleFolderPath, "basicInfo.yaml")); var yamlStream = new YamlStream(); yamlStream.Load(new StreamReader(stream)); var yamlNode = yamlStream.Documents[0].RootNode; diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiLicenseTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiLicenseTests.cs index 6a7f9a74e..9e940c3f1 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiLicenseTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiLicenseTests.cs @@ -18,7 +18,7 @@ public class OpenApiLicenseTests [Fact] public void ParseLicenseWithSpdxIdentifierShouldSucceed() { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "licenseWithSpdxIdentifier.yaml")); + using var stream = Resources.GetStream(Path.Join(SampleFolderPath, "licenseWithSpdxIdentifier.yaml")); var yamlStream = new YamlStream(); yamlStream.Load(new StreamReader(stream)); var yamlNode = yamlStream.Documents[0].RootNode; diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiMediaTypeTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiMediaTypeTests.cs index 903ffd84c..d3716b2d6 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiMediaTypeTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiMediaTypeTests.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.IO; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.OpenApi.Reader; using Microsoft.OpenApi.Reader.V32; using Microsoft.OpenApi.Tests; @@ -22,10 +21,10 @@ public class OpenApiMediaTypeTests public async Task ParseMediaTypeWithExampleShouldSucceed() { // Act - var mediaType = await OpenApiModelFactory.LoadAsync(Path.Combine(SampleFolderPath, "mediaTypeWithExample.yaml"), OpenApiSpecVersion.OpenApi3_2, new(), SettingsFixture.ReaderSettings); + var mediaType = await OpenApiModelFactory.LoadAsync(Path.Join(SampleFolderPath, "mediaTypeWithExample.yaml"), OpenApiSpecVersion.OpenApi3_2, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert - mediaType.Should().BeEquivalentTo( + OpenApiTestAssert.Equivalent( new OpenApiMediaType { Example = 5, @@ -34,19 +33,17 @@ public async Task ParseMediaTypeWithExampleShouldSucceed() Type = JsonSchemaType.Number, Format = "float" } - }, options => options.IgnoringCyclicReferences() - .Excluding(m => m.Example.Parent) - ); + }, mediaType, nameof(JsonNode.Parent)); } [Fact] public async Task ParseMediaTypeWithExamplesShouldSucceed() { // Act - var mediaType = await OpenApiModelFactory.LoadAsync(Path.Combine(SampleFolderPath, "mediaTypeWithExamples.yaml"), OpenApiSpecVersion.OpenApi3_2, new(), SettingsFixture.ReaderSettings); + var mediaType = await OpenApiModelFactory.LoadAsync(Path.Join(SampleFolderPath, "mediaTypeWithExamples.yaml"), OpenApiSpecVersion.OpenApi3_2, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert - mediaType.Should().BeEquivalentTo( + OpenApiTestAssert.Equivalent( new OpenApiMediaType { Examples = new Dictionary @@ -65,9 +62,7 @@ public async Task ParseMediaTypeWithExamplesShouldSucceed() Type = JsonSchemaType.Number, Format = "float" } - }, options => options.IgnoringCyclicReferences() - .Excluding(m => m.Examples["example1"].Value.Parent) - .Excluding(m => m.Examples["example2"].Value.Parent)); + }, mediaType, nameof(JsonNode.Parent)); } [Fact] @@ -103,14 +98,14 @@ public async Task ParseMediaTypeWithEmptyArrayInExamplesWorks() } "; JsonNode node; - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "examplesWithEmptyArray.json"))) + using (var stream = Resources.GetStream(Path.Join(SampleFolderPath, "examplesWithEmptyArray.json"))) { node = TestHelper.CreateYamlJsonNode(stream); } // Act var mediaType = OpenApiV32Deserializer.LoadMediaType(node, new(), new ParsingContext(new())); - var serialized = await mediaType.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2); + var serialized = await mediaType.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2, TestContext.Current.CancellationToken); // Assert Assert.Equal(expected.MakeLineBreaksEnvironmentNeutral(), serialized.MakeLineBreaksEnvironmentNeutral()); @@ -121,10 +116,10 @@ public async Task ParseMediaTypeWithItemSchemaShouldSucceed() { // Act var mediaType = await OpenApiModelFactory.LoadAsync( - Path.Combine(SampleFolderPath, "mediaTypeWithItemSchema.yaml"), + Path.Join(SampleFolderPath, "mediaTypeWithItemSchema.yaml"), OpenApiSpecVersion.OpenApi3_2, new(), - SettingsFixture.ReaderSettings); + SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert var expected = new OpenApiMediaType @@ -147,10 +142,10 @@ public async Task ParseMediaTypeWithItemEncodingShouldSucceed() { // Act var mediaType = await OpenApiModelFactory.LoadAsync( - Path.Combine(SampleFolderPath, "mediaTypeWithItemEncoding.yaml"), + Path.Join(SampleFolderPath, "mediaTypeWithItemEncoding.yaml"), OpenApiSpecVersion.OpenApi3_2, new(), - SettingsFixture.ReaderSettings); + SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.NotNull(mediaType); @@ -166,10 +161,10 @@ public async Task ParseMediaTypeWithPrefixEncodingShouldSucceed() { // Act var mediaType = await OpenApiModelFactory.LoadAsync( - Path.Combine(SampleFolderPath, "mediaTypeWithPrefixEncoding.yaml"), + Path.Join(SampleFolderPath, "mediaTypeWithPrefixEncoding.yaml"), OpenApiSpecVersion.OpenApi3_2, new(), - SettingsFixture.ReaderSettings); + SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.NotNull(mediaType); diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiPathItemDeserializerTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiPathItemDeserializerTests.cs index 66d2364c2..98e32a126 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiPathItemDeserializerTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiPathItemDeserializerTests.cs @@ -16,7 +16,7 @@ public class OpenApiPathItemDeserializerTests public async Task ParsePathItemWithQueryAndAdditionalOperationsV32Works() { // Arrange & Act - var result = await OpenApiDocument.LoadAsync($"{SampleFolderPath}pathItemWithQueryAndAdditionalOperations.yaml", SettingsFixture.ReaderSettings); + var result = await OpenApiDocument.LoadAsync($"{SampleFolderPath}pathItemWithQueryAndAdditionalOperations.yaml", SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var pathItem = result.Document.Paths["/pets"]; // Assert diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSchemaTests.cs index 166d15978..184a2e744 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSchemaTests.cs @@ -6,8 +6,6 @@ using System.Linq; using System.Text.Json.Nodes; using System.Threading.Tasks; -using FluentAssertions; -using FluentAssertions.Equivalency; using Microsoft.OpenApi.Reader; using Microsoft.OpenApi.Tests; using Xunit; @@ -24,7 +22,7 @@ public class OpenApiSchemaTests public static MemoryStream GetMemoryStream(string fileName) { - var filePath = Path.Combine(SampleFolderPath, fileName); + var filePath = Path.Join(SampleFolderPath, fileName); var fileBytes = File.ReadAllBytes(filePath); return new MemoryStream(fileBytes); } @@ -107,7 +105,7 @@ public async Task ParseBasicV32SchemaShouldSucceed() // Act var schema = await OpenApiModelFactory.LoadAsync( - Path.Combine(SampleFolderPath, "jsonSchema.json"), OpenApiSpecVersion.OpenApi3_2, new(), SettingsFixture.ReaderSettings); + Path.Join(SampleFolderPath, "jsonSchema.json"), OpenApiSpecVersion.OpenApi3_2, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.Equivalent(expectedObject, schema); @@ -191,10 +189,10 @@ public void TestSchemaCopyConstructorWithTypeArrayWorks() [Fact] public async Task ParseV32SchemaShouldSucceed() { - var path = Path.Combine(SampleFolderPath, "schema.yaml"); + var path = Path.Join(SampleFolderPath, "schema.yaml"); // Act - var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi3_2, new(), SettingsFixture.ReaderSettings); + var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi3_2, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var expectedSchema = new OpenApiSchema { Type = JsonSchemaType.Object, @@ -216,8 +214,8 @@ public async Task ParseV32SchemaShouldSucceed() public async Task ParseAdvancedV32SchemaShouldSucceed() { // Arrange and Act - var path = Path.Combine(SampleFolderPath, "advancedSchema.yaml"); - var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi3_2, new(), SettingsFixture.ReaderSettings); + var path = Path.Join(SampleFolderPath, "advancedSchema.yaml"); + var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi3_2, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var expectedSchema = new OpenApiSchema { @@ -291,10 +289,7 @@ public async Task ParseAdvancedV32SchemaShouldSucceed() }; // Assert - schema.Should().BeEquivalentTo(expectedSchema, options => options - .IgnoringCyclicReferences() - .Excluding((IMemberInfo memberInfo) => - memberInfo.Path.EndsWith("Parent"))); + OpenApiTestAssert.Equivalent(expectedSchema, schema, nameof(JsonNode.Parent)); } [Fact] @@ -344,10 +339,10 @@ public async Task SerializeV32SchemaWithMultipleTypesAsV3Works() var expected = @"type: string nullable: true"; - var path = Path.Combine(SampleFolderPath, "schemaWithTypeArray.yaml"); + var path = Path.Join(SampleFolderPath, "schemaWithTypeArray.yaml"); // Act - var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi3_2, new(), SettingsFixture.ReaderSettings); + var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi3_2, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var writer = new StringWriter(); schema.SerializeAsV3(new OpenApiYamlWriter(writer)); @@ -363,10 +358,10 @@ public async Task SerializeV32SchemaWithMultipleTypesAsV2Works() var expected = @"type: string x-nullable: true"; - var path = Path.Combine(SampleFolderPath, "schemaWithTypeArray.yaml"); + var path = Path.Join(SampleFolderPath, "schemaWithTypeArray.yaml"); // Act - var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi3_2, new(), SettingsFixture.ReaderSettings); + var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi3_2, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var writer = new StringWriter(); schema.SerializeAsV2(new OpenApiYamlWriter(writer)); @@ -383,10 +378,10 @@ public async Task SerializeV3SchemaWithNullableAsV32Works() - 'null' - string"; - var path = Path.Combine(SampleFolderPath, "schemaWithNullable.yaml"); + var path = Path.Join(SampleFolderPath, "schemaWithNullable.yaml"); // Act - var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi3_0, new(), SettingsFixture.ReaderSettings); + var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi3_0, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var writer = new StringWriter(); schema.SerializeAsV32(new OpenApiYamlWriter(writer)); @@ -403,10 +398,10 @@ public async Task SerializeV2SchemaWithNullableExtensionAsV32Works() - 'null' - string"; - var path = Path.Combine(SampleFolderPath, "schemaWithNullableExtension.yaml"); + var path = Path.Join(SampleFolderPath, "schemaWithNullableExtension.yaml"); // Act - var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi2_0, new(), SettingsFixture.ReaderSettings); + var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi2_0, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var writer = new StringWriter(); schema.SerializeAsV32(new OpenApiYamlWriter(writer)); @@ -443,10 +438,10 @@ public async Task LoadSchemaWithNullableExtensionAsV32ShouldNotWork(string fileP // and "x-nullable" is only for 2.0. // Arrange - var path = Path.Combine(SampleFolderPath, filePath); + var path = Path.Join(SampleFolderPath, filePath); // Act - var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi3_2, new(), SettingsFixture.ReaderSettings); + var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi3_2, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.Equal(JsonSchemaType.String, schema.Type); @@ -483,10 +478,10 @@ public async Task SerializeSchemaWithJsonSchemaKeywordsWorks() $dynamicRef: '#addressDef' description: Schema for a person object "; - var path = Path.Combine(SampleFolderPath, "schemaWithJsonSchemaKeywords.yaml"); + var path = Path.Join(SampleFolderPath, "schemaWithJsonSchemaKeywords.yaml"); // Act - var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi3_2, new(), SettingsFixture.ReaderSettings); + var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi3_2, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // serialization var writer = new StringWriter(); @@ -527,10 +522,10 @@ public async Task ParseSchemaWithConstWorks() } }"; - var path = Path.Combine(SampleFolderPath, "schemaWithConst.json"); + var path = Path.Join(SampleFolderPath, "schemaWithConst.json"); // Act - var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi3_2, new(), SettingsFixture.ReaderSettings); + var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi3_2, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); Assert.Equal("active", schema.Properties["status"].Const); Assert.Equal("admin", schema.Properties["user"].Properties["role"].Const); @@ -570,10 +565,10 @@ public async Task ParseSchemaWithConstNullWorks() } }"; - var path = Path.Combine(SampleFolderPath, "schemaWithConstNull.json"); + var path = Path.Join(SampleFolderPath, "schemaWithConstNull.json"); // Act - var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi3_2, new(), SettingsFixture.ReaderSettings); + var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi3_2, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var statusSchema = Assert.IsType(schema.Properties["status"]); Assert.Null(statusSchema.Const); @@ -872,7 +867,7 @@ public async Task ParseSchemaReferencePreservesJsonSchema2020KeywordSiblings() // Act using var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(yaml)); - var result = await OpenApiDocument.LoadAsync(stream, "yaml", SettingsFixture.ReaderSettings); + var result = await OpenApiDocument.LoadAsync(stream, "yaml", SettingsFixture.ReaderSettings, cancellationToken: TestContext.Current.CancellationToken); Assert.NotNull(result.Document.Components); Assert.NotNull(result.Document.Components.Schemas); var referencing = result.Document.Components.Schemas["Referencing"]; @@ -914,14 +909,14 @@ public async Task SerializeSchemaReferencePreservesJsonSchema2020KeywordSiblings // Act — parse then serialize back using var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(yaml)); - var result = await OpenApiDocument.LoadAsync(stream, "yaml", SettingsFixture.ReaderSettings); + var result = await OpenApiDocument.LoadAsync(stream, "yaml", SettingsFixture.ReaderSettings, cancellationToken: TestContext.Current.CancellationToken); var writer = new StringWriter(); result.Document.SerializeAsV32(new OpenApiYamlWriter(writer)); var output = writer.ToString(); // Assert — round-trip preserves $dynamicAnchor and $defs alongside $ref using var roundTripStream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(output)); - var roundTripResult = await OpenApiDocument.LoadAsync(roundTripStream, "yaml", SettingsFixture.ReaderSettings); + var roundTripResult = await OpenApiDocument.LoadAsync(roundTripStream, "yaml", SettingsFixture.ReaderSettings, cancellationToken: TestContext.Current.CancellationToken); Assert.NotNull(roundTripResult.Document.Components); Assert.NotNull(roundTripResult.Document.Components.Schemas); var referencing = roundTripResult.Document.Components.Schemas["Referencing"]; @@ -958,7 +953,7 @@ public async Task ParseSchemaReferencePreservesScalarKeywordSiblings() // Act using var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(yaml)); - var result = await OpenApiDocument.LoadAsync(stream, "yaml", SettingsFixture.ReaderSettings); + var result = await OpenApiDocument.LoadAsync(stream, "yaml", SettingsFixture.ReaderSettings, cancellationToken: TestContext.Current.CancellationToken); Assert.NotNull(result.Document.Components); Assert.NotNull(result.Document.Components.Schemas); var referencing = result.Document.Components.Schemas["Referencing"]; @@ -997,14 +992,14 @@ public async Task SerializeSchemaReferencePreservesScalarKeywordSiblings() // Act — parse then serialize back using var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(yaml)); - var result = await OpenApiDocument.LoadAsync(stream, "yaml", SettingsFixture.ReaderSettings); + var result = await OpenApiDocument.LoadAsync(stream, "yaml", SettingsFixture.ReaderSettings, cancellationToken: TestContext.Current.CancellationToken); var writer = new StringWriter(); result.Document.SerializeAsV32(new OpenApiYamlWriter(writer)); var output = writer.ToString(); // Assert — round-trip preserves scalar siblings alongside $ref using var roundTripStream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(output)); - var roundTripResult = await OpenApiDocument.LoadAsync(roundTripStream, "yaml", SettingsFixture.ReaderSettings); + var roundTripResult = await OpenApiDocument.LoadAsync(roundTripStream, "yaml", SettingsFixture.ReaderSettings, cancellationToken: TestContext.Current.CancellationToken); Assert.NotNull(roundTripResult.Document.Components); Assert.NotNull(roundTripResult.Document.Components.Schemas); var referencing = roundTripResult.Document.Components.Schemas["Referencing"]; @@ -1040,7 +1035,7 @@ public async Task ParseSchemaReferencePreservesVocabularySibling() // Act using var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(yaml)); - var result = await OpenApiDocument.LoadAsync(stream, "yaml", SettingsFixture.ReaderSettings); + var result = await OpenApiDocument.LoadAsync(stream, "yaml", SettingsFixture.ReaderSettings, cancellationToken: TestContext.Current.CancellationToken); Assert.NotNull(result.Document.Components); Assert.NotNull(result.Document.Components.Schemas); var referencing = result.Document.Components.Schemas["Referencing"]; @@ -1087,7 +1082,7 @@ public async Task ParseSchemaReferencePreservesDynamicAnchorInsideDefsInAllOf() // Act using var stream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(yaml)); - var result = await OpenApiDocument.LoadAsync(stream, "yaml", SettingsFixture.ReaderSettings); + var result = await OpenApiDocument.LoadAsync(stream, "yaml", SettingsFixture.ReaderSettings, cancellationToken: TestContext.Current.CancellationToken); Assert.NotNull(result.Document.Components); Assert.NotNull(result.Document.Components.Schemas); var assetPaged = result.Document.Components.Schemas["AssetPaged"]; diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSecuritySchemeTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSecuritySchemeTests.cs index 50c836f66..d0f477617 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSecuritySchemeTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/OpenApiSecuritySchemeTests.cs @@ -17,7 +17,7 @@ public class OpenApiSecuritySchemeTests public async Task ParseHttpSecuritySchemeShouldSucceed() { // Act - var securityScheme = await OpenApiModelFactory.LoadAsync(Path.Combine(SampleFolderPath, "httpSecurityScheme.yaml"), OpenApiSpecVersion.OpenApi3_2, new(), SettingsFixture.ReaderSettings); + var securityScheme = await OpenApiModelFactory.LoadAsync(Path.Join(SampleFolderPath, "httpSecurityScheme.yaml"), OpenApiSpecVersion.OpenApi3_2, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.Equivalent( @@ -32,7 +32,7 @@ public async Task ParseHttpSecuritySchemeShouldSucceed() public async Task ParseApiKeySecuritySchemeShouldSucceed() { // Act - var securityScheme = await OpenApiModelFactory.LoadAsync(Path.Combine(SampleFolderPath, "apiKeySecurityScheme.yaml"), OpenApiSpecVersion.OpenApi3_2, new(), SettingsFixture.ReaderSettings); + var securityScheme = await OpenApiModelFactory.LoadAsync(Path.Join(SampleFolderPath, "apiKeySecurityScheme.yaml"), OpenApiSpecVersion.OpenApi3_2, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.Equivalent( @@ -48,7 +48,7 @@ public async Task ParseApiKeySecuritySchemeShouldSucceed() public async Task ParseBearerSecuritySchemeShouldSucceed() { // Act - var securityScheme = await OpenApiModelFactory.LoadAsync(Path.Combine(SampleFolderPath, "bearerSecurityScheme.yaml"), OpenApiSpecVersion.OpenApi3_2, new(), SettingsFixture.ReaderSettings); + var securityScheme = await OpenApiModelFactory.LoadAsync(Path.Join(SampleFolderPath, "bearerSecurityScheme.yaml"), OpenApiSpecVersion.OpenApi3_2, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.Equivalent( @@ -64,7 +64,7 @@ public async Task ParseBearerSecuritySchemeShouldSucceed() public async Task ParseOAuth2SecuritySchemeShouldSucceed() { // Act - var securityScheme = await OpenApiModelFactory.LoadAsync(Path.Combine(SampleFolderPath, "oauth2SecurityScheme.yaml"), OpenApiSpecVersion.OpenApi3_2, new(), SettingsFixture.ReaderSettings); + var securityScheme = await OpenApiModelFactory.LoadAsync(Path.Join(SampleFolderPath, "oauth2SecurityScheme.yaml"), OpenApiSpecVersion.OpenApi3_2, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.Equivalent( @@ -91,10 +91,10 @@ public async Task ParseOAuth2SecuritySchemeWithMetadataUrlShouldSucceed() { // Act var securityScheme = await OpenApiModelFactory.LoadAsync( - Path.Combine(SampleFolderPath, "oauth2SecuritySchemeWithMetadataUrl.yaml"), + Path.Join(SampleFolderPath, "oauth2SecuritySchemeWithMetadataUrl.yaml"), OpenApiSpecVersion.OpenApi3_2, new(), - SettingsFixture.ReaderSettings); + SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.Equivalent( @@ -120,7 +120,7 @@ public async Task ParseOAuth2SecuritySchemeWithMetadataUrlShouldSucceed() public async Task ParseOpenIdConnectSecuritySchemeShouldSucceed() { // Act - var securityScheme = await OpenApiModelFactory.LoadAsync(Path.Combine(SampleFolderPath, "openIdConnectSecurityScheme.yaml"), OpenApiSpecVersion.OpenApi3_2, new(), SettingsFixture.ReaderSettings); + var securityScheme = await OpenApiModelFactory.LoadAsync(Path.Join(SampleFolderPath, "openIdConnectSecurityScheme.yaml"), OpenApiSpecVersion.OpenApi3_2, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.Equivalent( @@ -137,10 +137,10 @@ public async Task ParseMutualTlsSecuritySchemeShouldSucceed() { // Act var securityScheme = await OpenApiModelFactory.LoadAsync( - Path.Combine(SampleFolderPath, "mutualTlsSecurityScheme.yaml"), + Path.Join(SampleFolderPath, "mutualTlsSecurityScheme.yaml"), OpenApiSpecVersion.OpenApi3_2, new(), - SettingsFixture.ReaderSettings); + SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.Equivalent( @@ -156,10 +156,10 @@ public async Task ParseOAuth2SecuritySchemeWithDeviceAuthorizationUrlShouldSucce { // Act var securityScheme = await OpenApiModelFactory.LoadAsync( - Path.Combine(SampleFolderPath, "oauth2SecuritySchemeWithDeviceUrl.yaml"), + Path.Join(SampleFolderPath, "oauth2SecuritySchemeWithDeviceUrl.yaml"), OpenApiSpecVersion.OpenApi3_2, new(), - SettingsFixture.ReaderSettings); + SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.NotNull(securityScheme); diff --git a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/RelativeReferenceTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/RelativeReferenceTests.cs index 7b53c9688..87080b921 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V32Tests/RelativeReferenceTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V32Tests/RelativeReferenceTests.cs @@ -17,10 +17,10 @@ public class RelativeReferenceTests public async Task ParseInlineLocalReferenceWorks() { // Arrange - var filePath = Path.Combine(SampleFolderPath, "inlineLocalReference.yaml"); + var filePath = Path.Join(SampleFolderPath, "inlineLocalReference.yaml"); // Act - var actual = (await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings)).Document; + var actual = (await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken)).Document; var schemaType = actual.Paths["/item"].Operations[HttpMethod.Get].Parameters[0].Schema.Type; // Assert @@ -40,7 +40,7 @@ public async Task ParseInlineExternalReferenceWorks() } }; - var path = Path.Combine(Directory.GetCurrentDirectory(), SampleFolderPath); + var path = Path.Join(Directory.GetCurrentDirectory(), SampleFolderPath); var settings = new OpenApiReaderSettings { @@ -50,7 +50,7 @@ public async Task ParseInlineExternalReferenceWorks() settings.AddYamlReader(); // Act - var actual = (await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "inlineExternalReference.yaml"), settings)).Document; + var actual = (await OpenApiDocument.LoadAsync(Path.Join(SampleFolderPath, "inlineExternalReference.yaml"), settings, token: TestContext.Current.CancellationToken)).Document; var exampleValue = actual.Paths["/items"].Operations[HttpMethod.Get].Responses["200"].Content["application/json"].Examples["item-list"].Value; // Assert @@ -63,7 +63,7 @@ public async Task ParseInlineExternalReferenceWorks() public async Task ParseComponentExternalReferenceWorks() { // Arrange - var path = Path.Combine(Directory.GetCurrentDirectory(), SampleFolderPath); + var path = Path.Join(Directory.GetCurrentDirectory(), SampleFolderPath); var settings = new OpenApiReaderSettings { LoadExternalRefs = true, @@ -72,7 +72,7 @@ public async Task ParseComponentExternalReferenceWorks() settings.AddYamlReader(); // Act - var actual = (await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "componentExternalReference.yaml"), settings)).Document; + var actual = (await OpenApiDocument.LoadAsync(Path.Join(SampleFolderPath, "componentExternalReference.yaml"), settings, token: TestContext.Current.CancellationToken)).Document; var securitySchemeValue = actual.Components.SecuritySchemes["customapikey"]; // Assert @@ -83,10 +83,10 @@ public async Task ParseComponentExternalReferenceWorks() public async Task ParseRootInlineJsonSchemaReferenceWorks() { // Arrange - var filePath = Path.Combine(SampleFolderPath, "rootInlineSchemaReference.yaml"); + var filePath = Path.Join(SampleFolderPath, "rootInlineSchemaReference.yaml"); // Act - var actual = (await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings)).Document; + var actual = (await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken)).Document; var schema = actual.Paths["/item"].Operations[HttpMethod.Get].Responses["200"].Content["application/json"].Schema; // Assert @@ -97,10 +97,10 @@ public async Task ParseRootInlineJsonSchemaReferenceWorks() public async Task ParseSubschemaInlineJsonSchemaReferenceWorks() { // Arrange - var filePath = Path.Combine(SampleFolderPath, "subschemaInlineSchemaReference.yaml"); + var filePath = Path.Join(SampleFolderPath, "subschemaInlineSchemaReference.yaml"); // Act - var actual = (await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings)).Document; + var actual = (await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken)).Document; var schema = actual.Paths["/items"].Operations[HttpMethod.Get].Responses["200"].Content["application/json"].Schema.Items; // Assert @@ -111,10 +111,10 @@ public async Task ParseSubschemaInlineJsonSchemaReferenceWorks() public async Task ParseRootComponentJsonSchemaReferenceWorks() { // Arrange - var filePath = Path.Combine(SampleFolderPath, "rootComponentSchemaReference.yaml"); + var filePath = Path.Join(SampleFolderPath, "rootComponentSchemaReference.yaml"); // Act - var actual = (await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings)).Document; + var actual = (await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken)).Document; var schema = actual.Components.Schemas["specialitem"]; // Assert @@ -126,10 +126,10 @@ public async Task ParseRootComponentJsonSchemaReferenceWorks() public async Task ParseSubschemaComponentJsonSchemaReferenceWorks() { // Arrange - var filePath = Path.Combine(SampleFolderPath, "subschemaComponentSchemaReference.yaml"); + var filePath = Path.Join(SampleFolderPath, "subschemaComponentSchemaReference.yaml"); // Act - var actual = (await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings)).Document; + var actual = (await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken)).Document; var schema = actual.Components.Schemas["items"].Items; // Assert @@ -140,10 +140,10 @@ public async Task ParseSubschemaComponentJsonSchemaReferenceWorks() public async Task ParseInternalComponentSubschemaJsonSchemaReferenceWorks() { // Arrange - var filePath = Path.Combine(SampleFolderPath, "internalComponentsSubschemaReference.yaml"); + var filePath = Path.Join(SampleFolderPath, "internalComponentsSubschemaReference.yaml"); // Act - var actual = (await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings)).Document; + var actual = (await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken)).Document; var addressSchema = actual.Paths["/person/{id}/address"].Operations[HttpMethod.Get].Responses["200"].Content["application/json"].Schema; var itemsSchema = actual.Paths["/human"].Operations[HttpMethod.Get].Responses["200"].Content["application/json"].Schema; @@ -156,7 +156,7 @@ public async Task ParseInternalComponentSubschemaJsonSchemaReferenceWorks() public async Task ParseExternalComponentSubschemaJsonSchemaReferenceWorks() { // Arrange - var path = Path.Combine(Directory.GetCurrentDirectory(), SampleFolderPath); + var path = Path.Join(Directory.GetCurrentDirectory(), SampleFolderPath); var settings = new OpenApiReaderSettings { LoadExternalRefs = true, @@ -165,7 +165,7 @@ public async Task ParseExternalComponentSubschemaJsonSchemaReferenceWorks() settings.AddYamlReader(); // Act - var actual = (await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "externalComponentSubschemaReference.yaml"), settings)).Document; + var actual = (await OpenApiDocument.LoadAsync(Path.Join(SampleFolderPath, "externalComponentSubschemaReference.yaml"), settings, token: TestContext.Current.CancellationToken)).Document; var schema = actual.Paths["/person/{id}"].Operations[HttpMethod.Get].Responses["200"].Content["application/json"].Schema; // Assert @@ -196,7 +196,7 @@ await File.WriteAllTextAsync(rootPath, application/json: schema: $ref: 'OAS-schemas.yaml#address' -"); +", TestContext.Current.CancellationToken); await File.WriteAllTextAsync(externalPath, @" openapi: 3.2.0 @@ -211,7 +211,7 @@ await File.WriteAllTextAsync(externalPath, address: $anchor: address type: object -"); +", TestContext.Current.CancellationToken); try { @@ -222,7 +222,7 @@ await File.WriteAllTextAsync(externalPath, }; settings.AddYamlReader(); - var result = await OpenApiDocument.LoadAsync(rootPath, settings); + var result = await OpenApiDocument.LoadAsync(rootPath, settings, token: TestContext.Current.CancellationToken); var schema = result.Document.Paths["/person/{id}"].Operations[HttpMethod.Get].Responses["200"].Content["application/json"].Schema; Assert.Equal(JsonSchemaType.Object, schema.Type); @@ -257,7 +257,7 @@ await File.WriteAllTextAsync(rootPath, application/json: schema: $ref: 'OAS-schemas.yaml#/person/properties/address' -"); +", TestContext.Current.CancellationToken); await File.WriteAllTextAsync(externalPath, @" person: @@ -265,7 +265,7 @@ await File.WriteAllTextAsync(externalPath, properties: address: type: object -"); +", TestContext.Current.CancellationToken); try { @@ -276,7 +276,7 @@ await File.WriteAllTextAsync(externalPath, }; settings.AddYamlReader(); - var result = await OpenApiDocument.LoadAsync(rootPath, settings); + var result = await OpenApiDocument.LoadAsync(rootPath, settings, token: TestContext.Current.CancellationToken); var schema = result.Document.Paths["/person/{id}"].Operations[HttpMethod.Get].Responses["200"].Content["application/json"].Schema; var schemaReference = Assert.IsType(schema); @@ -293,10 +293,10 @@ await File.WriteAllTextAsync(externalPath, public async Task ParseReferenceToInternalComponentUsingDollarIdWorks() { // Arrange - var filePath = Path.Combine(SampleFolderPath, "internalComponentReferenceUsingId.yaml"); + var filePath = Path.Join(SampleFolderPath, "internalComponentReferenceUsingId.yaml"); // Act - var actual = (await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings)).Document; + var actual = (await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken)).Document; var schema = actual.Paths["/person/{id}"].Operations[HttpMethod.Get].Responses["200"].Content["application/json"].Schema; // Assert @@ -338,11 +338,11 @@ await File.WriteAllTextAsync(rootPath, properties: street: type: string -"); +", TestContext.Current.CancellationToken); try { - var result = await OpenApiDocument.LoadAsync(rootPath, SettingsFixture.ReaderSettings); + var result = await OpenApiDocument.LoadAsync(rootPath, SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var schema = result.Document.Paths["/person/{id}/address"].Operations[HttpMethod.Get].Responses["200"].Content["application/json"].Schema; Assert.Equal(JsonSchemaType.Object, schema.Type); @@ -358,12 +358,12 @@ await File.WriteAllTextAsync(rootPath, public async Task ParseLocalReferenceToJsonSchemaResourceWorks() { // Arrange - var filePath = Path.Combine(SampleFolderPath, "localReferenceToJsonSchemaResource.yaml"); + var filePath = Path.Join(SampleFolderPath, "localReferenceToJsonSchemaResource.yaml"); var stringWriter = new StringWriter(); var writer = new OpenApiYamlWriter(stringWriter); // Act - var actual = (await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings)).Document; + var actual = (await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken)).Document; var schema = actual.Components.Schemas["a"].Properties["b"].Properties["c"].Properties["b"]; schema.SerializeAsV32(writer); @@ -452,10 +452,10 @@ public void ResolveSubSchema_ShouldRecurseIntoAllOfComposition() public async Task ShouldResolveRelativeSubReference() { // Arrange - var filePath = Path.Combine(SampleFolderPath, "relativeSubschemaReference.json"); + var filePath = Path.Join(SampleFolderPath, "relativeSubschemaReference.json"); // Act - var (actual, _) = await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings); + var (actual, _) = await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var fooComponentSchema = actual.Components.Schemas["Foo"]; var seq1Property = fooComponentSchema.Properties["seq1"]; @@ -469,9 +469,9 @@ public async Task ShouldResolveRelativeSubReference() public async Task ShouldResolveRelativeSubReferenceUsingParsingContext() { // Arrange - var filePath = Path.Combine(SampleFolderPath, "relativeSubschemaReference.json"); + var filePath = Path.Join(SampleFolderPath, "relativeSubschemaReference.json"); using var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read); - var jsonNode = await JsonNode.ParseAsync(fs); + var jsonNode = await JsonNode.ParseAsync(fs, cancellationToken: TestContext.Current.CancellationToken); var schemaJsonNode = jsonNode["components"]?["schemas"]?["Foo"]; Assert.NotNull(schemaJsonNode); var diagnostic = new OpenApiDiagnostic(); @@ -562,10 +562,10 @@ public void ShouldResolveAbsoluteSubReferenceFromTheObjectModel() public async Task ShouldResolveRecursiveRelativeSubReference() { // Arrange - var filePath = Path.Combine(SampleFolderPath, "recursiveRelativeSubschemaReference.json"); + var filePath = Path.Join(SampleFolderPath, "recursiveRelativeSubschemaReference.json"); // Act - var (actual, _) = await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings); + var (actual, _) = await OpenApiDocument.LoadAsync(filePath, SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var fooComponentSchema = actual.Components.Schemas["Foo"]; var fooSchemaParentProperty = fooComponentSchema.Properties["parent"]; @@ -583,9 +583,9 @@ public async Task ShouldResolveRecursiveRelativeSubReference() [Fact] public async Task ShouldResolveReferencesInSchemasFromSystemTextJson() { - var filePath = Path.Combine(SampleFolderPath, "STJSchema.json"); + var filePath = Path.Join(SampleFolderPath, "STJSchema.json"); using var fs = File.OpenRead(filePath); - var jsonNode = await JsonNode.ParseAsync(fs); + var jsonNode = await JsonNode.ParseAsync(fs, cancellationToken: TestContext.Current.CancellationToken); var parsingContext = new ParsingContext(new OpenApiDiagnostic()); var document = new OpenApiDocument(); diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiCallbackTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiCallbackTests.cs index e7982160c..e040b6ba5 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiCallbackTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiCallbackTests.cs @@ -19,7 +19,7 @@ public class OpenApiCallbackTests public async Task ParseBasicCallbackShouldSucceed() { // Act - var callback = await OpenApiModelFactory.LoadAsync(Path.Combine(SampleFolderPath, "basicCallback.yaml"), OpenApiSpecVersion.OpenApi3_0, new(), SettingsFixture.ReaderSettings); + var callback = await OpenApiModelFactory.LoadAsync(Path.Join(SampleFolderPath, "basicCallback.yaml"), OpenApiSpecVersion.OpenApi3_0, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.Equivalent( @@ -59,10 +59,10 @@ public async Task ParseBasicCallbackShouldSucceed() [Fact] public async Task ParseCallbackWithReferenceShouldSucceed() { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "callbackWithReference.yaml")); + using var stream = Resources.GetStream(Path.Join(SampleFolderPath, "callbackWithReference.yaml")); // Act - var result = await OpenApiModelFactory.LoadAsync(stream, OpenApiConstants.Yaml, SettingsFixture.ReaderSettings); + var result = await OpenApiModelFactory.LoadAsync(stream, OpenApiConstants.Yaml, SettingsFixture.ReaderSettings, cancellationToken: TestContext.Current.CancellationToken); // Assert var path = result.Document.Paths.First().Value; @@ -113,7 +113,7 @@ public async Task ParseCallbackWithReferenceShouldSucceed() public async Task ParseMultipleCallbacksWithReferenceShouldSucceed() { // Act - var result = await OpenApiModelFactory.LoadAsync(Path.Combine(SampleFolderPath, "multipleCallbacksWithReference.yaml"), SettingsFixture.ReaderSettings); + var result = await OpenApiModelFactory.LoadAsync(Path.Join(SampleFolderPath, "multipleCallbacksWithReference.yaml"), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert var path = result.Document.Paths.First().Value; diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDiscriminatorTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDiscriminatorTests.cs index 5e4a36d91..7ef4ffcfe 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDiscriminatorTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDiscriminatorTests.cs @@ -18,10 +18,10 @@ public class OpenApiDiscriminatorTests public async Task ParseBasicDiscriminatorShouldSucceed() { // Arrange - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "basicDiscriminator.yaml")); + using var stream = Resources.GetStream(Path.Join(SampleFolderPath, "basicDiscriminator.yaml")); // Copy stream to MemoryStream using var memoryStream = new MemoryStream(); - await stream.CopyToAsync(memoryStream); + await stream.CopyToAsync(memoryStream, TestContext.Current.CancellationToken); memoryStream.Position = 0; // Act diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs index 53e3802ed..7034eb46d 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiDocumentTests.cs @@ -13,7 +13,6 @@ using System.Text.Json.Nodes; using System.Threading; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.OpenApi.Reader; using Microsoft.OpenApi.Tests; using Moq; @@ -55,7 +54,7 @@ public void ParseDocumentFromInlineStringShouldSucceed() paths: {}", OpenApiConstants.Yaml, SettingsFixture.ReaderSettings); - result.Document.Should().BeEquivalentTo( + OpenApiTestAssert.Equivalent( new OpenApiDocument { Info = new OpenApiInfo @@ -64,7 +63,7 @@ public void ParseDocumentFromInlineStringShouldSucceed() Version = "0.9.1" }, Paths = new OpenApiPaths() - }, options => options.Excluding(x => x.Workspace).Excluding(y => y.BaseUri)); + }, result.Document, nameof(OpenApiDocument.Workspace), nameof(OpenApiDocument.BaseUri)); Assert.Equivalent( new OpenApiDiagnostic() @@ -92,11 +91,11 @@ public void ParseInlineStringWithoutProvidingFormatSucceeds() [Fact] public async Task ParseBasicDocumentWithMultipleServersShouldSucceed() { - var path = Path.Combine(SampleFolderPath, "basicDocumentWithMultipleServers.yaml"); - var result = await OpenApiDocument.LoadAsync(path, SettingsFixture.ReaderSettings); + var path = Path.Join(SampleFolderPath, "basicDocumentWithMultipleServers.yaml"); + var result = await OpenApiDocument.LoadAsync(path, SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); Assert.Empty(result.Diagnostic.Errors); - result.Document.Should().BeEquivalentTo( + OpenApiTestAssert.Equivalent( new OpenApiDocument { Info = new OpenApiInfo @@ -118,20 +117,20 @@ public async Task ParseBasicDocumentWithMultipleServersShouldSucceed() } ], Paths = new OpenApiPaths() - }, options => options.Excluding(x => x.Workspace).Excluding(y => y.BaseUri)); + }, result.Document, nameof(OpenApiDocument.Workspace), nameof(OpenApiDocument.BaseUri)); } [Fact] public async Task ParseBrokenMinimalDocumentShouldYieldExpectedDiagnostic() { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "brokenMinimalDocument.yaml")); + using var stream = Resources.GetStream(Path.Join(SampleFolderPath, "brokenMinimalDocument.yaml")); // Copy stream to MemoryStream using var memoryStream = new MemoryStream(); - await stream.CopyToAsync(memoryStream); + await stream.CopyToAsync(memoryStream, TestContext.Current.CancellationToken); memoryStream.Position = 0; - var result = await OpenApiDocument.LoadAsync(memoryStream, settings: SettingsFixture.ReaderSettings); + var result = await OpenApiDocument.LoadAsync(memoryStream, settings: SettingsFixture.ReaderSettings, cancellationToken: TestContext.Current.CancellationToken); - result.Document.Should().BeEquivalentTo( + OpenApiTestAssert.Equivalent( new OpenApiDocument { Info = new OpenApiInfo @@ -139,7 +138,7 @@ public async Task ParseBrokenMinimalDocumentShouldYieldExpectedDiagnostic() Version = "0.9" }, Paths = new OpenApiPaths() - }, options => options.Excluding(x => x.Workspace).Excluding(y => y.BaseUri)); + }, result.Document, nameof(OpenApiDocument.Workspace), nameof(OpenApiDocument.BaseUri)); Assert.Equivalent( new OpenApiDiagnostic @@ -190,9 +189,9 @@ public void ParseDocumentWithMalformedReferenceShouldYieldExpectedDiagnostic() [Fact] public async Task ParseMinimalDocumentShouldSucceed() { - var result = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "minimalDocument.yaml"), SettingsFixture.ReaderSettings); + var result = await OpenApiDocument.LoadAsync(Path.Join(SampleFolderPath, "minimalDocument.yaml"), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); - result.Document.Should().BeEquivalentTo( + OpenApiTestAssert.Equivalent( new OpenApiDocument { Info = new OpenApiInfo @@ -201,7 +200,7 @@ public async Task ParseMinimalDocumentShouldSucceed() Version = "0.9.1" }, Paths = new OpenApiPaths() - }, options => options.Excluding(x => x.Workspace).Excluding(y => y.BaseUri)); + }, result.Document, nameof(OpenApiDocument.Workspace), nameof(OpenApiDocument.BaseUri)); Assert.Equivalent( new OpenApiDiagnostic() @@ -214,8 +213,8 @@ public async Task ParseMinimalDocumentShouldSucceed() [Fact] public async Task ParseStandardPetStoreDocumentShouldSucceed() { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "petStore.yaml")); - var actual = await OpenApiDocument.LoadAsync(stream, OpenApiConstants.Yaml, SettingsFixture.ReaderSettings); + using var stream = Resources.GetStream(Path.Join(SampleFolderPath, "petStore.yaml")); + var actual = await OpenApiDocument.LoadAsync(stream, OpenApiConstants.Yaml, SettingsFixture.ReaderSettings, cancellationToken: TestContext.Current.CancellationToken); var components = new OpenApiComponents { @@ -591,7 +590,7 @@ public async Task ParseStandardPetStoreDocumentShouldSucceed() Components = components }; - actual.Document.Should().BeEquivalentTo(expectedDoc, options => options.Excluding(x => x.Workspace).Excluding(y => y.BaseUri)); + OpenApiTestAssert.Equivalent(expectedDoc, actual.Document, nameof(OpenApiDocument.Workspace), nameof(OpenApiDocument.BaseUri)); Assert.Equivalent( new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0, Format = OpenApiConstants.Yaml }, actual.Diagnostic); @@ -600,8 +599,8 @@ public async Task ParseStandardPetStoreDocumentShouldSucceed() [Fact] public async Task ParseModifiedPetStoreDocumentWithTagAndSecurityShouldSucceed() { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "petStoreWithTagAndSecurity.yaml")); - var actual = await OpenApiDocument.LoadAsync(stream, OpenApiConstants.Yaml, SettingsFixture.ReaderSettings); + using var stream = Resources.GetStream(Path.Join(SampleFolderPath, "petStoreWithTagAndSecurity.yaml")); + var actual = await OpenApiDocument.LoadAsync(stream, OpenApiConstants.Yaml, SettingsFixture.ReaderSettings, cancellationToken: TestContext.Current.CancellationToken); var components = new OpenApiComponents { @@ -1060,12 +1059,10 @@ [new OpenApiSecuritySchemeReference("securitySchemeName2")] = expected.RegisterComponents(); expected.SetReferenceHostDocument(); - actual.Document.Should().BeEquivalentTo(expected, options => options - .IgnoringCyclicReferences() - .Excluding(ctx => ctx.Path.Contains("Paths[\"/pets\"].Operations[HttpMethod.Get].Tags")) - .Excluding(ctx => ctx.Path.Contains("Paths[\"/pets\"].Operations[HttpMethod.Post].Tags")) - .Excluding(x => x.Workspace) - .Excluding(y => y.BaseUri)); + OpenApiTestAssert.Equivalent(expected, actual.Document, path => + path.MemberName is nameof(OpenApiDocument.Workspace) or nameof(OpenApiDocument.BaseUri) + || path.Value.Contains($"{nameof(OpenApiDocument.Paths)}[/pets].{nameof(OpenApiPathItem.Operations)}[{HttpMethod.Get}].{nameof(OpenApiOperation.Tags)}") + || path.Value.Contains($"{nameof(OpenApiDocument.Paths)}[/pets].{nameof(OpenApiPathItem.Operations)}[{HttpMethod.Post}].{nameof(OpenApiOperation.Tags)}")); Assert.Equivalent( new OpenApiDiagnostic() { SpecificationVersion = OpenApiSpecVersion.OpenApi3_0, Format = OpenApiConstants.Yaml }, actual.Diagnostic); @@ -1074,7 +1071,7 @@ [new OpenApiSecuritySchemeReference("securitySchemeName2")] = [Fact] public async Task ParsePetStoreExpandedShouldSucceed() { - var actual = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "petStoreExpanded.yaml"), SettingsFixture.ReaderSettings); + var actual = await OpenApiDocument.LoadAsync(Path.Join(SampleFolderPath, "petStoreExpanded.yaml"), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // TODO: Create the object in memory and compare with the one read from YAML file. @@ -1085,7 +1082,7 @@ public async Task ParsePetStoreExpandedShouldSucceed() [Fact] public async Task GlobalSecurityRequirementShouldReferenceSecurityScheme() { - var result = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "securedApi.yaml"), SettingsFixture.ReaderSettings); + var result = await OpenApiDocument.LoadAsync(Path.Join(SampleFolderPath, "securedApi.yaml"), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var securityRequirement = result.Document.Security[0]; @@ -1095,11 +1092,11 @@ public async Task GlobalSecurityRequirementShouldReferenceSecurityScheme() [Fact] public async Task HeaderParameterShouldAllowExample() { - var result = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "apiWithFullHeaderComponent.yaml"), SettingsFixture.ReaderSettings); + var result = await OpenApiDocument.LoadAsync(Path.Join(SampleFolderPath, "apiWithFullHeaderComponent.yaml"), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var exampleHeader = result.Document.Components?.Headers?["example-header"]; Assert.NotNull(exampleHeader); - exampleHeader.Should().BeEquivalentTo( + OpenApiTestAssert.Equivalent( new OpenApiHeader() { Description = "Test header with example", @@ -1115,12 +1112,11 @@ public async Task HeaderParameterShouldAllowExample() Type = JsonSchemaType.String, Format = "uuid" }, - }, options => options.IgnoringCyclicReferences() - .Excluding(e => e.Example.Parent)); + }, exampleHeader, nameof(JsonNode.Parent)); var examplesHeader = result.Document.Components?.Headers?["examples-header"]; Assert.NotNull(examplesHeader); - examplesHeader.Should().BeEquivalentTo( + OpenApiTestAssert.Equivalent( new OpenApiHeader() { Description = "Test header with example", @@ -1148,16 +1144,14 @@ public async Task HeaderParameterShouldAllowExample() Type = JsonSchemaType.String, Format = "uuid" }, - }, options => options.IgnoringCyclicReferences() - .Excluding(e => e.Examples["uuid1"].Value.Parent) - .Excluding(e => e.Examples["uuid2"].Value.Parent)); + }, examplesHeader, nameof(JsonNode.Parent)); } [Fact] public async Task ParseDocumentWithReferencedSecuritySchemeWorks() { // Act - var result = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "docWithSecuritySchemeReference.yaml"), SettingsFixture.ReaderSettings); + var result = await OpenApiDocument.LoadAsync(Path.Join(SampleFolderPath, "docWithSecuritySchemeReference.yaml"), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var securityScheme = result.Document.Components.SecuritySchemes["OAuth2"]; // Assert @@ -1168,10 +1162,10 @@ public async Task ParseDocumentWithReferencedSecuritySchemeWorks() public async Task ParseDocumentWithJsonSchemaReferencesWorks() { // Arrange - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "docWithJsonSchema.yaml")); + using var stream = Resources.GetStream(Path.Join(SampleFolderPath, "docWithJsonSchema.yaml")); // Act - var result = await OpenApiDocument.LoadAsync(stream, OpenApiConstants.Yaml, SettingsFixture.ReaderSettings); + var result = await OpenApiDocument.LoadAsync(stream, OpenApiConstants.Yaml, SettingsFixture.ReaderSettings, cancellationToken: TestContext.Current.CancellationToken); var actualSchema = result.Document.Paths["/users/{userId}"].Operations[HttpMethod.Get].Responses["200"].Content["application/json"].Schema; @@ -1184,7 +1178,7 @@ public async Task ParseDocumentWithJsonSchemaReferencesWorks() public async Task ValidateExampleShouldNotHaveDataTypeMismatch() { // Act - var result = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "documentWithDateExampleInSchema.yaml"), SettingsFixture.ReaderSettings); + var result = await OpenApiDocument.LoadAsync(Path.Join(SampleFolderPath, "documentWithDateExampleInSchema.yaml"), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert var warnings = result.Diagnostic.Warnings; @@ -1249,7 +1243,7 @@ public async Task ParsesDoubleHopReferences() { using var stream = new MemoryStream(Encoding.UTF8.GetBytes(DoubleHopReferenceSerializedDoc)); - var (document, _) = await OpenApiDocument.LoadAsync(stream); + var (document, _) = await OpenApiDocument.LoadAsync(stream, cancellationToken: TestContext.Current.CancellationToken); Assert.NotNull(document); var petReferenceInResponse = Assert.IsType(document.Paths["/pets"].Operations[HttpMethod.Get].Responses["200"].Content["application/json"].Schema); @@ -1331,7 +1325,7 @@ public async Task SerializesDoubleHopeReferences() using var stringWriter = new StringWriter(); var writer = new OpenApiJsonWriter(stringWriter); document.SerializeAsV31(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); var serializedDoc = stringWriter.ToString(); @@ -1418,25 +1412,19 @@ public async Task ParseDocWithRefsUsingProxyReferencesSucceeds() default: 10 """; - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "minifiedPetStore.yaml")); + using var stream = Resources.GetStream(Path.Join(SampleFolderPath, "minifiedPetStore.yaml")); // Act - var doc = (await OpenApiDocument.LoadAsync(stream, settings: SettingsFixture.ReaderSettings)).Document; + var doc = (await OpenApiDocument.LoadAsync(stream, settings: SettingsFixture.ReaderSettings, cancellationToken: TestContext.Current.CancellationToken)).Document; var actualParam = doc.Paths["/pets"].Operations[HttpMethod.Get].Parameters[0]; - var outputDoc = (await doc.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0)).MakeLineBreaksEnvironmentNeutral(); + var outputDoc = (await doc.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken)).MakeLineBreaksEnvironmentNeutral(); var expectedParam = expected.Paths["/pets"].Operations[HttpMethod.Get].Parameters[0]; var expectedParamReference = Assert.IsType(expectedParam); var actualParamReference = Assert.IsType(actualParam); // Assert - actualParamReference.Should().BeEquivalentTo(expectedParamReference, options => options - .Excluding(x => x.Reference) - .Excluding(x => x.Target) - .Excluding(x => x.RecursiveTarget) - .Excluding(x => x.Schema.Default.Parent) - .Excluding(x => x.Schema.Default.Options) - .IgnoringCyclicReferences()); + OpenApiTestAssert.Equivalent(expectedParamReference, actualParamReference, nameof(OpenApiParameterReference.Reference), nameof(OpenApiParameterReference.Target), nameof(OpenApiParameterReference.RecursiveTarget), nameof(JsonNode.Parent), nameof(JsonNode.Options)); Assert.Equal(expectedSerializedDoc.MakeLineBreaksEnvironmentNeutral(), outputDoc); } @@ -1487,7 +1475,7 @@ public void ParseBasicDocumentWithServerVariableShouldSucceed() Format = OpenApiConstants.Yaml }, result.Diagnostic); - result.Document.Should().BeEquivalentTo(expected, options => options.Excluding(x => x.BaseUri)); + OpenApiTestAssert.Equivalent(expected, result.Document, nameof(OpenApiDocument.BaseUri)); } [Fact] @@ -1513,7 +1501,7 @@ public void ParseBasicDocumentWithServerVariableAndNoDefaultShouldFail() [Fact] public async Task ParseDocumentWithEmptyPathsSucceeds() { - var result = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "docWithEmptyPaths.yaml"), SettingsFixture.ReaderSettings); + var result = await OpenApiDocument.LoadAsync(Path.Join(SampleFolderPath, "docWithEmptyPaths.yaml"), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); Assert.Empty(result.Diagnostic.Errors); } @@ -1521,17 +1509,17 @@ public async Task ParseDocumentWithEmptyPathsSucceeds() public async Task ParseDocumentWithExampleReferencesPasses() { // Act & Assert: Ensure no NullReferenceException is thrown - var result = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "docWithExampleReferences.yaml"), SettingsFixture.ReaderSettings); + var result = await OpenApiDocument.LoadAsync(Path.Join(SampleFolderPath, "docWithExampleReferences.yaml"), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); Assert.Empty(result.Diagnostic.Errors); } [Fact] public async Task ParseDocumentWithNonStandardMIMETypePasses() { - var path = Path.Combine(SampleFolderPath, "basicDocumentWithMultipleServers.yaml"); + var path = Path.Join(SampleFolderPath, "basicDocumentWithMultipleServers.yaml"); using var stream = Resources.GetStream(path); using var streamReader = new StreamReader(stream); - var contentAsString = await streamReader.ReadToEndAsync(); + var contentAsString = await streamReader.ReadToEndAsync(TestContext.Current.CancellationToken); var mockMessageHandler = new Mock(); mockMessageHandler.Protected() .Setup>("SendAsync", ItExpr.IsAny(), ItExpr.IsAny()) @@ -1545,7 +1533,7 @@ public async Task ParseDocumentWithNonStandardMIMETypePasses() }; settings.AddYamlReader(); // Act & Assert: Ensure NotSupportedException is not thrown for non-standard MIME type: text/x-yaml - var result = await OpenApiDocument.LoadAsync("https://localhost/doesntmatter/foo.bar", settings); + var result = await OpenApiDocument.LoadAsync("https://localhost/doesntmatter/foo.bar", settings, token: TestContext.Current.CancellationToken); Assert.NotNull(result.Document); } } diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiEncodingTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiEncodingTests.cs index 0c0c4e96f..240a98f3d 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiEncodingTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiEncodingTests.cs @@ -18,7 +18,7 @@ public class OpenApiEncodingTests public async Task ParseBasicEncodingShouldSucceed() { // Act - var encoding = await OpenApiModelFactory.LoadAsync(Path.Combine(SampleFolderPath, "basicEncoding.yaml"), OpenApiSpecVersion.OpenApi3_0, new(), SettingsFixture.ReaderSettings); + var encoding = await OpenApiModelFactory.LoadAsync(Path.Join(SampleFolderPath, "basicEncoding.yaml"), OpenApiSpecVersion.OpenApi3_0, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.Equivalent( @@ -31,10 +31,10 @@ public async Task ParseBasicEncodingShouldSucceed() [Fact] public async Task ParseAdvancedEncodingShouldSucceed() { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "advancedEncoding.yaml")); + using var stream = Resources.GetStream(Path.Join(SampleFolderPath, "advancedEncoding.yaml")); // Act - var encoding = await OpenApiModelFactory.LoadAsync(stream, OpenApiSpecVersion.OpenApi3_0, new(), settings: SettingsFixture.ReaderSettings); + var encoding = await OpenApiModelFactory.LoadAsync(stream, OpenApiSpecVersion.OpenApi3_0, new(), settings: SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.Equivalent( @@ -60,7 +60,7 @@ public async Task ParseAdvancedEncodingShouldSucceed() public async Task ParseEncodingWithAllowReservedShouldSucceed() { // Act - var encoding = await OpenApiModelFactory.LoadAsync(Path.Combine(SampleFolderPath, "encodingWithAllowReserved.yaml"), OpenApiSpecVersion.OpenApi3_0, new(), SettingsFixture.ReaderSettings); + var encoding = await OpenApiModelFactory.LoadAsync(Path.Join(SampleFolderPath, "encodingWithAllowReserved.yaml"), OpenApiSpecVersion.OpenApi3_0, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.Equivalent( diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiExampleTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiExampleTests.cs index e888b3d16..ead7840cd 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiExampleTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiExampleTests.cs @@ -4,8 +4,8 @@ using System.IO; using System.Text.Json.Nodes; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.OpenApi.Reader; +using Microsoft.OpenApi.Tests; using Xunit; namespace Microsoft.OpenApi.Readers.Tests.V3Tests @@ -18,7 +18,7 @@ public class OpenApiExampleTests [Fact] public async Task ParseAdvancedExampleShouldSucceed() { - var example = await OpenApiModelFactory.LoadAsync(Path.Combine(SampleFolderPath, "advancedExample.yaml"), OpenApiSpecVersion.OpenApi3_0, new(), SettingsFixture.ReaderSettings); + var example = await OpenApiModelFactory.LoadAsync(Path.Join(SampleFolderPath, "advancedExample.yaml"), OpenApiSpecVersion.OpenApi3_0, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var expected = new OpenApiExample { Value = new JsonObject @@ -56,21 +56,13 @@ public async Task ParseAdvancedExampleShouldSucceed() } }; - example.Should().BeEquivalentTo(expected, options => options.IgnoringCyclicReferences() - .Excluding(e => e.Value["versions"][0]["status"].Root) - .Excluding(e => e.Value["versions"][0]["id"].Root) - .Excluding(e => e.Value["versions"][0]["links"][0]["href"].Root) - .Excluding(e => e.Value["versions"][0]["links"][0]["rel"].Root) - .Excluding(e => e.Value["versions"][1]["status"].Root) - .Excluding(e => e.Value["versions"][1]["id"].Root) - .Excluding(e => e.Value["versions"][1]["links"][0]["href"].Root) - .Excluding(e => e.Value["versions"][1]["links"][0]["rel"].Root)); + OpenApiTestAssert.Equivalent(expected, example, nameof(JsonNode.Root)); } [Fact] public async Task ParseExampleForcedStringSucceed() { - var result = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "explicitString.yaml"), SettingsFixture.ReaderSettings); + var result = await OpenApiDocument.LoadAsync(Path.Join(SampleFolderPath, "explicitString.yaml"), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); Assert.Empty(result.Diagnostic.Errors); } @@ -79,10 +71,10 @@ public async Task ParseExampleWithDataValueExtensionShouldSucceed() { // Arrange & Act var example = await OpenApiModelFactory.LoadAsync( - Path.Combine(SampleFolderPath, "exampleWithDataValue.yaml"), + Path.Join(SampleFolderPath, "exampleWithDataValue.yaml"), OpenApiSpecVersion.OpenApi3_0, new(), - SettingsFixture.ReaderSettings); + SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.NotNull(example); @@ -97,10 +89,10 @@ public async Task ParseExampleWithSerializedValueExtensionShouldSucceed() { // Arrange & Act var example = await OpenApiModelFactory.LoadAsync( - Path.Combine(SampleFolderPath, "exampleWithSerializedValue.yaml"), + Path.Join(SampleFolderPath, "exampleWithSerializedValue.yaml"), OpenApiSpecVersion.OpenApi3_0, new(), - SettingsFixture.ReaderSettings); + SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.NotNull(example); diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiHeaderTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiHeaderTests.cs index 980e3f1ec..38281018c 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiHeaderTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiHeaderTests.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.IO; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.OpenApi.Reader; using Xunit; @@ -19,10 +18,10 @@ public class OpenApiHeaderTests public async Task ParseBasicHeaderShouldSucceed() { // Arrange - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "basicHeader.yaml")); + using var stream = Resources.GetStream(Path.Join(SampleFolderPath, "basicHeader.yaml")); // Act - var header = await OpenApiModelFactory.LoadAsync(stream, OpenApiSpecVersion.OpenApi3_0, new(), settings: SettingsFixture.ReaderSettings); + var header = await OpenApiModelFactory.LoadAsync(stream, OpenApiSpecVersion.OpenApi3_0, new(), settings: SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.Equivalent( @@ -40,10 +39,10 @@ public async Task ParseBasicHeaderShouldSucceed() public async Task ParseHeaderWithContentShouldSucceed() { // Arrange - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "headerWithContent.yaml")); + using var stream = Resources.GetStream(Path.Join(SampleFolderPath, "headerWithContent.yaml")); // Act - var header = await OpenApiModelFactory.LoadAsync(stream, OpenApiSpecVersion.OpenApi3_0, new(), settings: SettingsFixture.ReaderSettings); + var header = await OpenApiModelFactory.LoadAsync(stream, OpenApiSpecVersion.OpenApi3_0, new(), settings: SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.Equivalent( @@ -79,10 +78,10 @@ public async Task ParseHeaderWithContentShouldSucceed() public async Task ParseHeaderWithMultipleContentTypesShouldSucceed() { // Arrange - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "headerWithMultipleContentTypes.yaml")); + using var stream = Resources.GetStream(Path.Join(SampleFolderPath, "headerWithMultipleContentTypes.yaml")); // Act - var header = await OpenApiModelFactory.LoadAsync(stream, OpenApiSpecVersion.OpenApi3_0, new(), settings: SettingsFixture.ReaderSettings); + var header = await OpenApiModelFactory.LoadAsync(stream, OpenApiSpecVersion.OpenApi3_0, new(), settings: SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.Equivalent( @@ -120,10 +119,10 @@ public async Task ParseHeaderWithMultipleContentTypesShouldSucceed() public async Task ParseHeaderWithStyleAndContentShouldPreferContent() { // Arrange - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "headerWithStyleAndContent.yaml")); + using var stream = Resources.GetStream(Path.Join(SampleFolderPath, "headerWithStyleAndContent.yaml")); // Act - var header = await OpenApiModelFactory.LoadAsync(stream, OpenApiSpecVersion.OpenApi3_0, new(), settings: SettingsFixture.ReaderSettings); + var header = await OpenApiModelFactory.LoadAsync(stream, OpenApiSpecVersion.OpenApi3_0, new(), settings: SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert // Both content and style can be present, content takes precedence for serialization behavior diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiInfoTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiInfoTests.cs index 726f47e59..a11b044f4 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiInfoTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiInfoTests.cs @@ -6,8 +6,8 @@ using System.IO; using System.Text.Json.Nodes; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.OpenApi.Reader; +using Microsoft.OpenApi.Tests; using Xunit; namespace Microsoft.OpenApi.Readers.Tests.V3Tests @@ -21,10 +21,10 @@ public class OpenApiInfoTests public async Task ParseAdvancedInfoShouldSucceed() { // Act - var openApiInfo = await OpenApiModelFactory.LoadAsync(Path.Combine(SampleFolderPath, "advancedInfo.yaml"), OpenApiSpecVersion.OpenApi3_0, new(), SettingsFixture.ReaderSettings); + var openApiInfo = await OpenApiModelFactory.LoadAsync(Path.Join(SampleFolderPath, "advancedInfo.yaml"), OpenApiSpecVersion.OpenApi3_0, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert - openApiInfo.Should().BeEquivalentTo( + OpenApiTestAssert.Equivalent( new OpenApiInfo { Title = "Advanced Info", @@ -61,27 +61,14 @@ public async Task ParseAdvancedInfoShouldSucceed() }), ["x-list"] = new JsonNodeExtension (new JsonArray { "1", "2" }) } - }, options => options.IgnoringCyclicReferences() - .Excluding(i => ((JsonNodeExtension)i.Contact.Extensions["x-twitter"]).Node.Parent) - .Excluding(i => ((JsonNodeExtension)i.License.Extensions["x-disclaimer"]).Node.Parent) - .Excluding(i => ((JsonNodeExtension)i.Extensions["x-something"]).Node.Parent) - .Excluding(i => ((JsonNodeExtension)i.Extensions["x-contact"]).Node["name"].Parent) - .Excluding(i => ((JsonNodeExtension)i.Extensions["x-contact"]).Node["name"].Root) - .Excluding(i => ((JsonNodeExtension)i.Extensions["x-contact"]).Node["url"].Parent) - .Excluding(i => ((JsonNodeExtension)i.Extensions["x-contact"]).Node["url"].Root) - .Excluding(i => ((JsonNodeExtension)i.Extensions["x-contact"]).Node["email"].Parent) - .Excluding(i => ((JsonNodeExtension)i.Extensions["x-contact"]).Node["email"].Root) - .Excluding(i => ((JsonNodeExtension)i.Extensions["x-list"]).Node[0].Parent) - .Excluding(i => ((JsonNodeExtension)i.Extensions["x-list"]).Node[0].Root) - .Excluding(i => ((JsonNodeExtension)i.Extensions["x-list"]).Node[1].Parent) - .Excluding(i => ((JsonNodeExtension)i.Extensions["x-list"]).Node[1].Root)); + }, openApiInfo, nameof(JsonNode.Parent), nameof(JsonNode.Root)); } [Fact] public async Task ParseBasicInfoShouldSucceed() { // Act - var openApiInfo = await OpenApiModelFactory.LoadAsync(Path.Combine(SampleFolderPath, "basicInfo.yaml"), OpenApiSpecVersion.OpenApi3_0, new(), SettingsFixture.ReaderSettings); + var openApiInfo = await OpenApiModelFactory.LoadAsync(Path.Join(SampleFolderPath, "basicInfo.yaml"), OpenApiSpecVersion.OpenApi3_0, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.Equivalent( @@ -108,10 +95,10 @@ public async Task ParseBasicInfoShouldSucceed() [Fact] public async Task ParseMinimalInfoShouldSucceed() { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "minimalInfo.yaml")); + using var stream = Resources.GetStream(Path.Join(SampleFolderPath, "minimalInfo.yaml")); // Act - var openApiInfo = await OpenApiModelFactory.LoadAsync(stream, OpenApiSpecVersion.OpenApi3_0, new(), settings: SettingsFixture.ReaderSettings); + var openApiInfo = await OpenApiModelFactory.LoadAsync(stream, OpenApiSpecVersion.OpenApi3_0, new(), settings: SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.Equivalent( diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiMediaTypeTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiMediaTypeTests.cs index be0e5cc95..d533a2078 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiMediaTypeTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiMediaTypeTests.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.IO; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.OpenApi.Reader; using Microsoft.OpenApi.Reader.V3; using Microsoft.OpenApi.Tests; @@ -22,10 +21,10 @@ public class OpenApiMediaTypeTests public async Task ParseMediaTypeWithExampleShouldSucceed() { // Act - var mediaType = await OpenApiModelFactory.LoadAsync(Path.Combine(SampleFolderPath, "mediaTypeWithExample.yaml"), OpenApiSpecVersion.OpenApi3_0, new(), SettingsFixture.ReaderSettings); + var mediaType = await OpenApiModelFactory.LoadAsync(Path.Join(SampleFolderPath, "mediaTypeWithExample.yaml"), OpenApiSpecVersion.OpenApi3_0, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert - mediaType.Should().BeEquivalentTo( + OpenApiTestAssert.Equivalent( new OpenApiMediaType { Example = 5, @@ -34,19 +33,17 @@ public async Task ParseMediaTypeWithExampleShouldSucceed() Type = JsonSchemaType.Number, Format = "float" } - }, options => options.IgnoringCyclicReferences() - .Excluding(m => m.Example.Parent) - ); + }, mediaType, nameof(JsonNode.Parent)); } [Fact] public async Task ParseMediaTypeWithExamplesShouldSucceed() { // Act - var mediaType = await OpenApiModelFactory.LoadAsync(Path.Combine(SampleFolderPath, "mediaTypeWithExamples.yaml"), OpenApiSpecVersion.OpenApi3_0, new(), SettingsFixture.ReaderSettings); + var mediaType = await OpenApiModelFactory.LoadAsync(Path.Join(SampleFolderPath, "mediaTypeWithExamples.yaml"), OpenApiSpecVersion.OpenApi3_0, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert - mediaType.Should().BeEquivalentTo( + OpenApiTestAssert.Equivalent( new OpenApiMediaType { Examples = new Dictionary @@ -65,9 +62,7 @@ public async Task ParseMediaTypeWithExamplesShouldSucceed() Type = JsonSchemaType.Number, Format = "float" } - }, options => options.IgnoringCyclicReferences() - .Excluding(m => m.Examples["example1"].Value.Parent) - .Excluding(m => m.Examples["example2"].Value.Parent)); + }, mediaType, nameof(JsonNode.Parent)); } [Fact] @@ -103,14 +98,14 @@ public async Task ParseMediaTypeWithEmptyArrayInExamplesWorks() } "; JsonNode node; - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "examplesWithEmptyArray.json"))) + using (var stream = Resources.GetStream(Path.Join(SampleFolderPath, "examplesWithEmptyArray.json"))) { node = TestHelper.CreateYamlJsonNode(stream); } // Act var mediaType = OpenApiV3Deserializer.LoadMediaType(node, new(), new ParsingContext(new())); - var serialized = await mediaType.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var serialized = await mediaType.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert Assert.Equal(expected.MakeLineBreaksEnvironmentNeutral(), serialized.MakeLineBreaksEnvironmentNeutral()); @@ -121,10 +116,10 @@ public async Task ParseMediaTypeWithXOaiItemEncodingShouldSucceed() { // Act var mediaType = await OpenApiModelFactory.LoadAsync( - Path.Combine(SampleFolderPath, "mediaTypeWithXOaiItemEncoding.yaml"), + Path.Join(SampleFolderPath, "mediaTypeWithXOaiItemEncoding.yaml"), OpenApiSpecVersion.OpenApi3_0, new(), - SettingsFixture.ReaderSettings); + SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.NotNull(mediaType); @@ -140,10 +135,10 @@ public async Task ParseMediaTypeWithXOaiPrefixEncodingShouldSucceed() { // Act var mediaType = await OpenApiModelFactory.LoadAsync( - Path.Combine(SampleFolderPath, "mediaTypeWithXOaiPrefixEncoding.yaml"), + Path.Join(SampleFolderPath, "mediaTypeWithXOaiPrefixEncoding.yaml"), OpenApiSpecVersion.OpenApi3_0, new(), - SettingsFixture.ReaderSettings); + SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.NotNull(mediaType); diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiOperationTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiOperationTests.cs index 7b0844a9b..de462ebb2 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiOperationTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiOperationTests.cs @@ -7,7 +7,7 @@ using System.Net.Http; using System.Text.Json.Nodes; using System.Threading.Tasks; -using FluentAssertions; +using Microsoft.OpenApi.Tests; using Microsoft.OpenApi.Reader; using Xunit; @@ -20,7 +20,7 @@ public class OpenApiOperationTests [Fact] public async Task OperationWithSecurityRequirementShouldReferenceSecurityScheme() { - var result = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "securedOperation.yaml"), SettingsFixture.ReaderSettings); + var result = await OpenApiDocument.LoadAsync(Path.Join(SampleFolderPath, "securedOperation.yaml"), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var securityScheme = result.Document.Paths["/"].Operations[HttpMethod.Get].Security[0].Keys.First(); Assert.Equivalent(result.Document.Components.SecuritySchemes.First().Value, securityScheme); @@ -34,7 +34,7 @@ public async Task ParseOperationWithParameterWithNoLocationShouldSucceed() Tags = new HashSet { new() { Name = "user" } } }; // Act - var operation = await OpenApiModelFactory.LoadAsync(Path.Combine(SampleFolderPath, "operationWithParameterWithNoLocation.json"), OpenApiSpecVersion.OpenApi3_0, openApiDocument, SettingsFixture.ReaderSettings); + var operation = await OpenApiModelFactory.LoadAsync(Path.Join(SampleFolderPath, "operationWithParameterWithNoLocation.json"), OpenApiSpecVersion.OpenApi3_0, openApiDocument, SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var expectedOp = new OpenApiOperation { Tags = new HashSet() @@ -71,9 +71,7 @@ public async Task ParseOperationWithParameterWithNoLocationShouldSucceed() }; // Assert - expectedOp.Should().BeEquivalentTo(operation, - options => - options.Excluding(x => x.Tags)); + OpenApiTestAssert.Equivalent(expectedOp, operation, nameof(OpenApiOperation.Tags)); } [Fact] public void DeduplicatesTagReferences() diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiParameterTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiParameterTests.cs index b3a58e6d0..764ebac7a 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiParameterTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiParameterTests.cs @@ -3,10 +3,11 @@ using System.Collections.Generic; using System.IO; -using FluentAssertions; +using System.Text.Json.Nodes; using Microsoft.OpenApi.Reader; using Xunit; using Microsoft.OpenApi.Reader.V3; +using Microsoft.OpenApi.Tests; using System.Threading.Tasks; using System.Net.Http; @@ -21,10 +22,10 @@ public class OpenApiParameterTests public async Task ParsePathParameterShouldSucceed() { // Arrange - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "pathParameter.yaml")); + using var stream = Resources.GetStream(Path.Join(SampleFolderPath, "pathParameter.yaml")); // Act - var parameter = await OpenApiModelFactory.LoadAsync(stream, OpenApiSpecVersion.OpenApi3_0, new(), settings: SettingsFixture.ReaderSettings); + var parameter = await OpenApiModelFactory.LoadAsync(stream, OpenApiSpecVersion.OpenApi3_0, new(), settings: SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.Equivalent( @@ -45,7 +46,7 @@ public async Task ParsePathParameterShouldSucceed() public async Task ParseQueryParameterShouldSucceed() { // Act - var parameter = await OpenApiModelFactory.LoadAsync(Path.Combine(SampleFolderPath, "queryParameter.yaml"), OpenApiSpecVersion.OpenApi3_0, new(), settings: SettingsFixture.ReaderSettings); + var parameter = await OpenApiModelFactory.LoadAsync(Path.Join(SampleFolderPath, "queryParameter.yaml"), OpenApiSpecVersion.OpenApi3_0, new(), settings: SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.Equivalent( @@ -72,7 +73,7 @@ public async Task ParseQueryParameterShouldSucceed() public async Task ParseQueryParameterWithObjectTypeShouldSucceed() { // Act - var parameter = await OpenApiModelFactory.LoadAsync(Path.Combine(SampleFolderPath, "queryParameterWithObjectType.yaml"), OpenApiSpecVersion.OpenApi3_0, new(), settings: SettingsFixture.ReaderSettings); + var parameter = await OpenApiModelFactory.LoadAsync(Path.Join(SampleFolderPath, "queryParameterWithObjectType.yaml"), OpenApiSpecVersion.OpenApi3_0, new(), settings: SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.Equivalent( @@ -96,10 +97,10 @@ public async Task ParseQueryParameterWithObjectTypeShouldSucceed() public async Task ParseQueryParameterWithObjectTypeAndContentShouldSucceed() { // Arrange - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "queryParameterWithObjectTypeAndContent.yaml")); + using var stream = Resources.GetStream(Path.Join(SampleFolderPath, "queryParameterWithObjectTypeAndContent.yaml")); // Act - var parameter = await OpenApiModelFactory.LoadAsync(stream, OpenApiSpecVersion.OpenApi3_0, new(), settings: SettingsFixture.ReaderSettings); + var parameter = await OpenApiModelFactory.LoadAsync(stream, OpenApiSpecVersion.OpenApi3_0, new(), settings: SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.Equivalent( @@ -140,7 +141,7 @@ public async Task ParseQueryParameterWithObjectTypeAndContentShouldSucceed() public async Task ParseHeaderParameterShouldSucceed() { // Act - var parameter = await OpenApiModelFactory.LoadAsync(Path.Combine(SampleFolderPath, "headerParameter.yaml"), OpenApiSpecVersion.OpenApi3_0, new(), settings: SettingsFixture.ReaderSettings); + var parameter = await OpenApiModelFactory.LoadAsync(Path.Join(SampleFolderPath, "headerParameter.yaml"), OpenApiSpecVersion.OpenApi3_0, new(), settings: SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.Equivalent( @@ -168,7 +169,7 @@ public async Task ParseHeaderParameterShouldSucceed() public async Task ParseParameterWithNullLocationShouldSucceed() { // Act - var parameter = await OpenApiModelFactory.LoadAsync(Path.Combine(SampleFolderPath, "parameterWithNullLocation.yaml"), OpenApiSpecVersion.OpenApi3_0, new(), settings: SettingsFixture.ReaderSettings); + var parameter = await OpenApiModelFactory.LoadAsync(Path.Join(SampleFolderPath, "parameterWithNullLocation.yaml"), OpenApiSpecVersion.OpenApi3_0, new(), settings: SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.Equivalent( @@ -189,10 +190,10 @@ public async Task ParseParameterWithNullLocationShouldSucceed() public async Task ParseParameterWithNoLocationShouldSucceed() { // Arrange - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "parameterWithNoLocation.yaml")); + using var stream = Resources.GetStream(Path.Join(SampleFolderPath, "parameterWithNoLocation.yaml")); // Act - var parameter = await OpenApiModelFactory.LoadAsync(stream, OpenApiSpecVersion.OpenApi3_0, new(), settings: SettingsFixture.ReaderSettings); + var parameter = await OpenApiModelFactory.LoadAsync(stream, OpenApiSpecVersion.OpenApi3_0, new(), settings: SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.Equivalent( @@ -213,10 +214,10 @@ public async Task ParseParameterWithNoLocationShouldSucceed() public async Task ParseParameterWithUnknownLocationShouldSucceed() { // Arrange - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "parameterWithUnknownLocation.yaml")); + using var stream = Resources.GetStream(Path.Join(SampleFolderPath, "parameterWithUnknownLocation.yaml")); // Act - var parameter = await OpenApiModelFactory.LoadAsync(stream, OpenApiSpecVersion.OpenApi3_0, new(), settings: SettingsFixture.ReaderSettings); + var parameter = await OpenApiModelFactory.LoadAsync(stream, OpenApiSpecVersion.OpenApi3_0, new(), settings: SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.Equivalent( @@ -237,10 +238,10 @@ public async Task ParseParameterWithUnknownLocationShouldSucceed() public async Task ParseParameterWithExampleShouldSucceed() { // Act - var parameter = await OpenApiModelFactory.LoadAsync(Path.Combine(SampleFolderPath, "parameterWithExample.yaml"), OpenApiSpecVersion.OpenApi3_0, new(), settings: SettingsFixture.ReaderSettings); + var parameter = await OpenApiModelFactory.LoadAsync(Path.Join(SampleFolderPath, "parameterWithExample.yaml"), OpenApiSpecVersion.OpenApi3_0, new(), settings: SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert - parameter.Should().BeEquivalentTo( + OpenApiTestAssert.Equivalent( new OpenApiParameter { In = null, @@ -253,17 +254,17 @@ public async Task ParseParameterWithExampleShouldSucceed() Type = JsonSchemaType.Number, Format = "float" } - }, options => options.IgnoringCyclicReferences().Excluding(p => p.Example.Parent)); + }, parameter, nameof(JsonNode.Parent)); } [Fact] public async Task ParseParameterWithExamplesShouldSucceed() { // Act - var parameter = await OpenApiModelFactory.LoadAsync(Path.Combine(SampleFolderPath, "parameterWithExamples.yaml"), OpenApiSpecVersion.OpenApi3_0, new(), settings: SettingsFixture.ReaderSettings); + var parameter = await OpenApiModelFactory.LoadAsync(Path.Join(SampleFolderPath, "parameterWithExamples.yaml"), OpenApiSpecVersion.OpenApi3_0, new(), settings: SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert - parameter.Should().BeEquivalentTo( + OpenApiTestAssert.Equivalent( new OpenApiParameter { In = null, @@ -286,9 +287,7 @@ public async Task ParseParameterWithExamplesShouldSucceed() Type = JsonSchemaType.Number, Format = "float" } - }, options => options.IgnoringCyclicReferences() - .Excluding(p => p.Examples["example1"].Value.Parent) - .Excluding(p => p.Examples["example2"].Value.Parent)); + }, parameter, nameof(JsonNode.Parent)); } [Fact] @@ -353,7 +352,7 @@ public void ParseParameterWithReferenceWorks() document.Workspace.RegisterComponents(document); - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "parameterWithRef.yaml")); + using var stream = Resources.GetStream(Path.Join(SampleFolderPath, "parameterWithRef.yaml")); var node = TestHelper.CreateYamlJsonNode(stream); var expected = document.Components.Parameters["tagsParameter"]; diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiPathItemDeserializerTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiPathItemDeserializerTests.cs index c91f73df5..a47bcdb6d 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiPathItemDeserializerTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiPathItemDeserializerTests.cs @@ -17,7 +17,7 @@ public class OpenApiPathItemDeserializerTests public async Task ExtraneousOperationsAreParsedAsExtensionsIn30() { // Arrange & Act - var result = await OpenApiDocument.LoadAsync($"{SampleFolderPath}pathItemWithQueryAndAdditionalOperations.yaml", SettingsFixture.ReaderSettings); + var result = await OpenApiDocument.LoadAsync($"{SampleFolderPath}pathItemWithQueryAndAdditionalOperations.yaml", SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var pathItem = result.Document.Paths["/pets"]; // Assert diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiResponseTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiResponseTests.cs index 47d017b4b..e29f7740e 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiResponseTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiResponseTests.cs @@ -16,7 +16,7 @@ public class OpenApiResponseTests [Fact] public async Task ResponseWithReferencedHeaderShouldReferenceComponent() { - var result = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "responseWithHeaderReference.yaml"), SettingsFixture.ReaderSettings); + var result = await OpenApiDocument.LoadAsync(Path.Join(SampleFolderPath, "responseWithHeaderReference.yaml"), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var response = result.Document.Components.Responses["Test"]; var expected = response.Headers.First().Value; @@ -28,7 +28,7 @@ public async Task ResponseWithReferencedHeaderShouldReferenceComponent() [Fact] public async Task ResponseWithSummaryV32ShouldDeserializeCorrectly() { - var result = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "responseWithSummary.yaml"), SettingsFixture.ReaderSettings); + var result = await OpenApiDocument.LoadAsync(Path.Join(SampleFolderPath, "responseWithSummary.yaml"), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var response = result.Document.Components.Responses["SuccessResponse"] as OpenApiResponse; @@ -40,7 +40,7 @@ public async Task ResponseWithSummaryV32ShouldDeserializeCorrectly() [Fact] public async Task ResponseWithSummaryExtensionV31ShouldDeserializeCorrectly() { - var result = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "responseWithSummaryExtension.yaml"), SettingsFixture.ReaderSettings); + var result = await OpenApiDocument.LoadAsync(Path.Join(SampleFolderPath, "responseWithSummaryExtension.yaml"), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var response = result.Document.Components.Responses["SuccessResponse"] as OpenApiResponse; diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs index fab39f674..f574245e6 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSchemaTests.cs @@ -6,8 +6,6 @@ using System.Net.Http; using System.Text.Json.Nodes; using System.Threading.Tasks; -using FluentAssertions; -using FluentAssertions.Equivalency; using Microsoft.OpenApi.Reader; using Microsoft.OpenApi.Reader.V3; using Microsoft.OpenApi.Tests; @@ -27,7 +25,7 @@ public class OpenApiSchemaTests [Fact] public void ParsePrimitiveSchemaShouldSucceed() { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "primitiveSchema.yaml")); + using var stream = Resources.GetStream(Path.Join(SampleFolderPath, "primitiveSchema.yaml")); var yamlStream = new YamlStream(); yamlStream.Load(new StreamReader(stream)); var yamlNode = yamlStream.Documents[0].RootNode; @@ -67,12 +65,12 @@ public void ParseExampleStringFragmentShouldSucceed() // Assert Assert.Equivalent(new OpenApiDiagnostic(), diagnostic); - jsonNodeExtension.Should().BeEquivalentTo(new JsonNodeExtension( + OpenApiTestAssert.Equivalent(new JsonNodeExtension( new JsonObject { ["foo"] = "bar", ["baz"] = new JsonArray() { 1, 2 } - }), options => options.IgnoringCyclicReferences()); + }), jsonNodeExtension); } [Fact] @@ -90,12 +88,12 @@ public void ParseEnumFragmentShouldSucceed() // Assert Assert.Equivalent(new OpenApiDiagnostic(), diagnostic); - jsonNodeExtension.Should().BeEquivalentTo(new JsonNodeExtension( + OpenApiTestAssert.Equivalent(new JsonNodeExtension( new JsonArray { "foo", "baz" - }), options => options.IgnoringCyclicReferences()); + }), jsonNodeExtension); } [Fact] @@ -150,7 +148,7 @@ public void ParseSchemaWithNullableBeforeOrAfterTypePreservesNullFlag(string sch [Fact] public void ParseDictionarySchemaShouldSucceed() { - using (var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "dictionarySchema.yaml"))) + using (var stream = Resources.GetStream(Path.Join(SampleFolderPath, "dictionarySchema.yaml"))) { var yamlStream = new YamlStream(); yamlStream.Load(new StreamReader(stream)); @@ -237,7 +235,7 @@ public void ParseSchemaWithOaiCompatibilityKeywordsShouldSucceed() [Fact] public void ParseBasicSchemaWithExampleShouldSucceed() { - using var stream = Resources.GetStream(Path.Combine(SampleFolderPath, "basicSchemaWithExample.yaml")); + using var stream = Resources.GetStream(Path.Join(SampleFolderPath, "basicSchemaWithExample.yaml")); var yamlStream = new YamlStream(); yamlStream.Load(new StreamReader(stream)); var yamlNode = yamlStream.Documents[0].RootNode; @@ -254,7 +252,7 @@ public void ParseBasicSchemaWithExampleShouldSucceed() // Assert Assert.Equivalent(new OpenApiDiagnostic(), diagnostic); - schema.Should().BeEquivalentTo( + OpenApiTestAssert.Equivalent( new OpenApiSchema { Type = JsonSchemaType.Object, @@ -279,19 +277,14 @@ public void ParseBasicSchemaWithExampleShouldSucceed() ["name"] = new JsonNodeExtension("Puma").Node, ["id"] = new JsonNodeExtension(1).Node } - }, options => options - .IgnoringCyclicReferences() - .Excluding((IMemberInfo memberInfo) => - memberInfo.Path.EndsWith("Parent")) - .Excluding((IMemberInfo memberInfo) => - memberInfo.Path.EndsWith("Root"))); + }, schema, nameof(JsonNode.Parent), nameof(JsonNode.Root)); } [Fact] public async Task ParseBasicSchemaWithReferenceShouldSucceed() { // Act - var result = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "basicSchemaWithReference.yaml"), SettingsFixture.ReaderSettings); + var result = await OpenApiDocument.LoadAsync(Path.Join(SampleFolderPath, "basicSchemaWithReference.yaml"), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert var components = result.Document.Components; @@ -358,7 +351,7 @@ public async Task ParseBasicSchemaWithReferenceShouldSucceed() public async Task ParseAdvancedSchemaWithReferenceShouldSucceed() { // Act - var result = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "advancedSchemaWithReference.yaml"), SettingsFixture.ReaderSettings); + var result = await OpenApiDocument.LoadAsync(Path.Join(SampleFolderPath, "advancedSchemaWithReference.yaml"), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var expectedComponents = new OpenApiComponents { @@ -444,8 +437,8 @@ public async Task ParseAdvancedSchemaWithReferenceShouldSucceed() }; // We serialize so that we can get rid of the schema BaseUri properties which show up as diffs - var actual = await result.Document.Components.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0); - var expected = await expectedComponents.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await result.Document.Components.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); + var expected = await expectedComponents.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert Assert.Equal(expected, actual); @@ -455,7 +448,7 @@ public async Task ParseAdvancedSchemaWithReferenceShouldSucceed() public async Task ParseExternalReferenceSchemaShouldSucceed() { // Act - var result = await OpenApiDocument.LoadAsync(Path.Combine(SampleFolderPath, "externalReferencesSchema.yaml"), SettingsFixture.ReaderSettings); + var result = await OpenApiDocument.LoadAsync(Path.Join(SampleFolderPath, "externalReferencesSchema.yaml"), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert var components = result.Document.Components; @@ -519,10 +512,10 @@ public async Task SerializeSchemaWithNullableShouldSucceed() var expected = @"type: string nullable: true"; - var path = Path.Combine(SampleFolderPath, "schemaWithNullable.yaml"); + var path = Path.Join(SampleFolderPath, "schemaWithNullable.yaml"); // Act - var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi3_0, new(), SettingsFixture.ReaderSettings); + var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi3_0, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var writer = new StringWriter(); schema.SerializeAsV3(new OpenApiYamlWriter(writer)); @@ -538,10 +531,10 @@ public async Task SerializeSchemaWithOnlyNullableShouldSucceed() // Arrange var expected = @"{ }"; - var path = Path.Combine(SampleFolderPath, "schemaWithOnlyNullable.yaml"); + var path = Path.Join(SampleFolderPath, "schemaWithOnlyNullable.yaml"); // Act - var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi3_0, new(), SettingsFixture.ReaderSettings); + var schema = await OpenApiModelFactory.LoadAsync(path, OpenApiSpecVersion.OpenApi3_0, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); var writer = new StringWriter(); schema.SerializeAsV3(new OpenApiYamlWriter(writer)); diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSecuritySchemeTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSecuritySchemeTests.cs index 734b80a31..92aac44ae 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSecuritySchemeTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiSecuritySchemeTests.cs @@ -19,7 +19,7 @@ public class OpenApiSecuritySchemeTests public async Task ParseHttpSecuritySchemeShouldSucceed() { // Act - var securityScheme = await OpenApiModelFactory.LoadAsync(Path.Combine(SampleFolderPath, "httpSecurityScheme.yaml"), OpenApiSpecVersion.OpenApi3_0, new(), SettingsFixture.ReaderSettings); + var securityScheme = await OpenApiModelFactory.LoadAsync(Path.Join(SampleFolderPath, "httpSecurityScheme.yaml"), OpenApiSpecVersion.OpenApi3_0, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.Equivalent( @@ -34,7 +34,7 @@ public async Task ParseHttpSecuritySchemeShouldSucceed() public async Task ParseApiKeySecuritySchemeShouldSucceed() { // Act - var securityScheme = await OpenApiModelFactory.LoadAsync(Path.Combine(SampleFolderPath, "apiKeySecurityScheme.yaml"), OpenApiSpecVersion.OpenApi3_0, new(), SettingsFixture.ReaderSettings); + var securityScheme = await OpenApiModelFactory.LoadAsync(Path.Join(SampleFolderPath, "apiKeySecurityScheme.yaml"), OpenApiSpecVersion.OpenApi3_0, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.Equivalent( @@ -50,7 +50,7 @@ public async Task ParseApiKeySecuritySchemeShouldSucceed() public async Task ParseBearerSecuritySchemeShouldSucceed() { // Act - var securityScheme = await OpenApiModelFactory.LoadAsync(Path.Combine(SampleFolderPath, "bearerSecurityScheme.yaml"), OpenApiSpecVersion.OpenApi3_0, new(), SettingsFixture.ReaderSettings); + var securityScheme = await OpenApiModelFactory.LoadAsync(Path.Join(SampleFolderPath, "bearerSecurityScheme.yaml"), OpenApiSpecVersion.OpenApi3_0, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.Equivalent( @@ -66,7 +66,7 @@ public async Task ParseBearerSecuritySchemeShouldSucceed() public async Task ParseOAuth2SecuritySchemeShouldSucceed() { // Act - var securityScheme = await OpenApiModelFactory.LoadAsync(Path.Combine(SampleFolderPath, "oauth2SecurityScheme.yaml"), OpenApiSpecVersion.OpenApi3_0, new(), SettingsFixture.ReaderSettings); + var securityScheme = await OpenApiModelFactory.LoadAsync(Path.Join(SampleFolderPath, "oauth2SecurityScheme.yaml"), OpenApiSpecVersion.OpenApi3_0, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.Equivalent( @@ -92,7 +92,7 @@ public async Task ParseOAuth2SecuritySchemeShouldSucceed() public async Task ParseOpenIdConnectSecuritySchemeShouldSucceed() { // Act - var securityScheme = await OpenApiModelFactory.LoadAsync(Path.Combine(SampleFolderPath, "openIdConnectSecurityScheme.yaml"), OpenApiSpecVersion.OpenApi3_0, new(), SettingsFixture.ReaderSettings); + var securityScheme = await OpenApiModelFactory.LoadAsync(Path.Join(SampleFolderPath, "openIdConnectSecurityScheme.yaml"), OpenApiSpecVersion.OpenApi3_0, new(), SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.Equivalent( @@ -109,10 +109,10 @@ public async Task ParseOAuth2SecuritySchemeWithDeviceAuthorizationUrlShouldSucce { // Act var securityScheme = await OpenApiModelFactory.LoadAsync( - Path.Combine(SampleFolderPath, "oauth2SecuritySchemeWithDeviceUrl.yaml"), + Path.Join(SampleFolderPath, "oauth2SecuritySchemeWithDeviceUrl.yaml"), OpenApiSpecVersion.OpenApi3_0, new(), - SettingsFixture.ReaderSettings); + SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert Assert.NotNull(securityScheme); diff --git a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiXmlTests.cs b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiXmlTests.cs index 0c9c78e6b..61f916cf4 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiXmlTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/V3Tests/OpenApiXmlTests.cs @@ -18,7 +18,7 @@ public class OpenApiXmlTests public async Task ParseBasicXmlShouldSucceed() { // Act - var xml = await OpenApiModelFactory.LoadAsync(Resources.GetStream(Path.Combine(SampleFolderPath, "basicXml.yaml")), OpenApiSpecVersion.OpenApi3_0, new(), settings: SettingsFixture.ReaderSettings); + var xml = await OpenApiModelFactory.LoadAsync(Resources.GetStream(Path.Join(SampleFolderPath, "basicXml.yaml")), OpenApiSpecVersion.OpenApi3_0, new(), settings: SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken); // Assert #pragma warning disable CS0618 // Type or member is obsolete diff --git a/test/Microsoft.OpenApi.Readers.Tests/YamlConverterGlobalSettingsCollection.cs b/test/Microsoft.OpenApi.Readers.Tests/YamlConverterGlobalSettingsCollection.cs new file mode 100644 index 000000000..8c1c979f5 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/YamlConverterGlobalSettingsCollection.cs @@ -0,0 +1,9 @@ +using Xunit; + +namespace Microsoft.OpenApi.Readers.Tests; + +[CollectionDefinition(Name, DisableParallelization = true)] +public sealed class YamlConverterGlobalSettingsCollection +{ + public const string Name = "YamlConverterGlobalSettings"; +} diff --git a/test/Microsoft.OpenApi.Readers.Tests/YamlConverterGlobalSettingsTests.cs b/test/Microsoft.OpenApi.Readers.Tests/YamlConverterGlobalSettingsTests.cs new file mode 100644 index 000000000..6e423cd85 --- /dev/null +++ b/test/Microsoft.OpenApi.Readers.Tests/YamlConverterGlobalSettingsTests.cs @@ -0,0 +1,81 @@ +using System; +using System.Text.Json.Nodes; +using Microsoft.OpenApi.YamlReader; +using SharpYaml.Serialization; +using Xunit; + +namespace Microsoft.OpenApi.Readers.Tests; + +[Collection(YamlConverterGlobalSettingsCollection.Name)] +public class YamlConverterGlobalSettingsTests +{ + [Fact] + public void ConversionLimitsDefaultToDocumentedValues() + { + Assert.Equal(64u, YamlConverter.DefaultMaxDepth); + Assert.Equal(5_000_000u, YamlConverter.DefaultMaxNodeCount); + Assert.Equal(5_000u, YamlConverter.DefaultMaxAliasExpansionNodeCount); + Assert.Equal(YamlConverter.DefaultMaxDepth, YamlConverter.MaxDepth); + Assert.Equal(YamlConverter.DefaultMaxNodeCount, YamlConverter.MaxNodeCount); + Assert.Equal(YamlConverter.DefaultMaxAliasExpansionNodeCount, YamlConverter.MaxAliasExpansionNodeCount); + } + + [Fact] + public void SettingMaxDepthToZeroThrows() + { + Assert.Throws(() => YamlConverter.MaxDepth = 0); + Assert.Equal(YamlConverter.DefaultMaxDepth, YamlConverter.MaxDepth); + } + + [Fact] + public void SettingMaxNodeCountToZeroThrows() + { + Assert.Throws(() => YamlConverter.MaxNodeCount = 0); + Assert.Equal(YamlConverter.DefaultMaxNodeCount, YamlConverter.MaxNodeCount); + } + + [Fact] + public void SettingMaxAliasExpansionNodeCountToZeroThrows() + { + Assert.Throws(() => YamlConverter.MaxAliasExpansionNodeCount = 0); + Assert.Equal(YamlConverter.DefaultMaxAliasExpansionNodeCount, YamlConverter.MaxAliasExpansionNodeCount); + } + + [Fact] + public void SettingMaxDepthAboveSafeCeilingThrows() + { + Assert.Throws(() => YamlConverter.MaxDepth = YamlConverter.MaximumAllowedDepth + 1); + Assert.Equal(YamlConverter.DefaultMaxDepth, YamlConverter.MaxDepth); + } + + [Fact] + public void SettingMaxNodeCountAboveSafeCeilingThrows() + { + Assert.Throws(() => YamlConverter.MaxNodeCount = YamlConverter.MaximumAllowedNodeCount + 1); + Assert.Equal(YamlConverter.DefaultMaxNodeCount, YamlConverter.MaxNodeCount); + } + + [Fact] + public void RaisingMaxDepthAllowsDocumentsDeeperThanTheDefault() + { + const int depth = 70; + YamlNode deeplyNested = new YamlScalarNode("value"); + for (var index = 0; index < depth; index++) + { + deeplyNested = new YamlSequenceNode(deeplyNested); + } + + try + { + YamlConverter.MaxDepth = depth + 10; + + var jsonNode = deeplyNested.ToJsonNode(); + + Assert.IsType(jsonNode); + } + finally + { + YamlConverter.MaxDepth = YamlConverter.DefaultMaxDepth; + } + } +} diff --git a/test/Microsoft.OpenApi.Readers.Tests/YamlConverterTests.cs b/test/Microsoft.OpenApi.Readers.Tests/YamlConverterTests.cs index 7bc094d48..ce8393696 100644 --- a/test/Microsoft.OpenApi.Readers.Tests/YamlConverterTests.cs +++ b/test/Microsoft.OpenApi.Readers.Tests/YamlConverterTests.cs @@ -363,7 +363,7 @@ public void ExcessiveNestingDepthIsRejected() const int depth = 70; var deeplyNested = new string('[', depth) + new string(']', depth); - Assert.Throws(() => ConvertYamlStringToJsonNode(deeplyNested)); + Assert.Throws(() => ConvertYamlStringToJsonNode(deeplyNested)); } [Fact] @@ -382,48 +382,50 @@ public void LegitimateAliasesStillConvert() } [Fact] - public void ConversionLimitsDefaultToDocumentedValues() + public void CyclicYamlNodeGraphIsRejected() { - Assert.Equal(64u, YamlConverter.DefaultMaxDepth); - Assert.Equal(5_000_000u, YamlConverter.DefaultMaxNodeCount); - Assert.Equal(YamlConverter.DefaultMaxDepth, YamlConverter.MaxDepth); - Assert.Equal(YamlConverter.DefaultMaxNodeCount, YamlConverter.MaxNodeCount); - } + var sequence = new YamlSequenceNode(); + sequence.Add(sequence); - [Fact] - public void SettingMaxDepthToZeroThrows() - { - Assert.Throws(() => YamlConverter.MaxDepth = 0); - // The invalid assignment must not have changed the effective limit. - Assert.Equal(YamlConverter.DefaultMaxDepth, YamlConverter.MaxDepth); + Assert.Throws(() => sequence.ToJsonNode()); } [Fact] - public void SettingMaxNodeCountToZeroThrows() + public void ComplexMappingKeyIsRejectedAsReaderException() { - Assert.Throws(() => YamlConverter.MaxNodeCount = 0); - // The invalid assignment must not have changed the effective limit. - Assert.Equal(YamlConverter.DefaultMaxNodeCount, YamlConverter.MaxNodeCount); + var mapping = new YamlMappingNode + { + { new YamlSequenceNode(new YamlScalarNode("key")), new YamlScalarNode("value") } + }; + + Assert.Throws(() => mapping.ToJsonNode()); } [Fact] - public void RaisingMaxDepthAllowsDocumentsDeeperThanTheDefault() + public void SharedSubtreeGraftedTooDeepIsRejected() { - // A document nested deeper than the default depth limit (64) is rejected by default - // but can be permitted by a consumer that opts into a higher limit. - const int depth = 70; - var deeplyNested = new string('[', depth) + new string(']', depth); - - try + // The converter memoizes each YamlNode it has already materialized, then deep-clones the + // result when the same instance reappears. The shared subtree clears the depth check where + // it is first seen, so without charging its height at the reuse site it can be replayed + // from a deeper position to build a tree past the limit. + YamlNode shared = new YamlScalarNode("value"); + for (var index = 0; index < 40; index++) { - YamlConverter.MaxDepth = depth + 10; - var jsonNode = ConvertYamlStringToJsonNode(deeplyNested); - Assert.IsType(jsonNode); + shared = new YamlSequenceNode(shared); } - finally + + var grafted = shared; + for (var index = 0; index < 30; index++) { - YamlConverter.MaxDepth = YamlConverter.DefaultMaxDepth; + grafted = new YamlSequenceNode(grafted); } + + // The shallow element is converted first and memoizes the shared subtree; the deep element + // then reuses it 31 levels down, which would materialize 72 levels against a limit of 64. + var root = new YamlSequenceNode(shared, grafted); + + var exception = Assert.Throws(() => root.ToJsonNode()); + Assert.Contains("expands an alias", exception.Message, StringComparison.Ordinal); } private static JsonNode ConvertYamlStringToJsonNode(string yamlInput) diff --git a/test/Microsoft.OpenApi.Tests/DefaultSettingsFixture.cs b/test/Microsoft.OpenApi.Tests/DefaultSettingsFixture.cs deleted file mode 100644 index d2afed0c6..000000000 --- a/test/Microsoft.OpenApi.Tests/DefaultSettingsFixture.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using FluentAssertions; - -namespace Microsoft.OpenApi.Tests -{ - /// - /// Fixture containing default settings for external libraries. - /// - public class DefaultSettingsFixture - { - /// - /// Initializes an instance of . - /// - public DefaultSettingsFixture() - { - // We need RespectingRuntimeTypes() to ensure equivalence test works property, - // given that there are multiple types that can be used for the declared type OpenApiAny. - // Without this option, properties specific to those types would not be compared. - AssertionOptions.AssertEquivalencyUsing( - o => o - .AllowingInfiniteRecursion() - .RespectingRuntimeTypes() - .WithStrictOrdering()); - } - } -} diff --git a/test/Microsoft.OpenApi.Tests/DefaultSettingsFixtureCollection.cs b/test/Microsoft.OpenApi.Tests/DefaultSettingsFixtureCollection.cs deleted file mode 100644 index 22f562c4d..000000000 --- a/test/Microsoft.OpenApi.Tests/DefaultSettingsFixtureCollection.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT license. - -using Xunit; - -namespace Microsoft.OpenApi.Tests -{ - /// - /// Collection dummy class for . - /// - /// - /// This class is needed in xUnit framework to define collection name - /// to be used in unit test classes. - /// - [CollectionDefinition("DefaultSettings")] - public class DefaultSettingsFixtureCollection : ICollectionFixture - { - } -} diff --git a/test/Microsoft.OpenApi.Tests/Expressions/RuntimeExpressionTests.cs b/test/Microsoft.OpenApi.Tests/Expressions/RuntimeExpressionTests.cs index 08a1debc0..91509184b 100644 --- a/test/Microsoft.OpenApi.Tests/Expressions/RuntimeExpressionTests.cs +++ b/test/Microsoft.OpenApi.Tests/Expressions/RuntimeExpressionTests.cs @@ -145,6 +145,28 @@ public void BuildRuntimeExpressionTwiceCreatesNewEquivalentInstances(string expr Assert.Equal(runtimeExpression1, runtimeExpression2); } + [Fact] + public void CompositeRuntimeExpressionPreservesMultilineCaptures() + { + const string expression = "prefix {$request.header.foo\nbar} {$url} suffix"; + + var composite = Assert.IsType(RuntimeExpression.Build(expression)); + + Assert.Equal(expression, composite.Expression); + Assert.Equal(new[] { "$request.header.foo\nbar", "$url" }, + composite.ContainedExpressions.Select(static item => item.Expression)); + } + + [Fact] + public void CompositeRuntimeExpressionPreservesUnterminatedCapture() + { + const string expression = "prefix {$url"; + + var composite = Assert.IsType(RuntimeExpression.Build(expression)); + + Assert.IsType(Assert.Single(composite.ContainedExpressions)); + } + [Fact] public void CompositeRuntimeExpressionContainsExpression() { diff --git a/test/Microsoft.OpenApi.Tests/Extensions/OpenApiSerializableExtensionsTests.cs b/test/Microsoft.OpenApi.Tests/Extensions/OpenApiSerializableExtensionsTests.cs index a1e323ebf..470fb022b 100644 --- a/test/Microsoft.OpenApi.Tests/Extensions/OpenApiSerializableExtensionsTests.cs +++ b/test/Microsoft.OpenApi.Tests/Extensions/OpenApiSerializableExtensionsTests.cs @@ -27,11 +27,11 @@ public async Task UsesTheTerseOutputInformationFromSettingsTrue() }; using var stream = new MemoryStream(); - await parameter.SerializeAsync(stream, OpenApiSpecVersion.OpenApi3_1, OpenApiConstants.Json, settings); + await parameter.SerializeAsync(stream, OpenApiSpecVersion.OpenApi3_1, OpenApiConstants.Json, settings, TestContext.Current.CancellationToken); stream.Position = 0; using var reader = new StreamReader(stream); - var output = await reader.ReadToEndAsync(); + var output = await reader.ReadToEndAsync(TestContext.Current.CancellationToken); Assert.Equal("{\"name\":\"param1\",\"in\":\"query\",\"description\":\"A sample parameter\",\"schema\":{\"type\":\"string\"}}", output); } @@ -57,11 +57,11 @@ public async Task UsesTheTerseOutputInformationFromSettingsFalse() }; using var stream = new MemoryStream(); - await parameter.SerializeAsync(stream, OpenApiSpecVersion.OpenApi3_1, OpenApiConstants.Json, settings); + await parameter.SerializeAsync(stream, OpenApiSpecVersion.OpenApi3_1, OpenApiConstants.Json, settings, TestContext.Current.CancellationToken); stream.Position = 0; using var reader = new StreamReader(stream); - var output = await reader.ReadToEndAsync(); + var output = await reader.ReadToEndAsync(TestContext.Current.CancellationToken); Assert.Equal("{\n \"name\": \"param1\",\n \"in\": \"query\",\n \"description\": \"A sample parameter\",\n \"schema\": {\n \"type\": \"string\"\n }\n}", output); } @@ -82,11 +82,11 @@ public async Task UsesTheTerseOutputInformationFromSettingsNoSettings() }; using var stream = new MemoryStream(); - await parameter.SerializeAsync(stream, OpenApiSpecVersion.OpenApi3_1, OpenApiConstants.Json, null); + await parameter.SerializeAsync(stream, OpenApiSpecVersion.OpenApi3_1, OpenApiConstants.Json, null, TestContext.Current.CancellationToken); stream.Position = 0; using var reader = new StreamReader(stream); - var output = await reader.ReadToEndAsync(); + var output = await reader.ReadToEndAsync(TestContext.Current.CancellationToken); Assert.Equal("{\n \"name\": \"param1\",\n \"in\": \"query\",\n \"description\": \"A sample parameter\",\n \"schema\": {\n \"type\": \"string\"\n }\n}", output); } diff --git a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj index 58bd19afc..e79f44dba 100644 --- a/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj +++ b/test/Microsoft.OpenApi.Tests/Microsoft.OpenApi.Tests.csproj @@ -5,17 +5,17 @@ true Library ..\..\src\Microsoft.OpenApi.snk + true - - - - - - - - + + + + + + + diff --git a/test/Microsoft.OpenApi.Tests/Mocks/OpenApiResponseSerializationTests.cs b/test/Microsoft.OpenApi.Tests/Mocks/OpenApiResponseSerializationTests.cs index 27246fa7e..95a5a6980 100644 --- a/test/Microsoft.OpenApi.Tests/Mocks/OpenApiResponseSerializationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Mocks/OpenApiResponseSerializationTests.cs @@ -1,5 +1,7 @@ using System.IO; using System.Net.Http; +using System.Text.Json.Nodes; +using System.Threading.Tasks; using Moq; using Xunit; @@ -61,5 +63,25 @@ public void SerializeAsV3_DoesNotCallV31OrV2Serialization() _linkMock.Verify(l => l.SerializeAsV31(It.IsAny()), Times.Never); _linkMock.Verify(l => l.SerializeAsV2(It.IsAny()), Times.Never); } + + [Theory] + [InlineData(OpenApiSpecVersion.OpenApi2_0)] + [InlineData(OpenApiSpecVersion.OpenApi3_0)] + [InlineData(OpenApiSpecVersion.OpenApi3_1)] + [InlineData(OpenApiSpecVersion.OpenApi3_2)] + public async Task SerializeResponseWithoutDescriptionAsJsonDoesNotWriteNullDescription(OpenApiSpecVersion specVersion) + { + // Arrange + var response = new OpenApiResponse(); + + // Act + var actual = await response.SerializeAsJsonAsync(specVersion, TestContext.Current.CancellationToken); + + // Assert + var node = JsonNode.Parse(actual); + Assert.NotNull(node); + var responseObject = Assert.IsType(node); + Assert.False(responseObject.ContainsKey(OpenApiConstants.Description)); + } } } diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiCallbackTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiCallbackTests.cs index 952185498..7d4181147 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiCallbackTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiCallbackTests.cs @@ -103,7 +103,7 @@ public async Task SerializeAdvancedCallbackAsV3JsonWorksAsync(bool produceTerseO // Act AdvancedCallback.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -120,7 +120,7 @@ public async Task SerializeReferencedCallbackAsV3JsonWorksAsync(bool produceTers // Act CallbackProxy.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -137,7 +137,7 @@ public async Task SerializeReferencedCallbackAsV3JsonWithoutReferenceWorksAsync( // Act ReferencedCallback.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiComponentsMediaTypesTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiComponentsMediaTypesTests.cs index fba65ccf7..a137d86bd 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiComponentsMediaTypesTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiComponentsMediaTypesTests.cs @@ -74,7 +74,7 @@ public async Task SerializeMediaTypesAsV32JsonWorks() """; // Act - var actual = await ComponentsWithMediaTypes.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2); + var actual = await ComponentsWithMediaTypes.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2, TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); @@ -90,7 +90,7 @@ public async Task SerializeMediaTypesAsV31JsonWorks() """; // Act - var actual = await ComponentsWithMediaTypes.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1); + var actual = await ComponentsWithMediaTypes.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1, TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); @@ -106,7 +106,7 @@ public async Task SerializeMediaTypesAsV30JsonWorks() """; // Act - var actual = await ComponentsWithMediaTypes.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await ComponentsWithMediaTypes.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiComponentsTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiComponentsTests.cs index 3427605fb..4d03aa04b 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiComponentsTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiComponentsTests.cs @@ -267,7 +267,7 @@ public async Task SerializeBasicComponentsAsJsonWorks() var expected = @"{ }"; // Act - var actual = await BasicComponents.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await BasicComponents.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -282,7 +282,7 @@ public async Task SerializeBasicComponentsAsYamlWorks() var expected = @"{ }"; // Act - var actual = await BasicComponents.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await BasicComponents.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -334,7 +334,7 @@ public async Task SerializeAdvancedComponentsAsJsonV3Works() """; // Act - var actual = await AdvancedComponents.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await AdvancedComponents.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -392,7 +392,7 @@ public async Task SerializeAdvancedComponentsWithReferenceAsJsonV3Works() """; // Act - var actual = await AdvancedComponentsWithReference.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await AdvancedComponentsWithReference.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -431,7 +431,7 @@ public async Task SerializeAdvancedComponentsAsYamlV3Works() """; // Act - var actual = await AdvancedComponents.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await AdvancedComponents.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -473,7 +473,7 @@ public async Task SerializeAdvancedComponentsWithReferenceAsYamlV3Works() """; // Act - var actual = await AdvancedComponentsWithReference.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await AdvancedComponentsWithReference.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -510,7 +510,7 @@ public async Task SerializeBrokenComponentsAsJsonV3Works() """; // Act - var actual = await BrokenComponents.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await BrokenComponents.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -540,7 +540,7 @@ public async Task SerializeBrokenComponentsAsYamlV3Works() """; // Act - var actual = await BrokenComponents.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await BrokenComponents.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -566,7 +566,7 @@ public async Task SerializeTopLevelReferencingComponentsAsYamlV3Works() """; // Act - var actual = await TopLevelReferencingComponents.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await TopLevelReferencingComponents.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -591,7 +591,7 @@ public async Task SerializeTopLevelSelfReferencingWithOtherPropertiesComponentsA type: string"; // Act - var actual = await TopLevelSelfReferencingComponentsWithOtherProperties.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await TopLevelSelfReferencingComponentsWithOtherProperties.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -646,7 +646,7 @@ public async Task SerializeComponentsWithPathItemsAsJsonWorks() } }"; // Act - var actual = await ComponentsWithPathItem.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1); + var actual = await ComponentsWithPathItem.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -683,7 +683,7 @@ public async Task SerializeComponentsWithPathItemsAsYamlWorks() type: integer"; // Act - var actual = await ComponentsWithPathItem.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_1); + var actual = await ComponentsWithPathItem.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_1, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiContactTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiContactTests.cs index f699b2f40..81050fcb4 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiContactTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiContactTests.cs @@ -34,7 +34,7 @@ public async Task SerializeBasicContactWorks( string expected) { // Arrange & Act - var actual = await BasicContact.SerializeAsync(version, format); + var actual = await BasicContact.SerializeAsync(version, format, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -59,7 +59,7 @@ public async Task SerializeAdvanceContactAsJsonWorks(OpenApiSpecVersion version) """; // Act - var actual = await AdvanceContact.SerializeAsJsonAsync(version); + var actual = await AdvanceContact.SerializeAsJsonAsync(version, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -82,7 +82,7 @@ public async Task SerializeAdvanceContactAsYamlWorks(OpenApiSpecVersion version) """; // Act - var actual = await AdvanceContact.SerializeAsYamlAsync(version); + var actual = await AdvanceContact.SerializeAsYamlAsync(version, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiDiscriminatorTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiDiscriminatorTests.cs index 495a0729e..256a780c4 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiDiscriminatorTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiDiscriminatorTests.cs @@ -29,7 +29,7 @@ public async Task SerializeDefaultMappingAsReferenceString(OpenApiSpecVersion sp }; // Act - var actual = JsonNode.Parse(await discriminator.SerializeAsJsonAsync(specVersion)); + var actual = JsonNode.Parse(await discriminator.SerializeAsJsonAsync(specVersion, TestContext.Current.CancellationToken)); // Assert Assert.NotNull(actual); @@ -56,7 +56,7 @@ public async Task SerializeMappingValuesAsReferenceStrings(OpenApiSpecVersion sp }; // Act - var actual = JsonNode.Parse(await discriminator.SerializeAsJsonAsync(specVersion)); + var actual = JsonNode.Parse(await discriminator.SerializeAsJsonAsync(specVersion, TestContext.Current.CancellationToken)); // Assert Assert.NotNull(actual); diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeDuplicateExtensionsAsV3JsonWorksAsync_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeDuplicateExtensionsAsV3JsonWorksAsync_produceTerseOutput=True.verified.txt index b87f4acf5..479557a10 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeDuplicateExtensionsAsV3JsonWorksAsync_produceTerseOutput=True.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.SerializeDuplicateExtensionsAsV3JsonWorksAsync_produceTerseOutput=True.verified.txt @@ -1 +1 @@ -{"openapi":"3.0.4","info":{"title":"Swagger Petstore (Simple)","description":"A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification","version":"1.0.0"},"servers":[{"url":"http://petstore.swagger.io/api"}],"paths":{"/add/{operand1}/{operand2}":{"get":{"operationId":"addByOperand1AndByOperand2","parameters":[{"name":"operand1","in":"path","description":"The first operand","required":true,"schema":{"type":"integer","my-extension":4},"my-extension":4},{"name":"operand2","in":"path","description":"The second operand","required":true,"schema":{"type":"integer","my-extension":4},"my-extension":4}],"responses":{"200":{"description":"pet response","content":{"application/json":{"schema":{"type":"array","items":{"required":["id","name"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}}}}}}}}}},"components":{"schemas":{"pet":{"required":["id","name"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}},"newPet":{"required":["name"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}},"errorModel":{"required":["code","message"],"type":"object","properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}}} +{"openapi":"3.0.4","info":{"title":"Swagger Petstore (Simple)","description":"A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification","version":"1.0.0"},"servers":[{"url":"http://petstore.swagger.io/api"}],"paths":{"/add/{operand1}/{operand2}":{"get":{"operationId":"addByOperand1AndByOperand2","parameters":[{"name":"operand1","in":"path","description":"The first operand","required":true,"schema":{"type":"integer","my-extension":4},"my-extension":4},{"name":"operand2","in":"path","description":"The second operand","required":true,"schema":{"type":"integer","my-extension":4},"my-extension":4}],"responses":{"200":{"description":"pet response","content":{"application/json":{"schema":{"type":"array","items":{"required":["id","name"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}}}}}}}}}},"components":{"schemas":{"pet":{"required":["id","name"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}},"newPet":{"required":["name"],"type":"object","properties":{"id":{"type":"integer","format":"int64"},"name":{"type":"string"},"tag":{"type":"string"}}},"errorModel":{"required":["code","message"],"type":"object","properties":{"code":{"type":"integer","format":"int32"},"message":{"type":"string"}}}}}} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs index f7095bd31..8b4629664 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiDocumentTests.cs @@ -1334,7 +1334,7 @@ public async Task SerializeAdvancedDocumentAsV3JsonWorksAsync(bool produceTerseO // Act AdvancedDocument.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -1351,7 +1351,7 @@ public async Task SerializeAdvancedDocumentWithReferenceAsV3JsonWorksAsync(bool // Act AdvancedDocumentWithReference.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -1368,7 +1368,7 @@ public async Task SerializeAdvancedDocumentWithServerVariableAsV2JsonWorksAsync( // Act AdvancedDocumentWithServerVariable.SerializeAsV2(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -1385,7 +1385,7 @@ public async Task SerializeAdvancedDocumentAsV2JsonWorksAsync(bool produceTerseO // Act AdvancedDocument.SerializeAsV2(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -1402,7 +1402,7 @@ public async Task SerializeDuplicateExtensionsAsV3JsonWorksAsync(bool produceTer // Act DuplicateExtensions.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -1419,7 +1419,7 @@ public async Task SerializeDuplicateExtensionsAsV2JsonWorksAsync(bool produceTer // Act DuplicateExtensions.SerializeAsV2(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -1436,7 +1436,7 @@ public async Task SerializeAdvancedDocumentWithReferenceAsV2JsonWorksAsync(bool // Act AdvancedDocumentWithReference.SerializeAsV2(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -1460,7 +1460,7 @@ public async Task SerializeSimpleDocumentWithTopLevelReferencingComponentsAsYaml type: string"; // Act - var actual = await SimpleDocumentWithTopLevelReferencingComponents.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi2_0); + var actual = await SimpleDocumentWithTopLevelReferencingComponents.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi2_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -1479,7 +1479,7 @@ public async Task SerializeSimpleDocumentWithTopLevelSelfReferencingComponentsAs schema1: { }"; // Act - var actual = await SimpleDocumentWithTopLevelSelfReferencingComponents.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi2_0); + var actual = await SimpleDocumentWithTopLevelSelfReferencingComponents.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi2_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -1507,7 +1507,7 @@ public async Task SerializeSimpleDocumentWithTopLevelSelfReferencingWithOtherPro type: string"; // Act - var actual = await SimpleDocumentWithTopLevelSelfReferencingComponentsWithOtherProperties.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi2_0); + var actual = await SimpleDocumentWithTopLevelSelfReferencingComponentsWithOtherProperties.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi2_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -1553,7 +1553,7 @@ public async Task SerializeDocumentWithReferenceButNoComponents() ((OpenApiMediaType)document.Paths["/"].Operations[HttpMethod.Get].Responses["200"].Content["application/json"]).Schema = new OpenApiSchemaReference("test", document); // Act - var actual = await document.SerializeAsync(OpenApiSpecVersion.OpenApi2_0, OpenApiConstants.Json); + var actual = await document.SerializeAsync(OpenApiSpecVersion.OpenApi2_0, OpenApiConstants.Json, TestContext.Current.CancellationToken); // Assert Assert.NotEmpty(actual); @@ -1581,7 +1581,7 @@ public async Task SerializeRelativePathAsV2JsonWorks() }; // Act - var actual = await doc.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi2_0); + var actual = await doc.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi2_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -1611,7 +1611,7 @@ public async Task SerializeRelativePathWithHostAsV2JsonWorks() }; // Act - var actual = await doc.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi2_0); + var actual = await doc.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi2_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -1640,7 +1640,7 @@ public async Task SerializeRelativeRootPathWithHostAsV2JsonWorks() }; // Act - var actual = await doc.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi2_0); + var actual = await doc.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi2_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -1653,18 +1653,18 @@ public async Task TestHashCodesForSimilarOpenApiDocuments() // Arrange var sampleFolderPath = "Models/Samples/"; - var doc1 = await ParseInputFileAsync(Path.Combine(sampleFolderPath, "sampleDocument.yaml")); - var doc2 = await ParseInputFileAsync(Path.Combine(sampleFolderPath, "sampleDocument.yaml")); - var doc3 = await ParseInputFileAsync(Path.Combine(sampleFolderPath, "sampleDocumentWithWhiteSpaces.yaml")); + var doc1 = await ParseInputFileAsync(Path.Join(sampleFolderPath, "sampleDocument.yaml")); + var doc2 = await ParseInputFileAsync(Path.Join(sampleFolderPath, "sampleDocument.yaml")); + var doc3 = await ParseInputFileAsync(Path.Join(sampleFolderPath, "sampleDocumentWithWhiteSpaces.yaml")); // Act && Assert /* Test whether reading in two similar documents yield the same hash code, And reading in similar documents(one has a whitespace) yields the same hash code as the result is terse */ - var doc1HashCode = await doc1.GetHashCodeAsync(); - var doc2HashCode = await doc2.GetHashCodeAsync(); - var doc3HashCode = await doc3.GetHashCodeAsync(); + var doc1HashCode = await doc1.GetHashCodeAsync(TestContext.Current.CancellationToken); + var doc2HashCode = await doc2.GetHashCodeAsync(TestContext.Current.CancellationToken); + var doc3HashCode = await doc3.GetHashCodeAsync(TestContext.Current.CancellationToken); Assert.NotNull(doc1HashCode); Assert.NotNull(doc2HashCode); Assert.NotNull(doc3HashCode); @@ -1725,7 +1725,7 @@ public async Task SerializeV2DocumentWithNonArraySchemaTypeDoesNotWriteOutCollec }; // Act - var actual = await doc.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi2_0); + var actual = await doc.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi2_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -1813,7 +1813,7 @@ public async Task SerializeV2DocumentWithStyleAsNullDoesNotWriteOutStyleValue() }; // Act - var actual = await doc.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await doc.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -1885,7 +1885,7 @@ public async Task SerializeDocumentWithWebhooksAsV3JsonWorks(bool produceTerseOu // Act DocumentWithWebhooks.SerializeAsV31(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); var actual = outputStringWriter.GetStringBuilder().ToString(); // Assert @@ -1929,7 +1929,7 @@ public async Task SerializeDocumentWithWebhooksAsV3YamlWorks() description: Return a 200 status to indicate that the data was received successfully"; // Act - var actual = await DocumentWithWebhooks.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_1); + var actual = await DocumentWithWebhooks.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_1, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -1959,7 +1959,7 @@ public async Task SerializeDocumentWithRootJsonSchemaDialectPropertyWorks() paths: { }"; // Act - var actual = await doc.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_1); + var actual = await doc.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_1, TestContext.Current.CancellationToken); // Assert Assert.Equal(expected.MakeLineBreaksEnvironmentNeutral(), actual.MakeLineBreaksEnvironmentNeutral()); @@ -1980,7 +1980,7 @@ public async Task SerializeV31DocumentWithRefsInWebhooksWorks() items: type: object"; - var doc = (await OpenApiDocument.LoadAsync("Models/Samples/docWithReusableWebhooks.yaml", SettingsFixture.ReaderSettings)).Document; + var doc = (await OpenApiDocument.LoadAsync("Models/Samples/docWithReusableWebhooks.yaml", SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken)).Document; var stringWriter = new StringWriter(); var writer = new OpenApiYamlWriter(stringWriter, new OpenApiWriterSettings { InlineLocalReferences = true }); @@ -2034,8 +2034,8 @@ public async Task SerializeDocWithDollarIdInDollarRefSucceeds() radius: type: number "; - var doc = (await OpenApiDocument.LoadAsync("Models/Samples/docWithDollarId.yaml", SettingsFixture.ReaderSettings)).Document; - var actual = await doc.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_1); + var doc = (await OpenApiDocument.LoadAsync("Models/Samples/docWithDollarId.yaml", SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken)).Document; + var actual = await doc.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_1, TestContext.Current.CancellationToken); Assert.Equal(expected.MakeLineBreaksEnvironmentNeutral(), actual.MakeLineBreaksEnvironmentNeutral()); } @@ -2089,7 +2089,7 @@ public async Task SerializeDocumentTagsWithMultipleExtensionsWorks() } }; - var actual = await doc.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await doc.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); Assert.Equal(expected.MakeLineBreaksEnvironmentNeutral(), actual.MakeLineBreaksEnvironmentNeutral()); } [Fact] @@ -2236,7 +2236,7 @@ public async Task SerializeAdvancedDocumentAsVersionJsonWorksAsync(OpenApiSpecVe // Act AdvancedDocument.SerializeAs(version, writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(version); @@ -2294,7 +2294,7 @@ public async Task SerializeDocWithSecuritySchemeWithInlineReferencesWorks() scopes: api://a2a7226d-e8d1-4ded-8c53-dd4c136ff456/repairs_read: Read repair records"; - var doc = (await OpenApiDocument.LoadAsync("Models/Samples/docWithSecurityScheme.yaml", SettingsFixture.ReaderSettings)).Document; + var doc = (await OpenApiDocument.LoadAsync("Models/Samples/docWithSecurityScheme.yaml", SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken)).Document; var stringWriter = new StringWriter(); doc.SerializeAsV3(new OpenApiYamlWriter(stringWriter, new OpenApiWriterSettings { InlineLocalReferences = true })); var actual = stringWriter.ToString(); @@ -2326,7 +2326,7 @@ public async Task SerializeDocWithoutOperationSecurityWorks() type: object """; - var doc = (await OpenApiDocument.LoadAsync("Models/Samples/docWithoutOperationSecurity.yaml", SettingsFixture.ReaderSettings)).Document; + var doc = (await OpenApiDocument.LoadAsync("Models/Samples/docWithoutOperationSecurity.yaml", SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken)).Document; var stringWriter = new StringWriter(); doc!.SerializeAsV3(new OpenApiYamlWriter(stringWriter, new OpenApiWriterSettings { InlineLocalReferences = true })); var actual = stringWriter.ToString(); @@ -2361,7 +2361,7 @@ public async Task SerializeDocWithEmptyOperationSecurityWorks() security: [ ] """; - var doc = (await OpenApiDocument.LoadAsync("Models/Samples/docWithEmptyOperationSecurity.yaml", SettingsFixture.ReaderSettings)).Document; + var doc = (await OpenApiDocument.LoadAsync("Models/Samples/docWithEmptyOperationSecurity.yaml", SettingsFixture.ReaderSettings, token: TestContext.Current.CancellationToken)).Document; var stringWriter = new StringWriter(); doc!.SerializeAsV3(new OpenApiYamlWriter(stringWriter, new OpenApiWriterSettings { InlineLocalReferences = true })); var actual = stringWriter.ToString(); @@ -2393,7 +2393,7 @@ public async Task SerializeDocumentWithSelfPropertyAsV32Works() paths: { }"; // Act - var actual = await doc.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_2); + var actual = await doc.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_2, TestContext.Current.CancellationToken); // Assert Assert.Equal(expected.MakeLineBreaksEnvironmentNeutral(), actual.MakeLineBreaksEnvironmentNeutral()); @@ -2441,7 +2441,7 @@ public async Task SerializeDocumentWithSecurityRequirementAsJsonWorks(OpenApiSpe """; // Act - var actual = await doc.SerializeAsJsonAsync(openApiSpecVersion); + var actual = await doc.SerializeAsJsonAsync(openApiSpecVersion, TestContext.Current.CancellationToken); // Assert var actualSecurity = JsonNode.Parse(actual)?["security"]; @@ -2471,7 +2471,7 @@ public async Task SerializeDocumentWithSelfPropertyAsV31WritesAsExtension() x-oai-$self: https://example.org/api/openapi.json"; // Act - var actual = await doc.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_1); + var actual = await doc.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_1, TestContext.Current.CancellationToken); // Assert Assert.Equal(expected.MakeLineBreaksEnvironmentNeutral(), actual.MakeLineBreaksEnvironmentNeutral()); @@ -2499,10 +2499,203 @@ public async Task SerializeDocumentWithSelfPropertyAsV30WritesAsExtension() x-oai-$self: https://example.org/api/openapi.json"; // Act - var actual = await doc.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await doc.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert Assert.Equal(expected.MakeLineBreaksEnvironmentNeutral(), actual.MakeLineBreaksEnvironmentNeutral()); } + + [Fact] + public void GetOperationById_ReturnsMatchingOperation() + { + var operation = new OpenApiOperation { OperationId = "getUser" }; + var doc = new OpenApiDocument + { + Info = new OpenApiInfo { Title = "Test", Version = "1.0" }, + Paths = new OpenApiPaths + { + ["/users/{id}"] = new OpenApiPathItem + { + Operations = new Dictionary + { + [HttpMethod.Get] = operation + } + } + } + }; + + var result = doc.GetOperationById("getUser"); + + Assert.Same(operation, result); + } + + [Fact] + public void GetOperationById_ReturnsNullWhenNotFound() + { + var doc = new OpenApiDocument + { + Info = new OpenApiInfo { Title = "Test", Version = "1.0" }, + Paths = new OpenApiPaths + { + ["/users"] = new OpenApiPathItem + { + Operations = new Dictionary + { + [HttpMethod.Get] = new OpenApiOperation { OperationId = "listUsers" } + } + } + } + }; + + var result = doc.GetOperationById("nonExistentId"); + + Assert.Null(result); + } + + [Fact] + public void GetOperationById_SearchesWebhooks() + { + var webhookOperation = new OpenApiOperation { OperationId = "onUserCreated" }; + var doc = new OpenApiDocument + { + Info = new OpenApiInfo { Title = "Test", Version = "1.0" }, + Paths = [], + Webhooks = new Dictionary + { + ["userCreated"] = new OpenApiPathItem + { + Operations = new Dictionary + { + [HttpMethod.Post] = webhookOperation + } + } + } + }; + + var result = doc.GetOperationById("onUserCreated"); + + Assert.Same(webhookOperation, result); + } + + [Fact] + public void GetOperationById_IsCaseSensitive() + { + var doc = new OpenApiDocument + { + Info = new OpenApiInfo { Title = "Test", Version = "1.0" }, + Paths = new OpenApiPaths + { + ["/users"] = new OpenApiPathItem + { + Operations = new Dictionary + { + [HttpMethod.Get] = new OpenApiOperation { OperationId = "getUser" } + } + } + } + }; + + Assert.NotNull(doc.GetOperationById("getUser")); + Assert.Null(doc.GetOperationById("GetUser")); + Assert.Null(doc.GetOperationById("GETUSER")); + } + + [Fact] + public void GetOperationById_ResolvesOperationThroughPathItemReference() + { + const string yaml = """ + openapi: '3.1.0' + info: + title: Test + version: 1.0.0 + paths: + /users: + $ref: '#/components/pathItems/userPathItem' + components: + pathItems: + userPathItem: + get: + operationId: listUsers + responses: + '200': + description: OK + """; + + var doc = OpenApiDocument.Parse(yaml, OpenApiConstants.Yaml, SettingsFixture.ReaderSettings).Document; + doc.Workspace.RegisterComponents(doc); + + var result = doc.GetOperationById("listUsers"); + + Assert.NotNull(result); + Assert.Equal("listUsers", result.OperationId); + } + + [Fact] + public void GetOperationById_DuplicateIdReturnsFirstMatch() + { + // operationId must be unique per spec, but if not, Paths takes priority over Webhooks + var pathsOperation = new OpenApiOperation { OperationId = "duplicateId" }; + var webhooksOperation = new OpenApiOperation { OperationId = "duplicateId" }; + var doc = new OpenApiDocument + { + Info = new OpenApiInfo { Title = "Test", Version = "1.0" }, + Paths = new OpenApiPaths + { + ["/users"] = new OpenApiPathItem + { + Operations = new Dictionary + { + [HttpMethod.Get] = pathsOperation + } + } + }, + Webhooks = new Dictionary + { + ["userEvent"] = new OpenApiPathItem + { + Operations = new Dictionary + { + [HttpMethod.Post] = webhooksOperation + } + } + } + }; + + var result = doc.GetOperationById("duplicateId"); + + Assert.Same(pathsOperation, result); + } + + [Fact] + public void GetOperationById_UnresolvedPathItemReferenceIsSkipped() + { + // An unresolved $ref has Target = null, so Operations = null — should be skipped gracefully + var unresolvedRef = new OpenApiPathItemReference("nonExistentPathItem", null); + var doc = new OpenApiDocument + { + Info = new OpenApiInfo { Title = "Test", Version = "1.0" }, + Paths = new OpenApiPaths + { + ["/users"] = unresolvedRef + } + }; + + var result = doc.GetOperationById("anyId"); + + Assert.Null(result); + } + + [Fact] + public void GetOperationById_ThrowsOnNullOrEmptyId() + { + var doc = new OpenApiDocument + { + Info = new OpenApiInfo { Title = "Test", Version = "1.0" }, + Paths = [] + }; + + Assert.Throws(() => doc.GetOperationById(null!)); + Assert.Throws(() => doc.GetOperationById(string.Empty)); + } } } diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiEncodingTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiEncodingTests.cs index a1280d48e..819f98cc3 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiEncodingTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiEncodingTests.cs @@ -27,7 +27,7 @@ public class OpenApiEncodingTests public async Task SerializeBasicEncodingAsV3Works(string format, string expected) { // Arrange & Act - var actual = await BasicEncoding.SerializeAsync(OpenApiSpecVersion.OpenApi3_0, format); + var actual = await BasicEncoding.SerializeAsync(OpenApiSpecVersion.OpenApi3_0, format, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -50,7 +50,7 @@ public async Task SerializeAdvanceEncodingAsV3JsonWorks() """; // Act - var actual = await AdvanceEncoding.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await AdvanceEncoding.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -71,7 +71,7 @@ public async Task SerializeAdvanceEncodingAsV3YamlWorks() """; // Act - var actual = await AdvanceEncoding.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await AdvanceEncoding.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -121,7 +121,7 @@ public async Task WhenExplodeIsSetOutputShouldHaveExplode(bool? expectedExplode, } // Act - var actual = await parameter.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await parameter.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -169,7 +169,7 @@ public async Task SerializeEncodingWithNestedEncodingAsV32JsonWorks() """; // Act - var actual = await encoding.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2); + var actual = await encoding.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2, TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(actual), JsonNode.Parse(expected))); @@ -210,7 +210,7 @@ public async Task SerializeEncodingWithNestedEncodingAsV32YamlWorks() """; // Act - var actual = await encoding.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_2); + var actual = await encoding.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_2, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -267,7 +267,7 @@ public async Task SerializeEncodingWithItemAndPrefixEncodingAsV32JsonWorks() """; // Act - var actual = await encoding.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2); + var actual = await encoding.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2, TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(actual), JsonNode.Parse(expected))); @@ -315,7 +315,7 @@ public async Task SerializeEncodingAsV31ShouldUseExtensionsForNewFields() """; // Act - var actual = await encoding.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1); + var actual = await encoding.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1, TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(actual), JsonNode.Parse(expected))); diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiExampleTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiExampleTests.cs index e9f5e3d62..ecd87836b 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiExampleTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiExampleTests.cs @@ -104,7 +104,7 @@ public async Task SerializeAdvancedExampleAsV3JsonWorksAsync(bool produceTerseOu // Act AdvancedExample.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -121,7 +121,7 @@ public async Task SerializeReferencedExampleAsV3JsonWorksAsync(bool produceTerse // Act OpenApiExampleReference.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -138,7 +138,7 @@ public async Task SerializeReferencedExampleAsV3JsonWithoutReferenceWorksAsync(b // Act ReferencedExample.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -164,7 +164,7 @@ public async Task SerializeExampleWithDataValueAsV32JsonWorksAsync(bool produceT // Act example.SerializeAsV32(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -190,7 +190,7 @@ public async Task SerializeExampleWithDataValueAsV31JsonWorksAsync(bool produceT // Act example.SerializeAsV31(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -212,7 +212,7 @@ public async Task SerializeExampleWithSerializedValueAsV32JsonWorksAsync(bool pr // Act example.SerializeAsV32(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -234,7 +234,7 @@ public async Task SerializeExampleWithSerializedValueAsV31JsonWorksAsync(bool pr // Act example.SerializeAsV31(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiExternalDocsTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiExternalDocsTests.cs index 9a290d425..95bcde5d1 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiExternalDocsTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiExternalDocsTests.cs @@ -25,7 +25,7 @@ public class OpenApiExternalDocsTests public async Task SerializeBasicExternalDocsAsV3Works(string format, string expected) { // Arrange & Act - var actual = await BasicExDocs.SerializeAsync(OpenApiSpecVersion.OpenApi3_0, format); + var actual = await BasicExDocs.SerializeAsync(OpenApiSpecVersion.OpenApi3_0, format, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -46,7 +46,7 @@ public async Task SerializeAdvanceExDocsAsV3JsonWorks() """; // Act - var actual = await AdvanceExDocs.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await AdvanceExDocs.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -65,7 +65,7 @@ public async Task SerializeAdvanceExDocsAsV3YamlWorks() """; // Act - var actual = await AdvanceExDocs.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await AdvanceExDocs.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiHeaderTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiHeaderTests.cs index 0f57503b3..a42fd1fb6 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiHeaderTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiHeaderTests.cs @@ -45,7 +45,7 @@ public async Task SerializeAdvancedHeaderAsV3JsonWorksAsync(bool produceTerseOut // Act AdvancedHeader.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -62,7 +62,7 @@ public async Task SerializeReferencedHeaderAsV3JsonWorksAsync(bool produceTerseO // Act OpenApiHeaderReference.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -79,7 +79,7 @@ public async Task SerializeReferencedHeaderAsV3JsonWithoutReferenceWorksAsync(bo // Act ReferencedHeader.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -96,7 +96,7 @@ public async Task SerializeAdvancedHeaderAsV2JsonWorksAsync(bool produceTerseOut // Act AdvancedHeader.SerializeAsV2(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -113,7 +113,7 @@ public async Task SerializeReferencedHeaderAsV2JsonWorksAsync(bool produceTerseO // Act OpenApiHeaderReference.SerializeAsV2(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -130,7 +130,7 @@ public async Task SerializeReferencedHeaderAsV2JsonWithoutReferenceWorksAsync(bo // Act ReferencedHeader.SerializeAsV2(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiInfoTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiInfoTests.cs index 57c3e1c7e..eaded3a3f 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiInfoTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiInfoTests.cs @@ -61,7 +61,7 @@ public static IEnumerable BasicInfoJsonExpected() public async Task SerializeBasicInfoAsJsonWorks(OpenApiSpecVersion version, string expected) { // Arrange & Act - var actual = await BasicInfo.SerializeAsJsonAsync(version); + var actual = await BasicInfo.SerializeAsJsonAsync(version, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -90,7 +90,7 @@ public static IEnumerable BasicInfoYamlExpected() public async Task SerializeBasicInfoAsYamlWorks(OpenApiSpecVersion version, string expected) { // Arrange & Act - var actual = await BasicInfo.SerializeAsYamlAsync(version); + var actual = await BasicInfo.SerializeAsYamlAsync(version, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -135,7 +135,7 @@ public static IEnumerable AdvanceInfoJsonExpect() public async Task SerializeAdvanceInfoAsJsonWorks(OpenApiSpecVersion version, string expected) { // Arrange & Act - var actual = await AdvanceInfo.SerializeAsJsonAsync(version); + var actual = await AdvanceInfo.SerializeAsJsonAsync(version, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -176,7 +176,7 @@ public static IEnumerable AdvanceInfoYamlExpect() public async Task SerializeAdvanceInfoAsYamlWorks(OpenApiSpecVersion version, string expected) { // Arrange & Act - var actual = await AdvanceInfo.SerializeAsYamlAsync(version); + var actual = await AdvanceInfo.SerializeAsYamlAsync(version, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -201,7 +201,7 @@ public async Task InfoVersionShouldAcceptDateStyledAsVersions() """; // Act - var actual = await info.SerializeAsync(OpenApiSpecVersion.OpenApi3_0, OpenApiConstants.Yaml); + var actual = await info.SerializeAsync(OpenApiSpecVersion.OpenApi3_0, OpenApiConstants.Yaml, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -219,7 +219,7 @@ public async Task SerializeInfoObjectWithSummaryAsV31YamlWorks() summary: This is a sample server for a pet store."; // Act - var actual = await InfoWithSummary.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_1); + var actual = await InfoWithSummary.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_1, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -239,7 +239,7 @@ public async Task SerializeInfoObjectWithSummaryAsV31JsonWorks() }"; // Act - var actual = await InfoWithSummary.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1); + var actual = await InfoWithSummary.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiLicenseTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiLicenseTests.cs index d81dce9b4..0f9a99c18 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiLicenseTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiLicenseTests.cs @@ -45,7 +45,7 @@ public async Task SerializeBasicLicenseAsJsonWorks(OpenApiSpecVersion version) """; // Act - var actual = await BasicLicense.SerializeAsJsonAsync(version); + var actual = await BasicLicense.SerializeAsJsonAsync(version, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -62,7 +62,7 @@ public async Task SerializeBasicLicenseAsYamlWorks(OpenApiSpecVersion version) var expected = "name: Apache 2.0"; // Act - var actual = await BasicLicense.SerializeAsYamlAsync(version); + var actual = await BasicLicense.SerializeAsYamlAsync(version, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -86,7 +86,7 @@ public async Task SerializeAdvanceLicenseAsJsonWorks(OpenApiSpecVersion version) """; // Act - var actual = await AdvanceLicense.SerializeAsJsonAsync(version); + var actual = await AdvanceLicense.SerializeAsJsonAsync(version, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -108,7 +108,7 @@ public async Task SerializeAdvanceLicenseAsYamlWorks(OpenApiSpecVersion version) """; // Act - var actual = await AdvanceLicense.SerializeAsYamlAsync(version); + var actual = await AdvanceLicense.SerializeAsYamlAsync(version, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -142,7 +142,7 @@ public async Task SerializeLicenseWithIdentifierAsJsonWorks() }"; // Act - var actual = await LicenseWithIdentifier.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1); + var actual = await LicenseWithIdentifier.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1, TestContext.Current.CancellationToken); // Assert Assert.Equal(expected.MakeLineBreaksEnvironmentNeutral(), actual.MakeLineBreaksEnvironmentNeutral()); @@ -161,7 +161,7 @@ public async Task SerializeLicenseWithIdentifierAsJsonWorksForV3AndV2(OpenApiSpe }"; // Act - var actual = await LicenseWithIdentifier.SerializeAsJsonAsync(version); + var actual = await LicenseWithIdentifier.SerializeAsJsonAsync(version, TestContext.Current.CancellationToken); // Assert Assert.Equal(expected.MakeLineBreaksEnvironmentNeutral(), actual.MakeLineBreaksEnvironmentNeutral()); @@ -175,7 +175,7 @@ public async Task SerializeLicenseWithIdentifierAsYamlWorks() identifier: Apache-2.0"; // Act - var actual = await LicenseWithIdentifier.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_1); + var actual = await LicenseWithIdentifier.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_1, TestContext.Current.CancellationToken); // Assert Assert.Equal(expected.MakeLineBreaksEnvironmentNeutral(), actual.MakeLineBreaksEnvironmentNeutral()); @@ -191,7 +191,7 @@ public async Task SerializeLicenseWithIdentifierAsYamlWorksForV3AndV2(OpenApiSpe x-oai-license-identifier: Apache-2.0"; // Act - var actual = await LicenseWithIdentifier.SerializeAsYamlAsync(version); + var actual = await LicenseWithIdentifier.SerializeAsYamlAsync(version, TestContext.Current.CancellationToken); // Assert Assert.Equal(expected.MakeLineBreaksEnvironmentNeutral(), actual.MakeLineBreaksEnvironmentNeutral()); diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiLinkTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiLinkTests.cs index f67e82356..bf5e49b2e 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiLinkTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiLinkTests.cs @@ -74,7 +74,7 @@ public async Task SerializeAdvancedLinkAsV3JsonWorksAsync(bool produceTerseOutpu // Act AdvancedLink.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -91,7 +91,7 @@ public async Task SerializeReferencedLinkAsV3JsonWorksAsync(bool produceTerseOut // Act LinkReference.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -108,7 +108,7 @@ public async Task SerializeReferencedLinkAsV3JsonWithoutReferenceWorksAsync(bool // Act ReferencedLink.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiMediaTypeTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiMediaTypeTests.cs index 6ad046fcd..23d0b8fbb 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiMediaTypeTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiMediaTypeTests.cs @@ -5,7 +5,6 @@ using System.Text.Json.Nodes; using System.Threading.Tasks; using Xunit; -using Xunit.Abstractions; namespace Microsoft.OpenApi.Tests.Models { @@ -151,7 +150,7 @@ public OpenApiMediaTypeTests(ITestOutputHelper output) public async Task SerializeBasicMediaTypeAsV3Works(string format, string expected) { // Arrange & Act - var actual = await BasicMediaType.SerializeAsync(OpenApiSpecVersion.OpenApi3_0, format); + var actual = await BasicMediaType.SerializeAsync(OpenApiSpecVersion.OpenApi3_0, format, cancellationToken: TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -179,7 +178,7 @@ public async Task SerializeAdvanceMediaTypeAsV3JsonWorks() """; // Act - var actual = await AdvanceMediaType.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await AdvanceMediaType.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, cancellationToken: TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(actual), JsonNode.Parse(expected))); @@ -201,7 +200,7 @@ public async Task SerializeAdvanceMediaTypeAsV3YamlWorks() """; // Act - var actual = await AdvanceMediaType.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await AdvanceMediaType.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0, cancellationToken: TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -236,7 +235,7 @@ public async Task SerializeMediaTypeWithObjectExampleAsV3YamlWorks() """; // Act - var actual = await MediaTypeWithObjectExample.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await MediaTypeWithObjectExample.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0, cancellationToken: TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -287,7 +286,7 @@ public async Task SerializeMediaTypeWithObjectExampleAsV3JsonWorks() """; // Act - var actual = await MediaTypeWithObjectExample.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await MediaTypeWithObjectExample.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, cancellationToken: TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(actual), JsonNode.Parse(expected))); @@ -309,7 +308,7 @@ public async Task SerializeMediaTypeWithXmlExampleAsV3YamlWorks() """; // Act - var actual = await MediaTypeWithXmlExample.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await MediaTypeWithXmlExample.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0, cancellationToken: TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -337,7 +336,7 @@ public async Task SerializeMediaTypeWithXmlExampleAsV3JsonWorks() """; // Act - var actual = await MediaTypeWithXmlExample.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await MediaTypeWithXmlExample.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, cancellationToken: TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(actual), JsonNode.Parse(expected))); @@ -372,7 +371,7 @@ public async Task SerializeMediaTypeWithObjectExamplesAsV3YamlWorks() """; // Act - var actual = await MediaTypeWithObjectExamples.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await MediaTypeWithObjectExamples.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0, cancellationToken: TestContext.Current.CancellationToken); _output.WriteLine(actual); // Assert @@ -428,7 +427,7 @@ public async Task SerializeMediaTypeWithObjectExamplesAsV3JsonWorks() """; // Act - var actual = await MediaTypeWithObjectExamples.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await MediaTypeWithObjectExamples.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, cancellationToken: TestContext.Current.CancellationToken); _output.WriteLine(actual); // Assert @@ -485,7 +484,7 @@ public async Task SerializeMediaTypeWithItemSchemaAsV32JsonWorks() """; // Act - var actual = await mediaType.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2); + var actual = await mediaType.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2, cancellationToken: TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(actual), JsonNode.Parse(expected))); @@ -522,7 +521,7 @@ public async Task SerializeMediaTypeWithItemSchemaAsV31JsonWorks() """; // Act - var actual = await mediaType.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1); + var actual = await mediaType.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1, cancellationToken: TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(actual), JsonNode.Parse(expected))); @@ -559,7 +558,7 @@ public async Task SerializeMediaTypeWithItemSchemaAsV3JsonWorks() """; // Act - var actual = await mediaType.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await mediaType.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, cancellationToken: TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(actual), JsonNode.Parse(expected))); @@ -603,7 +602,7 @@ public async Task SerializeMediaTypeWithItemEncodingAsV32JsonWorks() """; // Act - var actual = await MediaTypeWithItemEncoding.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2); + var actual = await MediaTypeWithItemEncoding.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2, cancellationToken: TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(actual), JsonNode.Parse(expected))); @@ -626,7 +625,7 @@ public async Task SerializeMediaTypeWithItemEncodingAsV31JsonWorks() """; // Act - var actual = await MediaTypeWithItemEncoding.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1); + var actual = await MediaTypeWithItemEncoding.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1, cancellationToken: TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(actual), JsonNode.Parse(expected))); @@ -655,7 +654,7 @@ public async Task SerializeMediaTypeWithPrefixEncodingAsV32JsonWorks() """; // Act - var actual = await MediaTypeWithPrefixEncoding.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2); + var actual = await MediaTypeWithPrefixEncoding.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2, cancellationToken: TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(actual), JsonNode.Parse(expected))); @@ -684,7 +683,7 @@ public async Task SerializeMediaTypeWithPrefixEncodingAsV31JsonWorks() """; // Act - var actual = await MediaTypeWithPrefixEncoding.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1); + var actual = await MediaTypeWithPrefixEncoding.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1, cancellationToken: TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(actual), JsonNode.Parse(expected))); diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiOAuthFlowTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiOAuthFlowTests.cs index 73cb64e95..86c1a7cfb 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiOAuthFlowTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiOAuthFlowTests.cs @@ -60,7 +60,7 @@ public async Task SerializeBasicOAuthFlowAsV3JsonWorks() """; // Act - var actual = await BasicOAuthFlow.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await BasicOAuthFlow.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -76,7 +76,7 @@ public async Task SerializeBasicOAuthFlowAsV3YamlWorks() @"scopes: { }"; // Act - var actual = await BasicOAuthFlow.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await BasicOAuthFlow.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -100,7 +100,7 @@ public async Task SerializePartialOAuthFlowAsV3JsonWorks() """; // Act - var actual = await PartialOAuthFlow.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await PartialOAuthFlow.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -126,7 +126,7 @@ public async Task SerializeCompleteOAuthFlowAsV3JsonWorks() """; // Act - var actual = await CompleteOAuthFlow.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await CompleteOAuthFlow.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -153,7 +153,7 @@ public async Task SerializeOAuthFlowWithDeviceAuthorizationUrlAsV32JsonWorks() """; // Act - var actual = await OAuthFlowWithDeviceAuthorizationUrl.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2); + var actual = await OAuthFlowWithDeviceAuthorizationUrl.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2, TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(actual), JsonNode.Parse(expected))); @@ -178,7 +178,7 @@ public async Task SerializeOAuthFlowWithDeviceAuthorizationUrlAsV31JsonWorks() """; // Act - var actual = await OAuthFlowWithDeviceAuthorizationUrl.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1); + var actual = await OAuthFlowWithDeviceAuthorizationUrl.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1, TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(actual), JsonNode.Parse(expected))); @@ -203,7 +203,7 @@ public async Task SerializeOAuthFlowWithDeviceAuthorizationUrlAsV3JsonWorks() """; // Act - var actual = await OAuthFlowWithDeviceAuthorizationUrl.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await OAuthFlowWithDeviceAuthorizationUrl.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(actual), JsonNode.Parse(expected))); diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiOAuthFlowsTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiOAuthFlowsTests.cs index cba2e1c93..550c7d7d5 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiOAuthFlowsTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiOAuthFlowsTests.cs @@ -71,7 +71,7 @@ public async Task SerializeBasicOAuthFlowsAsV3JsonWorks() @"{ }"; // Act - var actual = await BasicOAuthFlows.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await BasicOAuthFlows.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -87,7 +87,7 @@ public async Task SerializeBasicOAuthFlowsAsV3YamlWorks() @"{ }"; // Act - var actual = await BasicOAuthFlows.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await BasicOAuthFlows.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -113,7 +113,7 @@ public async Task SerializeOAuthFlowsWithSingleFlowAsV3JsonWorks() """; // Act - var actual = await OAuthFlowsWithSingleFlow.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await OAuthFlowsWithSingleFlow.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -147,7 +147,7 @@ public async Task SerializeOAuthFlowsWithMultipleFlowsAsV3JsonWorks() """; // Act - var actual = await OAuthFlowsWithMultipleFlows.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await OAuthFlowsWithMultipleFlows.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -174,7 +174,7 @@ public async Task SerializeOAuthFlowsWithDeviceAuthorizationAsV32JsonWorks() """; // Act - var actual = await OAuthFlowsWithDeviceAuthorization.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2); + var actual = await OAuthFlowsWithDeviceAuthorization.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2, TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); @@ -199,7 +199,7 @@ public async Task SerializeOAuthFlowsWithDeviceAuthorizationAsV31JsonWorks() """; // Act - var actual = await OAuthFlowsWithDeviceAuthorization.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1); + var actual = await OAuthFlowsWithDeviceAuthorization.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1, TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); @@ -224,7 +224,7 @@ public async Task SerializeOAuthFlowsWithDeviceAuthorizationAsV3JsonWorks() """; // Act - var actual = await OAuthFlowsWithDeviceAuthorization.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await OAuthFlowsWithDeviceAuthorization.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiOperationTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiOperationTests.cs index b82a51e75..d281f4437 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiOperationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiOperationTests.cs @@ -293,7 +293,7 @@ public async Task SerializeBasicOperationAsV3JsonWorks() """; // Act - var actual = await _basicOperation.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await _basicOperation.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -343,7 +343,6 @@ public async Task SerializeOperationWithBodyAsV3JsonWorks() "$ref": "#/components/responses/response1" }, "400": { - "description": null, "content": { "application/json": { "schema": { @@ -365,7 +364,7 @@ public async Task SerializeOperationWithBodyAsV3JsonWorks() """; // Act - var actual = await _operationWithBody.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await _operationWithBody.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); @@ -416,7 +415,6 @@ public async Task SerializeAdvancedOperationWithTagAndSecurityAsV3JsonWorks() "$ref": "#/components/responses/response1" }, "400": { - "description": null, "content": { "application/json": { "schema": { @@ -447,7 +445,7 @@ public async Task SerializeAdvancedOperationWithTagAndSecurityAsV3JsonWorks() """; // Act - var actual = await _advancedOperationWithTagsAndSecurity.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await _advancedOperationWithTagsAndSecurity.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); @@ -465,7 +463,7 @@ public async Task SerializeBasicOperationAsV2JsonWorks() """; // Act - var actual = await _basicOperation.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0); + var actual = await _basicOperation.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0, TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); @@ -542,7 +540,7 @@ public async Task SerializeOperationWithFormDataAsV3JsonWorks() """; // Act - var actual = await _operationWithFormData.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await _operationWithFormData.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); @@ -596,7 +594,7 @@ public async Task SerializeOperationWithFormDataAsV2JsonWorks() """; // Act - var actual = await _operationWithFormData.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0); + var actual = await _operationWithFormData.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0, TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); @@ -648,7 +646,6 @@ public async Task SerializeOperationWithBodyAsV2JsonWorks() "$ref": "#/responses/response1" }, "400": { - "description": null, "schema": { "type": "number", "maximum": 10, @@ -663,7 +660,7 @@ public async Task SerializeOperationWithBodyAsV2JsonWorks() """; // Act - var actual = await _operationWithBody.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0); + var actual = await _operationWithBody.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0, TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); @@ -718,7 +715,6 @@ public async Task SerializeAdvancedOperationWithTagAndSecurityAsV2JsonWorks() "$ref": "#/responses/response1" }, "400": { - "description": null, "schema": { "type": "number", "maximum": 10, @@ -742,7 +738,7 @@ public async Task SerializeAdvancedOperationWithTagAndSecurityAsV2JsonWorks() """; // Act - var actual = await _advancedOperationWithTagsAndSecurity.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0); + var actual = await _advancedOperationWithTagsAndSecurity.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0, TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); @@ -765,7 +761,7 @@ public async Task SerializeOperationWithNullCollectionAsV2JsonWorks() }; // Act - var actual = await operation.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0); + var actual = await operation.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0, TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); @@ -826,9 +822,9 @@ public async Task EnsureOpenApiOperationCopyConstructor_SerializationResultsInSa foreach (var operation in operations) { // Act - var expected = await operation.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var expected = await operation.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); var openApiOperation = new OpenApiOperation(operation); - var actual = await openApiOperation.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await openApiOperation.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert Assert.Equal(expected, actual); diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterCookieStyleTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterCookieStyleTests.cs index cf0348fa9..04f158bc2 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterCookieStyleTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterCookieStyleTests.cs @@ -86,7 +86,7 @@ public async Task SerializeCookieParameterAsV32JsonWorks() """; // Act - var actual = await CookieParameter.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2); + var actual = await CookieParameter.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2, TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(actual), JsonNode.Parse(expected))); @@ -109,7 +109,7 @@ public async Task SerializeCookieParameterWithDefaultStyleAsV32JsonWorks() """; // Act - var actual = await CookieParameterWithDefault.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2); + var actual = await CookieParameterWithDefault.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2, TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(actual), JsonNode.Parse(expected))); @@ -155,9 +155,9 @@ public async Task SerializeCookieParameterWithDefaultStyleWorksForEarlierVersion // Arrange & Act string actual = version switch { - OpenApiSpecVersion.OpenApi2_0 => await CookieParameterWithDefault.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0), - OpenApiSpecVersion.OpenApi3_0 => await CookieParameterWithDefault.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0), - OpenApiSpecVersion.OpenApi3_1 => await CookieParameterWithDefault.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1), + OpenApiSpecVersion.OpenApi2_0 => await CookieParameterWithDefault.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0, TestContext.Current.CancellationToken), + OpenApiSpecVersion.OpenApi3_0 => await CookieParameterWithDefault.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken), + OpenApiSpecVersion.OpenApi3_1 => await CookieParameterWithDefault.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1, TestContext.Current.CancellationToken), _ => throw new ArgumentOutOfRangeException() }; @@ -177,7 +177,7 @@ public async Task SerializeCookieParameterAsV32JsonWorksAsync(bool produceTerseO // Act CookieParameter.SerializeAsV32(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -221,7 +221,7 @@ public async Task SerializeCookieParameterAsYamlV32Works() """; // Act - var actual = await CookieParameter.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_2); + var actual = await CookieParameter.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_2, TestContext.Current.CancellationToken); // Assert Assert.Equal(expected.MakeLineBreaksEnvironmentNeutral(), actual.MakeLineBreaksEnvironmentNeutral()); diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.cs index 0cd2726dd..e6c30fc04 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiParameterTests.cs @@ -172,7 +172,7 @@ public async Task WhenStyleAndInIsNullTheDefaultValueOfStyleShouldBeSimple(Param // Act & Assert parameter.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); Assert.Equal(expectedStyle, parameter.Style); } @@ -189,7 +189,7 @@ public async Task SerializeQueryParameterWithMissingStyleSucceeds() type: integer"; // Act - var actual = await QueryParameterWithMissingStyle.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await QueryParameterWithMissingStyle.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert Assert.Equal(expected.MakeLineBreaksEnvironmentNeutral(), actual.MakeLineBreaksEnvironmentNeutral()); @@ -208,7 +208,7 @@ public async Task SerializeBasicParameterAsV3JsonWorks() """; // Act - var actual = await BasicParameter.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await BasicParameter.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -251,7 +251,7 @@ public async Task SerializeAdvancedParameterAsV3JsonWorks() """; // Act - var actual = await AdvancedPathParameterWithSchema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await AdvancedPathParameterWithSchema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -281,7 +281,7 @@ public async Task SerializeAdvancedParameterAsV2JsonWorks() """; // Act - var actual = await AdvancedPathParameterWithSchema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0); + var actual = await AdvancedPathParameterWithSchema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -300,7 +300,7 @@ public async Task SerializeReferencedParameterAsV3JsonWorksAsync(bool produceTer // Act OpenApiParameterReference.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -317,7 +317,7 @@ public async Task SerializeReferencedParameterAsV3JsonWithoutReferenceWorksAsync // Act ReferencedParameter.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -334,7 +334,7 @@ public async Task SerializeReferencedParameterAsV2JsonWorksAsync(bool produceTer // Act OpenApiParameterReference.SerializeAsV2(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -351,7 +351,7 @@ public async Task SerializeReferencedParameterAsV2JsonWithoutReferenceWorksAsync // Act ReferencedParameter.SerializeAsV2(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -368,7 +368,7 @@ public async Task SerializeParameterWithSchemaTypeObjectAsV2JsonWorksAsync(bool // Act AdvancedHeaderParameterWithSchemaTypeObject.SerializeAsV2(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -385,7 +385,7 @@ public async Task SerializeParameterWithFormStyleAndExplodeFalseWorksAsync(bool // Act ParameterWithFormStyleAndExplodeFalse.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -402,7 +402,7 @@ public async Task SerializeParameterWithFormStyleAndExplodeTrueWorksAsync(bool p // Act ParameterWithFormStyleAndExplodeTrue.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -451,7 +451,7 @@ public async Task SerializeQueryStringParameter_V32_Succeeds() Schema = null, // Explode must be false (default) and not set }; - var json = await parameter.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2); + var json = await parameter.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2, TestContext.Current.CancellationToken); Assert.Contains("querystring", json); } } diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiPathItemTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiPathItemTests.cs index 5f76693a3..a27675e1d 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiPathItemTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiPathItemTests.cs @@ -130,7 +130,7 @@ public async Task SerializeAsV2JsonWorks() var parsedExpectedJson = JsonNode.Parse(expectedJson); // When - var actualJson = await pathItem.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0); + var actualJson = await pathItem.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0, TestContext.Current.CancellationToken); var parsedActualJson = JsonNode.Parse(actualJson); // Then @@ -262,7 +262,7 @@ public async Task SerializeAsV3JsonWorks() var parsedExpectedJson = JsonNode.Parse(expectedJson); // When - var actualJson = await pathItem.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actualJson = await pathItem.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); var parsedActualJson = JsonNode.Parse(actualJson); // Then @@ -394,7 +394,7 @@ public async Task SerializeAsV31JsonWorks() var parsedExpectedJson = JsonNode.Parse(expectedJson); // When - var actualJson = await pathItem.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1); + var actualJson = await pathItem.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1, TestContext.Current.CancellationToken); var parsedActualJson = JsonNode.Parse(actualJson); // Then @@ -517,7 +517,7 @@ public async Task SerializeAsV32JsonWithQueryAndAdditionalOperationsWorks() var parsedExpectedJson = JsonNode.Parse(expectedJson); // When - var actualJson = await pathItem.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2); + var actualJson = await pathItem.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2, TestContext.Current.CancellationToken); var parsedActualJson = JsonNode.Parse(actualJson); // Then @@ -574,7 +574,7 @@ public async Task SerializeV32FeaturesAsExtensionsInV31Works() }; // When - var actualJson = await pathItem.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1); + var actualJson = await pathItem.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1, TestContext.Current.CancellationToken); var parsedActualJson = Assert.IsType(JsonNode.Parse(actualJson)); // Then - should contain x-oai- prefixed extensions in the operation @@ -633,7 +633,7 @@ public async Task SerializeV32FeaturesAsExtensionsInV3Works() }; // When - var actualJson = await pathItem.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actualJson = await pathItem.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); var parsedActualJson = Assert.IsType(JsonNode.Parse(actualJson)); // Then - should contain x-oai- prefixed extensions in the operation @@ -692,7 +692,7 @@ public async Task SerializeV32FeaturesAsExtensionsInV2Works() }; // When - var actualJson = await pathItem.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0); + var actualJson = await pathItem.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0, TestContext.Current.CancellationToken); var parsedActualJson = Assert.IsType(JsonNode.Parse(actualJson)); // Then - should contain x-oai- prefixed extensions in the operation @@ -790,7 +790,7 @@ public async Task SerializeV32PathItemToV31ProducesExtensions() }; // Act - var yaml = await pathItem.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_1); + var yaml = await pathItem.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_1, TestContext.Current.CancellationToken); // Assert Assert.Contains("Query:", yaml); @@ -839,7 +839,7 @@ public async Task SerializeV32PathItemToV3ProducesExtensions() }; // Act - var yaml = await pathItem.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0); + var yaml = await pathItem.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert Assert.Contains("Query:", yaml); diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiReferenceTests.cs index e2ad1f87c..a00963cfe 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiReferenceTests.cs @@ -97,7 +97,7 @@ public async Task SerializeSchemaReferenceAsJsonV3Works() """; // Act - var actual = await reference.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await reference.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); expected = expected.MakeLineBreaksEnvironmentNeutral(); actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -121,7 +121,7 @@ public async Task SerializeHttpSchemaReferenceAsJsonV31Works(string id, string r """; // Act - var actual = await reference.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1); + var actual = await reference.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1, TestContext.Current.CancellationToken); expected = expected.MakeLineBreaksEnvironmentNeutral(); actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -142,7 +142,7 @@ public async Task SerializeSchemaReferenceAsYamlV3Works() var expected = @"$ref: '#/components/schemas/Pet'"; // Act - var actual = await reference.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await reference.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert Assert.Equal(expected, actual); @@ -166,7 +166,7 @@ public async Task SerializeSchemaReferenceAsJsonV2Works() """.MakeLineBreaksEnvironmentNeutral(); // Act - var actual = await reference.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0); + var actual = await reference.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0, TestContext.Current.CancellationToken); // Assert Assert.Equal(expected, actual.MakeLineBreaksEnvironmentNeutral()); @@ -184,7 +184,7 @@ public async Task SerializeSchemaReferenceAsYamlV2Works() var expected = @"$ref: '#/definitions/Pet'"; // Act - var actual = await reference.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi2_0); + var actual = await reference.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi2_0, TestContext.Current.CancellationToken); // Assert Assert.Equal(expected, actual); @@ -209,7 +209,7 @@ public async Task SerializeExternalReferenceAsJsonV2Works() """; // Act - var actual = await reference.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0); + var actual = await reference.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0, TestContext.Current.CancellationToken); expected = expected.MakeLineBreaksEnvironmentNeutral(); actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -230,7 +230,7 @@ public async Task SerializeExternalReferenceAsYamlV2Works() var expected = @"$ref: main.json#/definitions/Pets"; // Act - var actual = await reference.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi2_0); + var actual = await reference.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi2_0, TestContext.Current.CancellationToken); // Assert Assert.Equal(expected, actual); @@ -250,7 +250,7 @@ public async Task SerializeExternalReferenceAsJsonV3Works() """; // Act - var actual = await reference.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await reference.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); expected = expected.MakeLineBreaksEnvironmentNeutral(); actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -266,7 +266,7 @@ public async Task SerializeExternalReferenceAsYamlV3Works() var expected = @"$ref: main.json#/components/schemas/Pets"; // Act - var actual = await reference.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await reference.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert Assert.Equal(expected, actual); diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiRequestBodyTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiRequestBodyTests.cs index d3df1e1ed..222de1c3c 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiRequestBodyTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiRequestBodyTests.cs @@ -57,7 +57,7 @@ public async Task SerializeAdvancedRequestBodyAsV3JsonWorksAsync(bool produceTer // Act AdvancedRequestBody.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -74,7 +74,7 @@ public async Task SerializeReferencedRequestBodyAsV3JsonWorksAsync(bool produceT // Act OpenApiRequestBodyReference.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -91,7 +91,7 @@ public async Task SerializeReferencedRequestBodyAsV3JsonWithoutReferenceWorksAsy // Act ReferencedRequestBody.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiResponseTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiResponseTests.cs index 5810e15fc..fdd291162 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiResponseTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiResponseTests.cs @@ -193,12 +193,10 @@ public async Task SerializeBasicResponseWorks( string format) { // Arrange - var expected = format == OpenApiConstants.Json ? @"{ - ""description"": null -}" : @"description: null"; + var expected = @"{ }"; // Act - var actual = await BasicResponse.SerializeAsync(version, format); + var actual = await BasicResponse.SerializeAsync(version, format, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -241,7 +239,7 @@ public async Task SerializeAdvancedResponseAsV3JsonWorks() }"; // Act - var actual = await AdvancedV3Response.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await AdvancedV3Response.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -274,7 +272,7 @@ public async Task SerializeAdvancedResponseAsV3YamlWorks() myextension: myextensionvalue"; // Act - var actual = await AdvancedV3Response.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await AdvancedV3Response.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -311,7 +309,7 @@ public async Task SerializeAdvancedResponseAsV2JsonWorks() }"; // Act - var actual = await AdvancedV2Response.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0); + var actual = await AdvancedV2Response.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -341,7 +339,7 @@ public async Task SerializeAdvancedResponseAsV2YamlWorks() type: integer"; // Act - var actual = await AdvancedV2Response.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi2_0); + var actual = await AdvancedV2Response.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi2_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -360,7 +358,7 @@ public async Task SerializeReferencedResponseAsV3JsonWorksAsync(bool produceTers // Act V3OpenApiResponseReference.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -377,7 +375,7 @@ public async Task SerializeReferencedResponseAsV3JsonWithoutReferenceWorksAsync( // Act ReferencedV3Response.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -394,7 +392,7 @@ public async Task SerializeReferencedResponseAsV2JsonWorksAsync(bool produceTers // Act V2OpenApiResponseReference.SerializeAsV2(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -411,7 +409,7 @@ public async Task SerializeReferencedResponseAsV2JsonWithoutReferenceWorksAsync( // Act ReferencedV2Response.SerializeAsV2(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -434,7 +432,7 @@ public async Task SerializeResponseWithSummaryAsV32Works() }"; // Act - var actual = await ResponseWithSummary.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2); + var actual = await ResponseWithSummary.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2, TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); @@ -457,7 +455,7 @@ public async Task SerializeResponseWithSummaryAsV31Works() }"; // Act - var actual = await ResponseWithSummary.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1); + var actual = await ResponseWithSummary.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1, TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); @@ -480,7 +478,7 @@ public async Task SerializeResponseWithSummaryAsV3Works() }"; // Act - var actual = await ResponseWithSummary.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await ResponseWithSummary.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs index 7a94ab513..c1815a2d7 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaTests.cs @@ -10,7 +10,6 @@ using System.Text.Json.Schema; using System.Text.Json.Serialization; using System.Threading.Tasks; -using FluentAssertions; using Microsoft.OpenApi.Reader; using VerifyXunit; using Xunit; @@ -219,7 +218,7 @@ public async Task SerializeBasicSchemaAsJsonWorks(OpenApiSpecVersion version) var expected = @"{ }"; // Act - var actual = await BasicSchema.SerializeAsJsonAsync(version); + var actual = await BasicSchema.SerializeAsJsonAsync(version, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -249,7 +248,7 @@ public async Task SerializeAdvancedSchemaNumberAsV3JsonWorks() """; // Act - var actual = await AdvancedSchemaNumber.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await AdvancedSchemaNumber.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); @@ -300,7 +299,7 @@ public async Task SerializeAdvancedSchemaObjectAsV3JsonWorks() """; // Act - var actual = await AdvancedSchemaObject.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await AdvancedSchemaObject.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); @@ -355,7 +354,7 @@ public async Task SerializeAdvancedSchemaWithAllOfAsV3JsonWorks() """; // Act - var actual = await AdvancedSchemaWithAllOf.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await AdvancedSchemaWithAllOf.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert Assert.True(JsonObject.DeepEquals(JsonObject.Parse(expected), JsonObject.Parse(actual))); @@ -372,7 +371,7 @@ public async Task SerializeReferencedSchemaAsV3WithoutReferenceJsonWorksAsync(bo // Act ReferencedSchema.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -389,7 +388,7 @@ public async Task SerializeReferencedSchemaAsV3JsonWorksAsync(bool produceTerseO // Act ReferencedSchema.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -406,7 +405,7 @@ public async Task SerializeSchemaWRequiredPropertiesAsV2JsonWorksAsync(bool prod // Act AdvancedSchemaWithRequiredPropertiesObject.SerializeAsV2(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -435,7 +434,7 @@ public async Task SerializeAsV2ShouldSetFormatPropertyInParentSchemaIfPresentInC // Act // Serialize as V2 schema.SerializeAsV2(openApiJsonWriter); - await openApiJsonWriter.FlushAsync(); + await openApiJsonWriter.FlushAsync(TestContext.Current.CancellationToken); var v2Schema = outputStringWriter.GetStringBuilder().ToString().MakeLineBreaksEnvironmentNeutral(); @@ -637,9 +636,7 @@ public void CloningSchemaExamplesWorks(JsonNode example) var schemaCopy = schema.CreateShallowCopy(); // Act && Assert - schema.Example.Should().BeEquivalentTo(schemaCopy.Example, options => options - .IgnoringCyclicReferences() - .Excluding(x => x.Options)); + OpenApiTestAssert.Equivalent(schemaCopy.Example, schema.Example, nameof(JsonNode.Options)); } #pragma warning restore CS0618 @@ -729,7 +726,7 @@ public async Task SerializeSchemaWithUnrecognizedPropertiesWorks() }"; // Act - var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1); + var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1, TestContext.Current.CancellationToken); // Assert Assert.Equal(expected.MakeLineBreaksEnvironmentNeutral(), actual.MakeLineBreaksEnvironmentNeutral()); @@ -751,7 +748,7 @@ public async Task WriteAsItemsPropertiesDoesNotWriteNull() // Act schema.WriteAsItemsProperties(writer); writer.WriteEndObject(); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert var actual = outputStringWriter.GetStringBuilder().ToString(); @@ -775,7 +772,7 @@ public async Task SerializeConstAsEnumV30() // Act - var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert var v3Node = JsonNode.Parse(actual); @@ -795,7 +792,7 @@ public async Task SerializeConstAsEnumV20() }; // Act - var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0); + var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0, TestContext.Current.CancellationToken); // Assert var v2Node = JsonNode.Parse(actual); @@ -815,7 +812,7 @@ public async Task SerializeAdditionalPropertiesAsV2WithEmptySchemaEmits() }; // When - var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0); + var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0, TestContext.Current.CancellationToken); // Then Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); @@ -833,7 +830,7 @@ public async Task SerializeAdditionalPropertiesAsV2WithRefSchemaEmits() }; // When - var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0); + var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0, TestContext.Current.CancellationToken); // Then Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); @@ -850,7 +847,7 @@ public async Task SerializeAdditionalPropertiesAllowedAsV2DefaultDoesNotEmit() }; // When - var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0); + var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0, TestContext.Current.CancellationToken); // Then Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); @@ -867,7 +864,7 @@ public async Task SerializeAdditionalPropertiesAllowedAsV2FalseEmits() }; // When - var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0); + var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0, TestContext.Current.CancellationToken); // Then Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); @@ -887,7 +884,7 @@ public async Task SerializeAdditionalPropertiesAllowedAsV3PlusDefaultDoesNotEmit }; // When - var actual = await schema.SerializeAsJsonAsync(version); + var actual = await schema.SerializeAsJsonAsync(version, TestContext.Current.CancellationToken); // Then Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); @@ -907,7 +904,7 @@ public async Task SerializeAdditionalPropertiesAllowedAsV3PlusFalseEmits(OpenApi }; // When - var actual = await schema.SerializeAsJsonAsync(version); + var actual = await schema.SerializeAsJsonAsync(version, TestContext.Current.CancellationToken); // Then Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); @@ -927,7 +924,7 @@ public async Task SerializeAdditionalPropertiesAsV3PlusEmits(OpenApiSpecVersion }; // When - var actual = await schema.SerializeAsJsonAsync(version); + var actual = await schema.SerializeAsJsonAsync(version, TestContext.Current.CancellationToken); // Then Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); @@ -955,7 +952,7 @@ public async Task SerializeOneOfWithNullAsV3ShouldUseNullableAsync() // Act schema.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); var v3Schema = outputStringWriter.GetStringBuilder().ToString(); @@ -1000,7 +997,7 @@ public async Task SerializeOneOfWithNullAndMultipleSchemasAsV3ShouldMarkItAsNull // Act schema.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); var v3Schema = outputStringWriter.GetStringBuilder().ToString(); @@ -1053,7 +1050,7 @@ public async Task SerializeAnyOfWithNullAsV3ShouldUseNullableAsync() // Act schema.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); var v3Schema = outputStringWriter.GetStringBuilder().ToString(); @@ -1101,7 +1098,7 @@ public async Task SerializeAnyOfWithNullAndMultipleSchemasAsV3ShouldApplyNullabl // Act schema.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); var v3Schema = outputStringWriter.GetStringBuilder().ToString(); @@ -1147,7 +1144,7 @@ public async Task SerializeOneOfWithOnlyNullAsV3ShouldJustBeNullableAsync() // Act schema.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); var v3Schema = outputStringWriter.GetStringBuilder().ToString(); @@ -1187,7 +1184,7 @@ public async Task SerializeOneOfWithNullAsV31ShouldNotChangeAsync() // Act schema.SerializeAsV31(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); var v31Schema = outputStringWriter.GetStringBuilder().ToString(); @@ -1251,7 +1248,7 @@ public async Task SerializeOneOfWithNullAndRefAsV3ShouldUseNullableAsync() // Act schema.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); var v3Schema = outputStringWriter.GetStringBuilder().ToString(); @@ -1293,7 +1290,7 @@ public async Task SerializeContainsKeywordsAsV31Works() // Act schema.SerializeAsV31(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); var v31Schema = outputStringWriter.GetStringBuilder().ToString(); @@ -1329,7 +1326,7 @@ public async Task SerializeContainsKeywordsAsV3EmitsCompatibilityExtensions() // Act schema.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); var v3Schema = outputStringWriter.GetStringBuilder().ToString(); @@ -1358,7 +1355,7 @@ public async Task SerializeUnevaluatedPropertiesBooleanDefaultDoesNotEmit() var schema = new OpenApiSchema(); // When - var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1); + var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1, TestContext.Current.CancellationToken); // Then Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); @@ -1375,7 +1372,7 @@ public async Task SerializeUnevaluatedPropertiesBooleanTrueDoesNotEmit() }; // When - var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1); + var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1, TestContext.Current.CancellationToken); // Then Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); @@ -1392,7 +1389,7 @@ public async Task SerializeUnevaluatedPropertiesBooleanFalseEmitsInV31() }; // When - var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1); + var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1, TestContext.Current.CancellationToken); // Then Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); @@ -1414,7 +1411,7 @@ public async Task SerializeUnevaluatedPropertiesSchemaEmits(OpenApiSpecVersion v }; // When - var actual = await schema.SerializeAsJsonAsync(version); + var actual = await schema.SerializeAsJsonAsync(version, TestContext.Current.CancellationToken); // Then Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); @@ -1435,7 +1432,7 @@ public async Task SerializeUnevaluatedPropertiesSchemaTakesPrecedenceOverBoolean }; // When - var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1); + var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1, TestContext.Current.CancellationToken); // Then Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); @@ -1452,7 +1449,7 @@ public async Task SerializeUnevaluatedPropertiesAsExtensionInEarlierVersions(Ope UnevaluatedProperties = false }; - var actual = await schema.SerializeAsJsonAsync(version); + var actual = await schema.SerializeAsJsonAsync(version, TestContext.Current.CancellationToken); Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); } @@ -1471,7 +1468,7 @@ public async Task SerializeUnevaluatedPropertiesSchemaAsExtensionInEarlierVersio } }; - var actual = await schema.SerializeAsJsonAsync(version); + var actual = await schema.SerializeAsJsonAsync(version, TestContext.Current.CancellationToken); Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); } @@ -1489,7 +1486,7 @@ public async Task SerializeUnevaluatedPropertiesTrueNotEmittedInEarlierVersions( }; // When - var actual = await schema.SerializeAsJsonAsync(version); + var actual = await schema.SerializeAsJsonAsync(version, TestContext.Current.CancellationToken); // Then Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); @@ -1544,7 +1541,7 @@ public async Task SerializeMissingPropertiesEmitsJsonSchemaKeywordsInV31() Else = new OpenApiSchema { MaxProperties = 0 } }; - var actual = JsonNode.Parse(await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1)); + var actual = JsonNode.Parse(await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1, TestContext.Current.CancellationToken)); Assert.True(JsonNode.DeepEquals(expected, actual)); } @@ -1606,7 +1603,7 @@ public async Task SerializeMissingPropertiesEmitsOaiExtensionsInV3() Else = new OpenApiSchema { MaxProperties = 0 } }; - var actual = JsonNode.Parse(await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0)); + var actual = JsonNode.Parse(await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken)); Assert.True(JsonNode.DeepEquals(expected, actual)); } @@ -1629,7 +1626,7 @@ public async Task SerializeUnevaluatedPropertiesFalseNotEmittedForNonObjectType( }; // When - var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1); + var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1, TestContext.Current.CancellationToken); // Then Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); @@ -1653,7 +1650,7 @@ public async Task SerializeUnevaluatedPropertiesSchemaNotEmittedForNonObjectType }; // When - var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1); + var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1, TestContext.Current.CancellationToken); // Then Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); @@ -1674,7 +1671,7 @@ public async Task SerializeUnevaluatedPropertiesNotEmittedAsExtensionForNonObjec }; // When - var actual = await schema.SerializeAsJsonAsync(version); + var actual = await schema.SerializeAsJsonAsync(version, TestContext.Current.CancellationToken); // Then - should not contain unevaluatedProperties extension var parsed = JsonNode.Parse(actual)!.AsObject(); @@ -1693,7 +1690,7 @@ public async Task SerializeUnevaluatedPropertiesFalseStillEmittedForObjectType() }; // When - var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1); + var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1, TestContext.Current.CancellationToken); // Then Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); @@ -1710,7 +1707,7 @@ public async Task SerializeUnevaluatedPropertiesFalseStillEmittedWhenTypeNotSet( }; // When - var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1); + var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1, TestContext.Current.CancellationToken); // Then Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); @@ -1733,7 +1730,7 @@ public async Task SerializePatternPropertiesAsKeywordInV31AndV32(OpenApiSpecVers }; // When - var actual = await schema.SerializeAsJsonAsync(version); + var actual = await schema.SerializeAsJsonAsync(version, TestContext.Current.CancellationToken); // Then Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); @@ -1755,7 +1752,7 @@ public async Task SerializePatternPropertiesAsExtensionInEarlierVersions(OpenApi }; // When - var actual = await schema.SerializeAsJsonAsync(version); + var actual = await schema.SerializeAsJsonAsync(version, TestContext.Current.CancellationToken); // Then Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); @@ -1779,7 +1776,7 @@ public async Task SerializeSingleExampleAsExampleInV2V3WhenExampleUnset(OpenApiS ] }; - var actual = await schema.SerializeAsJsonAsync(version); + var actual = await schema.SerializeAsJsonAsync(version, TestContext.Current.CancellationToken); Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); } @@ -1806,7 +1803,7 @@ public async Task SerializeMultipleExamplesInV2V3WhenExampleUnset(OpenApiSpecVer ] }; - var actual = await schema.SerializeAsJsonAsync(version); + var actual = await schema.SerializeAsJsonAsync(version, TestContext.Current.CancellationToken); Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); } @@ -1833,7 +1830,7 @@ public async Task SerializeExamplesAsJsonSchemaKeywordInV31AndLater(OpenApiSpecV ] }; - var actual = await schema.SerializeAsJsonAsync(version); + var actual = await schema.SerializeAsJsonAsync(version, TestContext.Current.CancellationToken); Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); } @@ -1851,7 +1848,7 @@ public async Task SerializeEmptyPatternPropertiesNotEmittedInEarlierVersions(Ope }; // When - var actual = await schema.SerializeAsJsonAsync(version); + var actual = await schema.SerializeAsJsonAsync(version, TestContext.Current.CancellationToken); // Then Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); @@ -2050,7 +2047,7 @@ public async Task SerializeNullableEnumWith3_0() // Otherwise, validators will consider null as invalid even if nullable is set to true. // It's unclear if it's an issue of the validators or not, but it's safer to do it that way. var schema = CreateNullableEnumSchema(); - var result = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var result = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); var expected = """ { "oneOf": [ @@ -2079,7 +2076,7 @@ public async Task SerializeNullableEnumWith3_0() public async Task SerializeNullableEnumWith3_1_And_Later(OpenApiSpecVersion version) { var schema = CreateNullableEnumSchema(); - var result = await schema.SerializeAsJsonAsync(version); + var result = await schema.SerializeAsJsonAsync(version, TestContext.Current.CancellationToken); var expected = """ { "oneOf": [ @@ -2102,7 +2099,7 @@ public async Task SerializeNullableEnumWith3_1_And_Later(OpenApiSpecVersion vers public async Task SerializeNullableTypeWith3_0() { var schema = CreateTypeNullSchema(); - var result = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var result = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); var expected = """ { "enum": [ @@ -2121,7 +2118,7 @@ public async Task SerializeNullableTypeWith3_0() public async Task SerializeNullableTypeWith3_1_And_Later(OpenApiSpecVersion version) { var schema = CreateTypeNullSchema(); - var result = await schema.SerializeAsJsonAsync(version); + var result = await schema.SerializeAsJsonAsync(version, TestContext.Current.CancellationToken); var expected = """ { "type": "null" @@ -2137,7 +2134,7 @@ public async Task SerializeMultipleTypesWith3_0_NotIncludingNull_NotIncludingOne { Type = JsonSchemaType.String | JsonSchemaType.Integer, }; - var result = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var result = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); var expected = """ { "anyOf": [ @@ -2158,7 +2155,7 @@ public async Task SerializeMultipleTypesWith3_0_NotIncludingNull_NotIncludingOne Assert.Null(deserializedSchema.AnyOf); Assert.Equal(schema.Type, deserializedSchema.Type); - var actual32 = await deserializedSchema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2); + var actual32 = await deserializedSchema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2, TestContext.Current.CancellationToken); var expected32 = """ { "type": [ "integer", "string" ] @@ -2185,7 +2182,7 @@ public async Task SerializeMultipleTypesWith3_0_NotIncludingNull_IncludingOneOfB }, ], }; - var result = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var result = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); var expected = """ { "anyOf": [ @@ -2214,7 +2211,7 @@ public async Task SerializeMultipleTypesWith3_0_NotIncludingNull_IncludingOneOfB Assert.Null(deserializedSchema.AnyOf); Assert.Equal(schema.Type, deserializedSchema.Type); - var actual32 = await deserializedSchema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2); + var actual32 = await deserializedSchema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2, TestContext.Current.CancellationToken); var expected32 = """ { "type": [ "integer", "string" ], @@ -2249,7 +2246,7 @@ public async Task SerializeMultipleTypesWith3_0_NotIncludingNull_IncludingAnyOfB }, ], }; - var result = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var result = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); var expected = """ { "oneOf": [ @@ -2278,7 +2275,7 @@ public async Task SerializeMultipleTypesWith3_0_NotIncludingNull_IncludingAnyOfB Assert.NotNull(deserializedSchema.AnyOf); Assert.Equal(schema.Type, deserializedSchema.Type); - var actual32 = await deserializedSchema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2); + var actual32 = await deserializedSchema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2, TestContext.Current.CancellationToken); var expected32 = """ { "type": [ "integer", "string" ], @@ -2324,7 +2321,7 @@ public async Task SerializeMultipleTypesWith3_0_NotIncludingNull_IncludingBothOn }, ], }; - var result = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var result = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); var expected = """ { "anyOf": [ @@ -2353,7 +2350,7 @@ public async Task SerializeMultipleTypesWith3_0_NotIncludingNull_IncludingBothOn Assert.NotNull(deserializedSchema.AnyOf); Assert.Null(deserializedSchema.Type); - var actual32 = await deserializedSchema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2); + var actual32 = await deserializedSchema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2, TestContext.Current.CancellationToken); var expected32 = """ { "anyOf": [ @@ -2384,7 +2381,7 @@ public async Task SerializeMultipleTypesWith3_0_IncludingNullAndSingleType_NotIn { Type = JsonSchemaType.String | JsonSchemaType.Null, }; - var result = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var result = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); var expected = """ { "type": "string", @@ -2399,7 +2396,7 @@ public async Task SerializeMultipleTypesWith3_0_IncludingNullAndSingleType_NotIn Assert.Null(deserializedSchema.AnyOf); Assert.Equal(schema.Type, deserializedSchema.Type); - var actual32 = await deserializedSchema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2); + var actual32 = await deserializedSchema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2, TestContext.Current.CancellationToken); var expected32 = """ { "type": [ "null", "string" ] @@ -2426,7 +2423,7 @@ public async Task SerializeMultipleTypesWith3_0_IncludingNullAndSingleType_Inclu }, ], }; - var result = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var result = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); var expected = """ { "type": "string", @@ -2449,7 +2446,7 @@ public async Task SerializeMultipleTypesWith3_0_IncludingNullAndSingleType_Inclu Assert.Null(deserializedSchema.AnyOf); Assert.Equal(schema.Type, deserializedSchema.Type); - var actual32 = await deserializedSchema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2); + var actual32 = await deserializedSchema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2, TestContext.Current.CancellationToken); var expected32 = """ { "type": [ "null", "string" ], @@ -2484,7 +2481,7 @@ public async Task SerializeMultipleTypesWith3_0_IncludingNullAndSingleType_Inclu }, ], }; - var result = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var result = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); var expected = """ { "type": "string", @@ -2507,7 +2504,7 @@ public async Task SerializeMultipleTypesWith3_0_IncludingNullAndSingleType_Inclu Assert.NotNull(deserializedSchema.AnyOf); Assert.Equal(schema.Type, deserializedSchema.Type); - var actual32 = await deserializedSchema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2); + var actual32 = await deserializedSchema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2, TestContext.Current.CancellationToken); var expected32 = """ { "type": [ "null", "string" ], @@ -2553,7 +2550,7 @@ public async Task SerializeMultipleTypesWith3_0_IncludingNullAndSingleType_Inclu }, ], }; - var result = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var result = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); var expected = """ { "type": "string", @@ -2584,7 +2581,7 @@ public async Task SerializeMultipleTypesWith3_0_IncludingNullAndSingleType_Inclu Assert.NotNull(deserializedSchema.AnyOf); Assert.Equal(schema.Type, deserializedSchema.Type); - var actual32 = await deserializedSchema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2); + var actual32 = await deserializedSchema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2, TestContext.Current.CancellationToken); var expected32 = """ { "type": [ "null", "string" ], @@ -2616,7 +2613,7 @@ public async Task SerializeMultipleTypesWith3_0_IncludingNullAndTwoTypes_NotIncl { Type = JsonSchemaType.String | JsonSchemaType.Integer | JsonSchemaType.Null, }; - var result = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var result = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); var expected = """ { "anyOf": [ @@ -2644,7 +2641,7 @@ public async Task SerializeMultipleTypesWith3_0_IncludingNullAndTwoTypes_NotIncl Assert.Null(deserializedSchema.AnyOf); Assert.Equal(schema.Type, deserializedSchema.Type); - var actual32 = await deserializedSchema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2); + var actual32 = await deserializedSchema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2, TestContext.Current.CancellationToken); var expected32 = """ { "type": [ "null", "integer", "string" ] @@ -2671,7 +2668,7 @@ public async Task SerializeMultipleTypesWith3_0_IncludingNullAndTwoTypes_Includi }, ], }; - var result = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var result = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); var expected = """ { "anyOf": [ @@ -2707,7 +2704,7 @@ public async Task SerializeMultipleTypesWith3_0_IncludingNullAndTwoTypes_Includi Assert.Null(deserializedSchema.AnyOf); Assert.Equal(schema.Type, deserializedSchema.Type); - var actual32 = await deserializedSchema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2); + var actual32 = await deserializedSchema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2, TestContext.Current.CancellationToken); var expected32 = """ { "type": [ "null", "integer", "string" ], @@ -2742,7 +2739,7 @@ public async Task SerializeMultipleTypesWith3_0_IncludingNullAndTwoTypes_Includi }, ], }; - var result = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var result = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); var expected = """ { "oneOf": [ @@ -2778,7 +2775,7 @@ public async Task SerializeMultipleTypesWith3_0_IncludingNullAndTwoTypes_Includi Assert.NotNull(deserializedSchema.AnyOf); Assert.Equal(schema.Type, deserializedSchema.Type); - var actual32 = await deserializedSchema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2); + var actual32 = await deserializedSchema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2, TestContext.Current.CancellationToken); var expected32 = """ { "type": [ "null", "integer", "string" ], @@ -2824,7 +2821,7 @@ public async Task SerializeMultipleTypesWith3_0_IncludingNullAndTwoTypes_Includi }, ], }; - var result = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var result = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, cancellationToken: TestContext.Current.CancellationToken); var expected = """ { "anyOf": [ @@ -2854,7 +2851,7 @@ public async Task SerializeMultipleTypesWith3_0_IncludingNullAndTwoTypes_Includi Assert.NotNull(deserializedSchema.AnyOf); Assert.Null(deserializedSchema.Type); - var actual32 = await deserializedSchema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2); + var actual32 = await deserializedSchema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2, cancellationToken: TestContext.Current.CancellationToken); var expected32 = """ { "anyOf": [ diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaV30CompatibilityTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaV30CompatibilityTests.cs index 1c272f837..ae03dcfb2 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaV30CompatibilityTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiSchemaV30CompatibilityTests.cs @@ -49,7 +49,7 @@ public async Task NullableEnumShouldRoundTripCorrectly() ] }; - var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); var expected = """ @@ -78,7 +78,7 @@ public async Task TypeNullAloneAsV3ShouldRoundTripCorrectly() { var schema = new OpenApiSchema { Type = JsonSchemaType.Null }; - var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); var expected = """ @@ -102,7 +102,7 @@ public async Task NullableTypeAsV3ShouldRoundTripCorrectly() { var schema = new OpenApiSchema { Type = JsonSchemaType.String | JsonSchemaType.Null }; - var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); var expected = """ @@ -122,7 +122,7 @@ public async Task SerializeMultipleNonNullTypesAsV3DoesNotOmitType() { var schema = new OpenApiSchema { Type = JsonSchemaType.String | JsonSchemaType.Integer }; - var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); var expected = """ { @@ -151,7 +151,7 @@ public async Task SerializeMultipleNonNullTypesWithNullAsV3DoesNotOmitType() Type = JsonSchemaType.String | JsonSchemaType.Integer | JsonSchemaType.Null }; - var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); var expected = """ { @@ -221,7 +221,7 @@ public async Task SerializeConstAsV3EmitsSingleValueEnum() { var schema = new OpenApiSchema { Type = JsonSchemaType.String, Const = "foo" }; - var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); var node = JsonNode.Parse(actual)!.AsObject(); Assert.False(node.ContainsKey("const")); @@ -245,7 +245,7 @@ public async Task SerializeExclusiveMaximumAsV3EmitsMaximumWithBooleanFlag() ExclusiveMaximum = "5" }; - var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); var expected = """ @@ -272,7 +272,7 @@ public async Task SerializeExclusiveMinimumAsV3EmitsMinimumWithBooleanFlag() ExclusiveMinimum = "1" }; - var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await schema.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); var expected = """ diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiSecurityRequirementTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiSecurityRequirementTests.cs index 29f5af3c6..47edf9aa9 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiSecurityRequirementTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiSecurityRequirementTests.cs @@ -6,7 +6,6 @@ using System.Globalization; using System.IO; using System.Threading.Tasks; -using FluentAssertions; using VerifyXunit; using Xunit; using System.Text.Json.Nodes; @@ -105,7 +104,7 @@ public async Task SerializeBasicSecurityRequirementAsV3JsonWorks() var expected = @"{ }"; // Act - var actual = await BasicSecurityRequirement.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await BasicSecurityRequirement.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -124,7 +123,7 @@ public async Task SerializeSecurityRequirementAsV3JsonWorksAsync(bool produceTer // Act SecurityRequirementWithReferencedSecurityScheme.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -153,7 +152,7 @@ public async Task SerializeSecurityRequirementWithReferencedSecuritySchemeAsJson """; // Act - var actual = await SecurityRequirementWithReferencedSecurityScheme.SerializeAsJsonAsync(openApiSpecVersion); + var actual = await SecurityRequirementWithReferencedSecurityScheme.SerializeAsJsonAsync(openApiSpecVersion, TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); @@ -178,7 +177,7 @@ public async Task SerializeSecurityRequirementWithUnreferencedSecuritySchemeAsJs """; // Act - var actual = await SecurityRequirementWithUnreferencedSecurityScheme.SerializeAsJsonAsync(openApiSpecVersion); + var actual = await SecurityRequirementWithUnreferencedSecurityScheme.SerializeAsJsonAsync(openApiSpecVersion, TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); @@ -234,14 +233,15 @@ public void SchemesShouldConsiderOnlyReferenceIdForEquality() Assert.Equal(2, securityRequirement.Count); - securityRequirement.Should().BeEquivalentTo( + OpenApiTestAssert.Equivalent( new OpenApiSecurityRequirement { // This should work with any security scheme object // as long as Reference.Id os securityScheme1 [new OpenApiSecuritySchemeReference("securityScheme1", null)] = new List(), [new OpenApiSecuritySchemeReference("securityScheme2", null)] = new List { "scope1", "scope2" }, - }); + }, + securityRequirement); } } } diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiSecuritySchemeTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiSecuritySchemeTests.cs index 31fe34ff4..9089d580c 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiSecuritySchemeTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiSecuritySchemeTests.cs @@ -158,7 +158,7 @@ public async Task SerializeApiKeySecuritySchemeAsV3JsonWorks() """; // Act - var actual = await ApiKeySecurityScheme.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await ApiKeySecurityScheme.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -179,7 +179,7 @@ public async Task SerializeApiKeySecuritySchemeAsV3YamlWorks() """; // Act - var actual = await ApiKeySecurityScheme.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await ApiKeySecurityScheme.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -275,7 +275,7 @@ public async Task SerializeHttpSecuritySchemeWithoutSchemeAsV2DoesNotThrow() }; // Act - var actual = await securityScheme.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0); + var actual = await securityScheme.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0, TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse("{}"), JsonNode.Parse(actual))); @@ -330,7 +330,7 @@ public async Task SerializeOpenIdConnectSecuritySchemeWithoutOpenIdConnectUrlAsV }; // Act - var actual = await securityScheme.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0); + var actual = await securityScheme.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi2_0, TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse("{}"), JsonNode.Parse(actual))); @@ -350,7 +350,7 @@ public async Task SerializeHttpBasicSecuritySchemeAsV3JsonWorks() """; // Act - var actual = await HttpBasicSecurityScheme.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await HttpBasicSecurityScheme.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -373,7 +373,7 @@ public async Task SerializeHttpBearerSecuritySchemeAsV3JsonWorks() """; // Act - var actual = await HttpBearerSecurityScheme.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await HttpBearerSecurityScheme.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -416,7 +416,7 @@ public async Task SerializeOAuthSingleFlowSecuritySchemeAsV3JsonWorks() """; // Act - var actual = await OAuth2SingleFlowSecurityScheme.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await OAuth2SingleFlowSecurityScheme.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -446,7 +446,7 @@ public async Task SerializeOAuthSecuritySchemeWithMetadataUrlAsV32JsonWorks() """; // Act - var actual = await OAuth2MetadataSecurityScheme.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2); + var actual = await OAuth2MetadataSecurityScheme.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2, TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); @@ -474,7 +474,7 @@ public async Task SerializeOAuthSecuritySchemeWithMetadataUrlAsV31JsonOmitsMetad """; // Act - var actual = await OAuth2MetadataSecurityScheme.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1); + var actual = await OAuth2MetadataSecurityScheme.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1, TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); @@ -518,7 +518,7 @@ public async Task SerializeOAuthMultipleFlowSecuritySchemeAsV3JsonWorks() """; // Act - var actual = await OAuth2MultipleFlowSecurityScheme.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await OAuth2MultipleFlowSecurityScheme.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -540,7 +540,7 @@ public async Task SerializeOpenIdConnectSecuritySchemeAsV3JsonWorks() """; // Act - var actual = await OpenIdConnectSecurityScheme.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await OpenIdConnectSecurityScheme.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -559,7 +559,7 @@ public async Task SerializeReferencedSecuritySchemeAsV3JsonWorksAsync(bool produ // Act OpenApiSecuritySchemeReference.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -576,7 +576,7 @@ public async Task SerializeReferencedSecuritySchemeAsV3JsonWithoutReferenceWorks // Act ReferencedSecurityScheme.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -598,7 +598,7 @@ public async Task SerializeDeprecatedSecuritySchemeAsV32JsonWorks() """; // Act - var actual = await DeprecatedApiKeySecurityScheme.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2); + var actual = await DeprecatedApiKeySecurityScheme.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2, TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); @@ -620,7 +620,7 @@ public async Task SerializeDeprecatedSecuritySchemeAsV31JsonWorks() """; // Act - var actual = await DeprecatedApiKeySecurityScheme.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1); + var actual = await DeprecatedApiKeySecurityScheme.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1, TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); @@ -642,7 +642,7 @@ public async Task SerializeDeprecatedSecuritySchemeAsV3JsonWorks() """; // Act - var actual = await DeprecatedApiKeySecurityScheme.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await DeprecatedApiKeySecurityScheme.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiServerTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiServerTests.cs index 7af4adbe4..1fa974263 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiServerTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiServerTests.cs @@ -65,7 +65,7 @@ public async Task SerializeBasicServerAsV3JsonWorks() """; // Act - var actual = await BasicServer.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await BasicServer.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -103,7 +103,7 @@ public async Task SerializeAdvancedServerAsV3JsonWorks() """; // Act - var actual = await AdvancedServer.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await AdvancedServer.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -125,7 +125,7 @@ public async Task SerializeServerWithNameAsV32JsonWorks() """; // Act - var actual = await ServerWithName.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2); + var actual = await ServerWithName.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2, TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); @@ -145,7 +145,7 @@ public async Task SerializeServerWithNameAsV31JsonWorks() """; // Act - var actual = await ServerWithName.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1); + var actual = await ServerWithName.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1, TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); @@ -165,7 +165,7 @@ public async Task SerializeServerWithNameAsV3JsonWorks() """; // Act - var actual = await ServerWithName.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await ServerWithName.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(expected), JsonNode.Parse(actual))); diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiServerVariableTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiServerVariableTests.cs index ee9a81826..4770d34d9 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiServerVariableTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiServerVariableTests.cs @@ -28,7 +28,7 @@ public class OpenApiServerVariableTests public async Task SerializeBasicServerVariableAsV3Works(string format, string expected) { // Arrange & Act - var actual = await BasicServerVariable.SerializeAsync(OpenApiSpecVersion.OpenApi3_0, format); + var actual = await BasicServerVariable.SerializeAsync(OpenApiSpecVersion.OpenApi3_0, format, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -53,7 +53,7 @@ public async Task SerializeAdvancedServerVariableAsV3JsonWorks() """; // Act - var actual = await AdvancedServerVariable.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await AdvancedServerVariable.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -75,7 +75,7 @@ public async Task SerializeAdvancedServerVariableAsV3YamlWorks() """; // Act - var actual = await AdvancedServerVariable.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0); + var actual = await AdvancedServerVariable.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_0, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiTagTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiTagTests.cs index 0a7ea4a84..946bac75b 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiTagTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiTagTests.cs @@ -49,7 +49,7 @@ public async Task SerializeBasicTagAsV3JsonWithoutReferenceWorksAsync(bool produ // Act BasicTag.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -66,7 +66,7 @@ public async Task SerializeBasicTagAsV2JsonWithoutReferenceWorksAsync(bool produ // Act BasicTag.SerializeAsV2(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -100,7 +100,7 @@ public async Task SerializeBasicTagAsV2YamlWithoutReferenceWorks() // Act BasicTag.SerializeAsV2(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); var actual = outputStringWriter.GetStringBuilder().ToString(); // Assert @@ -127,7 +127,7 @@ public async Task SerializeAdvancedTagAsV3YamlWithoutReferenceWorks() // Act AdvancedTag.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); var actual = outputStringWriter.GetStringBuilder().ToString(); // Assert @@ -154,7 +154,7 @@ public async Task SerializeAdvancedTagAsV2YamlWithoutReferenceWorks() // Act AdvancedTag.SerializeAsV2(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); var actual = outputStringWriter.GetStringBuilder().ToString(); // Assert @@ -174,7 +174,7 @@ public async Task SerializeAdvancedTagAsV3JsonWorksAsync(bool produceTerseOutput // Act AdvancedTag.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -191,7 +191,7 @@ public async Task SerializeAdvancedTagAsV2JsonWorksAsync(bool produceTerseOutput // Act AdvancedTag.SerializeAsV2(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -213,7 +213,7 @@ public async Task SerializeAdvancedTagAsV3YamlWorks() // Act AdvancedTag.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); var actual = outputStringWriter.GetStringBuilder().ToString(); // Assert @@ -238,7 +238,7 @@ public async Task SerializeAdvancedTagAsV2YamlWorks() // Act AdvancedTag.SerializeAsV2(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); var actual = outputStringWriter.GetStringBuilder().ToString(); // Assert @@ -258,7 +258,7 @@ public async Task SerializeReferencedTagAsV3JsonWorksAsync(bool produceTerseOutp // Act ReferencedTag.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -275,7 +275,7 @@ public async Task SerializeReferencedTagAsV2JsonWorksAsync(bool produceTerseOutp // Act ReferencedTag.SerializeAsV2(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -292,7 +292,7 @@ public async Task SerializeReferencedTagAsV3YamlWorks() // Act ReferencedTag.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); var actual = outputStringWriter.GetStringBuilder().ToString(); // Assert @@ -312,7 +312,7 @@ public async Task SerializeReferencedTagAsV2YamlWorks() // Act ReferencedTag.SerializeAsV2(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); var actual = outputStringWriter.GetStringBuilder().ToString(); // Assert @@ -333,7 +333,7 @@ public async Task SerializeTagWithV32PropertiesAsV31JsonWorksAsync(bool produceT // Act TagWithV32Properties.SerializeAsV31(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -356,7 +356,7 @@ public async Task SerializeTagWithV32PropertiesAsV31YamlWorks() // Act TagWithV32Properties.SerializeAsV31(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); var actual = outputStringWriter.GetStringBuilder().ToString(); // Assert @@ -377,7 +377,7 @@ public async Task SerializeTagWithV32PropertiesAsV32JsonWorksAsync(bool produceT // Act TagWithV32Properties.SerializeAsV32(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -400,7 +400,7 @@ public async Task SerializeTagWithV32PropertiesAsV32YamlWorks() // Act TagWithV32Properties.SerializeAsV32(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); var actual = outputStringWriter.GetStringBuilder().ToString(); // Assert @@ -427,7 +427,7 @@ public async Task SerializeTagWithV32PropertiesAsV3YamlWorks() // Act TagWithV32Properties.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); var actual = outputStringWriter.GetStringBuilder().ToString(); // Assert diff --git a/test/Microsoft.OpenApi.Tests/Models/OpenApiXmlTests.cs b/test/Microsoft.OpenApi.Tests/Models/OpenApiXmlTests.cs index 6426c0237..758615cc2 100644 --- a/test/Microsoft.OpenApi.Tests/Models/OpenApiXmlTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/OpenApiXmlTests.cs @@ -56,7 +56,7 @@ public async Task SerializeBasicXmlWorks( string format) { // Act - var actual = await BasicXml.SerializeAsync(version, format); + var actual = await BasicXml.SerializeAsync(version, format, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -81,7 +81,7 @@ public async Task SerializeAdvancedXmlAsJsonWorks(OpenApiSpecVersion version) """; // Act - var actual = await AdvancedXml.SerializeAsJsonAsync(version); + var actual = await AdvancedXml.SerializeAsJsonAsync(version, TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(actual), JsonNode.Parse(expected))); @@ -103,7 +103,7 @@ public async Task SerializeAdvancedXmlAsYamlWorks(OpenApiSpecVersion version) """; // Act - var actual = await AdvancedXml.SerializeAsYamlAsync(version); + var actual = await AdvancedXml.SerializeAsYamlAsync(version, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -127,7 +127,7 @@ public async Task SerializeXmlWithNodeTypeAsJsonV32Works() """; // Act - var actual = await XmlWithNodeType.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2); + var actual = await XmlWithNodeType.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2, TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(actual), JsonNode.Parse(expected))); @@ -147,7 +147,7 @@ public async Task SerializeXmlWithNodeTypeAsYamlV32Works() """; // Act - var actual = await XmlWithNodeType.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_2); + var actual = await XmlWithNodeType.SerializeAsYamlAsync(OpenApiSpecVersion.OpenApi3_2, TestContext.Current.CancellationToken); // Assert actual = actual.MakeLineBreaksEnvironmentNeutral(); @@ -168,7 +168,7 @@ public async Task SerializeXmlWithAttributeNodeTypeAsJsonV32Works() """; // Act - var actual = await XmlWithAttributeNodeType.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2); + var actual = await XmlWithAttributeNodeType.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2, TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(actual), JsonNode.Parse(expected))); @@ -190,7 +190,7 @@ public async Task SerializeXmlWithNodeTypeAsJsonV31DoesNotSerializeNodeType() """; // Act - var actual = await XmlWithNodeType.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1); + var actual = await XmlWithNodeType.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1, TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(actual), JsonNode.Parse(expected))); @@ -209,7 +209,7 @@ public async Task SerializeXmlWithAttributeNodeTypeAsJsonV31DoesNotSerializeNode """; // Act - var actual = await XmlWithAttributeNodeType.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1); + var actual = await XmlWithAttributeNodeType.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_1, TestContext.Current.CancellationToken); // Assert Assert.True(JsonNode.DeepEquals(JsonNode.Parse(actual), JsonNode.Parse(expected))); diff --git a/test/Microsoft.OpenApi.Tests/Models/References/BaseOpenApiReferenceHolderTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/BaseOpenApiReferenceHolderTests.cs new file mode 100644 index 000000000..24d2b038e --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/Models/References/BaseOpenApiReferenceHolderTests.cs @@ -0,0 +1,116 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; +using System.Collections.Generic; +using System.IO; +using Xunit; + +namespace Microsoft.OpenApi.Tests.Models.References +{ + public class BaseOpenApiReferenceHolderTests + { + [Fact] + public void DelegatedAccessDetectsCyclesForEveryComponentReferenceType() + { + AssertCircularAccessThrows(document => + { + document.AddComponent("A", new OpenApiCallbackReference("B", document)); + document.AddComponent("B", new OpenApiCallbackReference("A", document)); + return document.Components!.Callbacks!["A"].PathItems; + }); + AssertCircularAccessThrows(document => + { + document.AddComponent("A", new OpenApiExampleReference("B", document)); + document.AddComponent("B", new OpenApiExampleReference("A", document)); + return document.Components!.Examples!["A"].Description; + }); + AssertCircularAccessThrows(document => + { + document.AddComponent("A", new OpenApiHeaderReference("B", document)); + document.AddComponent("B", new OpenApiHeaderReference("A", document)); + return document.Components!.Headers!["A"].Description; + }); + AssertCircularAccessThrows(document => + { + document.AddComponent("A", new OpenApiLinkReference("B", document)); + document.AddComponent("B", new OpenApiLinkReference("A", document)); + return document.Components!.Links!["A"].Description; + }); + AssertCircularAccessThrows(document => + { + document.Components = new OpenApiComponents + { + MediaTypes = new Dictionary + { + ["A"] = new OpenApiMediaTypeReference("B", document), + ["B"] = new OpenApiMediaTypeReference("A", document) + } + }; + document.RegisterComponents(); + return document.Components.MediaTypes["A"].Schema; + }); + AssertCircularAccessThrows(document => + { + document.AddComponent("A", new OpenApiParameterReference("B", document)); + document.AddComponent("B", new OpenApiParameterReference("A", document)); + return document.Components!.Parameters!["A"].Description; + }); + AssertCircularAccessThrows(document => + { + document.AddComponent("A", new OpenApiPathItemReference("B", document)); + document.AddComponent("B", new OpenApiPathItemReference("A", document)); + return document.Components!.PathItems!["A"].Description; + }); + AssertCircularAccessThrows(document => + { + document.AddComponent("A", new OpenApiRequestBodyReference("B", document)); + document.AddComponent("B", new OpenApiRequestBodyReference("A", document)); + return document.Components!.RequestBodies!["A"].Description; + }); + AssertCircularAccessThrows(document => + { + document.AddComponent("A", new OpenApiResponseReference("B", document)); + document.AddComponent("B", new OpenApiResponseReference("A", document)); + return document.Components!.Responses!["A"].Description; + }); + AssertCircularAccessThrows(document => + { + document.AddComponent("A", new OpenApiSchemaReference("B", document)); + document.AddComponent("B", new OpenApiSchemaReference("A", document)); + return document.Components!.Schemas!["A"].Description; + }); + AssertCircularAccessThrows(document => + { + document.AddComponent("A", new OpenApiSecuritySchemeReference("B", document)); + document.AddComponent("B", new OpenApiSecuritySchemeReference("A", document)); + return document.Components!.SecuritySchemes!["A"].Description; + }); + } + + [Fact] + public void InliningACircularNonSchemaReferenceThrows() + { + // Arrange + var document = new OpenApiDocument(); + document.AddComponent("A", new OpenApiResponseReference("B", document)); + document.AddComponent("B", new OpenApiResponseReference("A", document)); + var response = Assert.IsType(document.Components!.Responses!["A"]); + var writer = new OpenApiJsonWriter( + new StringWriter(), + new OpenApiWriterSettings { InlineLocalReferences = true }); + + // Act + var exception = Assert.Throws(() => response.SerializeAsV31(writer)); + + // Assert + Assert.Contains("Circular reference detected while resolving reference:", exception.Message, StringComparison.Ordinal); + } + + private static void AssertCircularAccessThrows(Func access) + { + var exception = Assert.Throws(() => access(new OpenApiDocument())); + Assert.Contains("Circular reference detected while resolving reference:", exception.Message, StringComparison.Ordinal); + } + } +} diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.cs index c39db4c13..6c6f42665 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiCallbackReferenceTests.cs @@ -166,7 +166,7 @@ public async Task SerializeCallbackReferenceAsV3JsonWorks(bool produceTerseOutpu // Act _externalCallbackReference.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput, inlineLocalReferences); @@ -185,7 +185,7 @@ public async Task SerializeCallbackReferenceAsV31JsonWorks(bool produceTerseOutp // Act _externalCallbackReference.SerializeAsV31(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput, inlineLocalReferences); diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.cs index bd40056a4..98f352da7 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiExampleReferenceTests.cs @@ -156,7 +156,7 @@ public async Task SerializeExampleReferenceAsV3JsonWorks(bool produceTerseOutput // Act _localExampleReference.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput, inlineLocalReferences); @@ -175,7 +175,7 @@ public async Task SerializeExampleReferenceAsV31JsonWorks(bool produceTerseOutpu // Act _localExampleReference.SerializeAsV31(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput, inlineLocalReferences); diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.cs index 7a5e7fc9c..4556c1bff 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiHeaderReferenceTests.cs @@ -116,7 +116,7 @@ public async Task SerializeHeaderReferenceAsV3JsonWorks(bool produceTerseOutput, // Act _localHeaderReference.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput, inlineLocalReferences); @@ -135,7 +135,7 @@ public async Task SerializeHeaderReferenceAsV31JsonWorks(bool produceTerseOutput // Act _localHeaderReference.SerializeAsV31(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput, inlineLocalReferences); @@ -154,7 +154,7 @@ public async Task SerializeHeaderReferenceAsV2JsonWorksAsync(bool produceTerseOu // Act _localHeaderReference.SerializeAsV2(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput, inlineLocalReferences); diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.cs index 33354a67c..f75dbb11b 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiLinkReferenceTests.cs @@ -164,7 +164,7 @@ public async Task SerializeLinkReferenceAsV3JsonWorks(bool produceTerseOutput, b // Act _localLinkReference.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput, inlineLocalReferences); @@ -183,7 +183,7 @@ public async Task SerializeLinkReferenceAsV31JsonWorks(bool produceTerseOutput, // Act _localLinkReference.SerializeAsV31(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput, inlineLocalReferences); diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiMediaTypeReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiMediaTypeReferenceTests.cs index 7292eacdc..d75f97a38 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiMediaTypeReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiMediaTypeReferenceTests.cs @@ -253,7 +253,7 @@ public async Task SerializeMediaTypeReferenceAsV32JsonWorks(bool produceTerseOut // Act document.SerializeAsV32(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput, inlineLocalReferences); @@ -318,7 +318,7 @@ public async Task SerializeMediaTypeReferenceAsV31JsonWorks(bool produceTerseOut // Act document.SerializeAsV31(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput, inlineLocalReferences); @@ -383,7 +383,7 @@ public async Task SerializeMediaTypeReferenceAsV3JsonWorks(bool produceTerseOutp // Act document.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput, inlineLocalReferences); diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.cs index 89f96c990..f682b012f 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiParameterReferenceTests.cs @@ -117,7 +117,7 @@ public async Task SerializeParameterReferenceAsV3JsonWorks(bool produceTerseOutp // Act _localParameterReference.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput, inlineLocalReferences); @@ -136,7 +136,7 @@ public async Task SerializeParameterReferenceAsV31JsonWorks(bool produceTerseOut // Act _localParameterReference.SerializeAsV31(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput, inlineLocalReferences); @@ -155,7 +155,7 @@ public async Task SerializeParameterReferenceAsV2JsonWorksAsync(bool produceTers // Act _localParameterReference.SerializeAsV2(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput, inlineLocalReferences); diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.cs index 5c70a1ffa..e06ebc683 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiPathItemReferenceTests.cs @@ -126,7 +126,7 @@ public async Task SerializePathItemReferenceAsV31JsonWorks(bool produceTerseOutp // Act _localPathItemReference.SerializeAsV31(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput, inlineLocalReferences); diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.cs index e3219e021..5991237ba 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiRequestBodyReferenceTests.cs @@ -130,7 +130,7 @@ public async Task SerializeRequestBodyReferenceAsV3JsonWorks(bool produceTerseOu // Act _localRequestBodyReference.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput, inlineLocalReferences); @@ -149,7 +149,7 @@ public async Task SerializeRequestBodyReferenceAsV31JsonWorks(bool produceTerseO // Act _localRequestBodyReference.SerializeAsV31(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput, inlineLocalReferences); diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiResponseReferenceTest.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiResponseReferenceTest.cs index b1e44a90d..63f272acc 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiResponseReferenceTest.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiResponseReferenceTest.cs @@ -112,7 +112,7 @@ public async Task SerializeResponseReferenceAsV3JsonWorks(bool produceTerseOutpu // Act _localResponseReference.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput, inlineLocalReferences); @@ -131,7 +131,7 @@ public async Task SerializeResponseReferenceAsV31JsonWorks(bool produceTerseOutp // Act _localResponseReference.SerializeAsV31(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput, inlineLocalReferences); diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.SerializeSchemaReferenceAsV2JsonWorks_produceTerseOutput=False.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.SerializeSchemaReferenceAsV2JsonWorks_produceTerseOutput=False.verified.txt index ddb324ad6..0af262f8e 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.SerializeSchemaReferenceAsV2JsonWorks_produceTerseOutput=False.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.SerializeSchemaReferenceAsV2JsonWorks_produceTerseOutput=False.verified.txt @@ -1,3 +1,3 @@ { "$ref": "#/definitions/Pet" -} +} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.SerializeSchemaReferenceAsV2JsonWorks_produceTerseOutput=True.verified.txt b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.SerializeSchemaReferenceAsV2JsonWorks_produceTerseOutput=True.verified.txt index b36112c01..d4b02da13 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.SerializeSchemaReferenceAsV2JsonWorks_produceTerseOutput=True.verified.txt +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.SerializeSchemaReferenceAsV2JsonWorks_produceTerseOutput=True.verified.txt @@ -1 +1 @@ -{"$ref":"#/definitions/Pet"} +{"$ref":"#/definitions/Pet"} \ No newline at end of file diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.cs index 8f6cb3107..1041a651f 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSchemaReferenceTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. +using System; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -314,7 +315,7 @@ public async Task SerializeSchemaReferenceAsV31JsonWorks(bool produceTerseOutput // Act reference.SerializeAsV31(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -346,7 +347,7 @@ public async Task SerializeSchemaReferenceAsV32JsonWorks(bool produceTerseOutput // Act reference.SerializeAsV32(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -378,7 +379,7 @@ public async Task SerializeSchemaReferenceAsV3JsonWorks(bool produceTerseOutput) // Act reference.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -410,7 +411,7 @@ public async Task SerializeSchemaReferenceAsV2JsonWorks(bool produceTerseOutput) // Act reference.SerializeAsV2(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -581,7 +582,7 @@ public async Task SchemaReferenceExtensionsNotWrittenInV30() // Act reference.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); var output = outputStringWriter.ToString(); // Assert: In v3.0, ONLY $ref should appear - no description, no extensions @@ -606,11 +607,309 @@ public async Task SchemaReferenceExtensionsNotWrittenInV2() // Act reference.SerializeAsV2(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); var output = outputStringWriter.ToString(); // Assert: In v2, ONLY $ref should appear - no description, no extensions Assert.Equal(@"{""$ref"":""#/definitions/Pet""}", output); } + + private const string CircularSchemaReferencePairJson = + """ + { + "openapi": "3.0.0", + "info": { "title": "Test", "version": "0.0.1" }, + "paths": {}, + "components": { + "schemas": { + "A": { "$ref": "#/components/schemas/B" }, + "B": { "$ref": "#/components/schemas/A" } + } + } + } + """; + + [Fact] + public void CircularSchemaReferencePairThrowsWhenReadingAllOf() + { + // Arrange + var readResult = OpenApiDocument.Parse(CircularSchemaReferencePairJson, "json"); + Assert.NotNull(readResult.Document); + var schemaA = readResult.Document.Components!.Schemas!["A"]; + + // Act + var exception = Assert.Throws(() => _ = schemaA.AllOf); + + // Assert + Assert.Contains("Circular reference detected while resolving reference:", exception.Message, StringComparison.Ordinal); + } + + [Fact] + public void CircularSchemaReferencePairTargetReturnsTheImmediateReference() + { + // Arrange + var readResult = OpenApiDocument.Parse(CircularSchemaReferencePairJson, "json"); + Assert.NotNull(readResult.Document); + var schemaA = Assert.IsType(readResult.Document.Components!.Schemas!["A"]); + var schemaB = readResult.Document.Components.Schemas["B"]; + + // Act + var target = schemaA.Target; + + // Assert + Assert.Same(schemaB, target); + } + + [Fact] + public void CircularSchemaReferencePairRecursiveTargetThrows() + { + // Arrange + var readResult = OpenApiDocument.Parse(CircularSchemaReferencePairJson, "json"); + Assert.NotNull(readResult.Document); + var schemaA = Assert.IsType(readResult.Document.Components!.Schemas!["A"]); + + // Act + var exception = Assert.Throws(() => _ = schemaA.RecursiveTarget); + + // Assert + Assert.Contains("Circular reference detected while resolving reference:", exception.Message, StringComparison.Ordinal); + } + + [Fact] + public void CircularSchemaReferencePairThrowsWhenReadingDescription() + { + // Arrange + var readResult = OpenApiDocument.Parse(CircularSchemaReferencePairJson, "json"); + Assert.NotNull(readResult.Document); + var schemaA = readResult.Document.Components!.Schemas!["A"]; + + // Act + var exception = Assert.Throws(() => _ = schemaA.Description); + + // Assert + Assert.Contains("Circular reference detected while resolving reference:", exception.Message, StringComparison.Ordinal); + } + + [Fact] + public void CircularSchemaReferenceChainOfThreeThrowsWhenReadingType() + { + // Arrange + var readResult = OpenApiDocument.Parse( + """ + { + "openapi": "3.0.0", + "info": { "title": "Test", "version": "0.0.1" }, + "paths": {}, + "components": { + "schemas": { + "A": { "$ref": "#/components/schemas/B" }, + "B": { "$ref": "#/components/schemas/C" }, + "C": { "$ref": "#/components/schemas/A" } + } + } + } + """, "json"); + Assert.NotNull(readResult.Document); + var schemaA = readResult.Document.Components!.Schemas!["A"]; + + // Act + var exception = Assert.Throws(() => _ = schemaA.Type); + + // Assert + Assert.Contains("Circular reference detected while resolving reference:", exception.Message, StringComparison.Ordinal); + } + + [Fact] + public async Task CircularSchemaReferencePairStillSerializesAsAReference() + { + // Arrange + var readResult = OpenApiDocument.Parse(CircularSchemaReferencePairJson, "json"); + Assert.NotNull(readResult.Document); + var outputStringWriter = new StringWriter(CultureInfo.InvariantCulture); + var writer = new OpenApiJsonWriter(outputStringWriter, new OpenApiJsonWriterSettings { Terse = true }); + + // Act + readResult.Document.SerializeAsV3(writer); + await writer.FlushAsync(TestContext.Current.CancellationToken); + var output = outputStringWriter.ToString(); + + // Assert + Assert.Contains(@"""A"":{""$ref"":""#/components/schemas/B""}", output, StringComparison.Ordinal); + Assert.Contains(@"""B"":{""$ref"":""#/components/schemas/A""}", output, StringComparison.Ordinal); + } + + [Fact] + public void SchemaReferenceChainThatTerminatesStillResolvesTheTarget() + { + // Arrange + var readResult = OpenApiDocument.Parse( + """ + { + "openapi": "3.0.0", + "info": { "title": "Test", "version": "0.0.1" }, + "paths": {}, + "components": { + "schemas": { + "A": { "$ref": "#/components/schemas/B" }, + "B": { "$ref": "#/components/schemas/C" }, + "C": { "type": "string", "description": "The concrete schema" } + } + } + } + """, "json"); + Assert.NotNull(readResult.Document); + var schemaA = readResult.Document.Components!.Schemas!["A"]; + + // Act + var description = schemaA.Description; + var repeatedDescription = schemaA.Description; + + // Assert + Assert.Equal("The concrete schema", description); + Assert.Equal("The concrete schema", repeatedDescription); + Assert.Equal(JsonSchemaType.String, schemaA.Type); + } + + [Fact] + public void DelegatedAccessResolvesEachReferenceOnce() + { + // Arrange + const int referenceCount = 100; + var references = Enumerable.Range(0, referenceCount) + .Select(index => new CountingSchemaReference($"S{index}")) + .ToArray(); + for (var index = 0; index < references.Length - 1; index++) + { + references[index].ImmediateTarget = references[index + 1]; + } + references[^1].ImmediateTarget = new OpenApiSchema { Description = "end" }; + + // Act + var description = references[0].Description; + + // Assert + Assert.Equal("end", description); + Assert.Equal(referenceCount, references.Sum(reference => reference.TargetAccessCount)); + } + + [Fact] + public void RecursiveTargetResolvesADeepChainIteratively() + { + // Arrange + const int referenceCount = 10_000; + var references = Enumerable.Range(0, referenceCount) + .Select(index => new CountingSchemaReference($"S{index}")) + .ToArray(); + var concreteTarget = new OpenApiSchema { Description = "end" }; + for (var index = 0; index < references.Length - 1; index++) + { + references[index].ImmediateTarget = references[index + 1]; + } + references[^1].ImmediateTarget = concreteTarget; + + // Act + var target = references[0].RecursiveTarget; + + // Assert + Assert.Same(concreteTarget, target); + Assert.Equal(referenceCount, references.Sum(reference => reference.TargetAccessCount)); + } + + [Fact] + public void FilteringADocumentWithCircularSchemaReferencesPreservesTheReferences() + { + // Arrange + var readResult = OpenApiDocument.Parse( + """ + { + "openapi": "3.0.0", + "info": { "title": "Test", "version": "0.0.1" }, + "paths": { + "/test": { + "get": { + "operationId": "getTest", + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/A" } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "A": { "$ref": "#/components/schemas/B" }, + "B": { "$ref": "#/components/schemas/A" } + } + } + } + """, "json"); + Assert.NotNull(readResult.Document); + var predicate = OpenApiFilterService.CreatePredicate(operationIds: "getTest"); + + // Act + var filtered = OpenApiFilterService.CreateFilteredDocument(readResult.Document, predicate); + + // Assert + Assert.NotNull(filtered.Components); + Assert.NotNull(filtered.Components.Schemas); + Assert.Contains("A", filtered.Components.Schemas.Keys); + Assert.Contains("B", filtered.Components.Schemas.Keys); + } + + [Fact] + public void CircularSchemaReferencePairIsStillReportedAsResolved() + { + // Arrange + var readResult = OpenApiDocument.Parse(CircularSchemaReferencePairJson, "json"); + Assert.NotNull(readResult.Document); + var schemaA = Assert.IsType(readResult.Document.Components!.Schemas!["A"]); + + // Act + var unresolved = schemaA.UnresolvedReference; + + // Assert: the reference does resolve to an element, only the chain never terminates + Assert.False(unresolved); + } + + [Fact] + public void ValidatingADocumentWithACircularSchemaReferencePairDoesNotThrow() + { + // Arrange + var readResult = OpenApiDocument.Parse(CircularSchemaReferencePairJson, "json"); + Assert.NotNull(readResult.Document); + + // Act + var errors = readResult.Document.Validate(ValidationRuleSet.GetDefaultRuleSet()); + + // Assert + Assert.NotNull(errors); + } + + private sealed class CountingSchemaReference : OpenApiSchemaReference + { + public CountingSchemaReference(string referenceId) + : base(referenceId) + { + } + + public IOpenApiSchema ImmediateTarget { get; set; } + + public int TargetAccessCount { get; private set; } + + public override IOpenApiSchema Target + { + get + { + TargetAccessCount++; + return ImmediateTarget; + } + } + } } } diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSecuritySchemeReferenceTests.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSecuritySchemeReferenceTests.cs index 767ea126f..1fb49794a 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSecuritySchemeReferenceTests.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiSecuritySchemeReferenceTests.cs @@ -63,7 +63,7 @@ public async Task SerializeSecuritySchemeReferenceAsV3JsonWorks(bool produceTers // Act _openApiSecuritySchemeReference.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput, inlineLocalReferences); @@ -82,7 +82,7 @@ public async Task SerializeSecuritySchemeReferenceAsV31JsonWorks(bool produceTer // Act _openApiSecuritySchemeReference.SerializeAsV31(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput, inlineLocalReferences); diff --git a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiTagReferenceTest.cs b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiTagReferenceTest.cs index e760982d2..f7e1fb62e 100644 --- a/test/Microsoft.OpenApi.Tests/Models/References/OpenApiTagReferenceTest.cs +++ b/test/Microsoft.OpenApi.Tests/Models/References/OpenApiTagReferenceTest.cs @@ -95,7 +95,7 @@ public async Task SerializeTagReferenceAsV3JsonWorks(bool produceTerseOutput) // Act _openApiTagReference.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -112,7 +112,7 @@ public async Task SerializeTagReferenceAsV31JsonWorks(bool produceTerseOutput) // Act _openApiTagReference.SerializeAsV31(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert await Verifier.Verify(outputStringWriter).UseParameters(produceTerseOutput); @@ -129,7 +129,7 @@ public async Task SerializeTagAsV3JsonWorks(bool produceTerseOutput) // Act _openApiTagReference2.SerializeAsV3(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert Assert.Equal("\"users.user\"", outputStringWriter.ToString()); @@ -146,7 +146,7 @@ public async Task SerializeTagAsV31JsonWorks(bool produceTerseOutput) // Act _openApiTagReference2.SerializeAsV31(writer); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); // Assert Assert.Equal("\"users.user\"", outputStringWriter.ToString()); @@ -193,7 +193,7 @@ public async Task SerializesADescriptionWithNoMatchingTagDefinition(OpenApiSpecV document.SerializeAsV31(writer); break; } - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); var expected = version switch { diff --git a/test/Microsoft.OpenApi.Tests/OpenApiTestAssert.cs b/test/Microsoft.OpenApi.Tests/OpenApiTestAssert.cs new file mode 100644 index 000000000..e9bd68048 --- /dev/null +++ b/test/Microsoft.OpenApi.Tests/OpenApiTestAssert.cs @@ -0,0 +1,158 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Text.Json.Nodes; +using Xunit; + +namespace Microsoft.OpenApi.Tests; + +public static class OpenApiTestAssert +{ + public static void Equivalent(T expected, T actual, params string[] excludedMemberNames) + { + Equivalent(expected, actual, path => excludedMemberNames.Contains(path.MemberName)); + } + + public static void Equivalent(T expected, T actual, Func exclude) + { + Compare(expected, actual, EquivalencyPath.Root, exclude, new HashSet()); + } + + public readonly record struct EquivalencyPath(string Value, string MemberName) + { + public static EquivalencyPath Root { get; } = new(string.Empty, string.Empty); + + public EquivalencyPath Member(string memberName) + { + var value = string.IsNullOrEmpty(Value) ? memberName : $"{Value}.{memberName}"; + return new(value, memberName); + } + + public EquivalencyPath Index(object index) + { + return new($"{Value}[{index}]", MemberName); + } + } + + private static void Compare(object expected, object actual, EquivalencyPath path, Func exclude, HashSet visited) + { + if (exclude(path)) + { + return; + } + + if (expected is null || actual is null) + { + Assert.Equal(expected, actual); + return; + } + + if (expected is JsonNode || actual is JsonNode) + { + var expectedNode = Assert.IsAssignableFrom(expected); + var actualNode = Assert.IsAssignableFrom(actual); + Assert.True(JsonNode.DeepEquals(expectedNode, actualNode), $"Expected JSON node at '{path.Value}' to be equivalent."); + return; + } + + var expectedType = expected.GetType(); + if (IsSimpleType(expectedType)) + { + Assert.Equal(expected, actual); + return; + } + + if (!expectedType.IsValueType) + { + var pair = new ReferencePair(expected, actual); + if (!visited.Add(pair)) + { + return; + } + } + + if (expected is IDictionary expectedDictionary && actual is IDictionary actualDictionary) + { + Assert.Equal(expectedDictionary.Count, actualDictionary.Count); + foreach (DictionaryEntry expectedEntry in expectedDictionary) + { + Assert.True(actualDictionary.Contains(expectedEntry.Key), $"Expected dictionary at '{path.Value}' to contain key '{expectedEntry.Key}'."); + Compare(expectedEntry.Value, actualDictionary[expectedEntry.Key], path.Index(expectedEntry.Key), exclude, visited); + } + + return; + } + + if (expected is IEnumerable expectedEnumerable && actual is IEnumerable actualEnumerable && expected is not string) + { + var expectedItems = expectedEnumerable.Cast().ToList(); + var actualItems = actualEnumerable.Cast().ToList(); + + Assert.Equal(expectedItems.Count, actualItems.Count); + for (var index = 0; index < expectedItems.Count; index++) + { + Compare(expectedItems[index], actualItems[index], path.Index(index), exclude, visited); + } + + return; + } + + foreach (var property in expectedType.GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(static p => p.GetIndexParameters().Length == 0)) + { + var propertyPath = path.Member(property.Name); + if (exclude(propertyPath)) + { + continue; + } + + var actualProperty = actual.GetType().GetProperty(property.Name, BindingFlags.Instance | BindingFlags.Public); + Assert.NotNull(actualProperty); + Compare(property.GetValue(expected), actualProperty.GetValue(actual), propertyPath, exclude, visited); + } + + foreach (var field in expectedType.GetFields(BindingFlags.Instance | BindingFlags.Public)) + { + var fieldPath = path.Member(field.Name); + if (exclude(fieldPath)) + { + continue; + } + + var actualField = actual.GetType().GetField(field.Name, BindingFlags.Instance | BindingFlags.Public); + Assert.NotNull(actualField); + Compare(field.GetValue(expected), actualField.GetValue(actual), fieldPath, exclude, visited); + } + } + + private static bool IsSimpleType(Type type) + { + return type.IsPrimitive + || type.IsEnum + || type == typeof(string) + || type == typeof(decimal) + || type == typeof(DateTime) + || type == typeof(DateTimeOffset) + || type == typeof(TimeSpan) + || type == typeof(Uri) + || type == typeof(Guid); + } + + private readonly record struct ReferencePair(object Expected, object Actual) + { + public bool Equals(ReferencePair other) + { + return ReferenceEquals(Expected, other.Expected) && ReferenceEquals(Actual, other.Actual); + } + + public override int GetHashCode() + { + return HashCode.Combine(RuntimeHelpers.GetHashCode(Expected), RuntimeHelpers.GetHashCode(Actual)); + } + } +} diff --git a/test/Microsoft.OpenApi.Tests/Reader/OpenApiJsonReaderTests.cs b/test/Microsoft.OpenApi.Tests/Reader/OpenApiJsonReaderTests.cs index 8b0fac733..e6dd63989 100644 --- a/test/Microsoft.OpenApi.Tests/Reader/OpenApiJsonReaderTests.cs +++ b/test/Microsoft.OpenApi.Tests/Reader/OpenApiJsonReaderTests.cs @@ -57,6 +57,45 @@ public void ReadValidatesParsedDocumentAgainstConfiguredRules() Assert.Equal("Document failed validation.", error.Message); } + [Fact] + public void ReadReturnsValidationErrorForSelfReferentialDiscriminatorSchema() + { + var reader = new OpenApiJsonReader(); + using var stream = CreateStream( + """ + { + "openapi": "3.0.1", + "info": { + "title": "Sample", + "version": "1.0.0" + }, + "paths": {}, + "components": { + "schemas": { + "Pet": { + "type": "object", + "discriminator": { + "propertyName": "kind" + }, + "oneOf": [ + { + "$ref": "#/components/schemas/Pet" + } + ] + } + } + } + } + """); + + var result = reader.Read(stream, DocumentLocation, new OpenApiReaderSettings()); + + Assert.NotNull(result.Document); + Assert.Contains(result.Diagnostic.Errors, error => + error is OpenApiValidatorError validatorError && + validatorError.RuleName == nameof(OpenApiSchemaRules.ValidateSchemaDiscriminator)); + } + [Fact] public void ReadReturnsDiagnosticWhenRootNodeCannotBeParsedAsDocument() { diff --git a/test/Microsoft.OpenApi.Tests/Reader/OpenApiModelFactoryTests.cs b/test/Microsoft.OpenApi.Tests/Reader/OpenApiModelFactoryTests.cs index 595daabae..f7e9e0431 100644 --- a/test/Microsoft.OpenApi.Tests/Reader/OpenApiModelFactoryTests.cs +++ b/test/Microsoft.OpenApi.Tests/Reader/OpenApiModelFactoryTests.cs @@ -12,7 +12,7 @@ public class OpenApiModelFactoryTests [Fact] public async Task LoadDocumentWithCircularSchemaPropertyReferencesShouldSucceed() { - var filePath = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid():N}.json"); + var filePath = Path.Join(Path.GetTempPath(), $"{Guid.NewGuid():N}.json"); await File.WriteAllTextAsync(filePath, """ { "openapi": "3.0.0", @@ -40,11 +40,11 @@ await File.WriteAllTextAsync(filePath, """ } } } -"""); +""", TestContext.Current.CancellationToken); try { - var result = await OpenApiDocument.LoadAsync(filePath); + var result = await OpenApiDocument.LoadAsync(filePath, token: TestContext.Current.CancellationToken); Assert.NotNull(result.Document); Assert.Empty(result.Diagnostic.Errors); @@ -62,7 +62,7 @@ await File.WriteAllTextAsync(filePath, """ [Fact] public async Task LoadDocumentWithCircularRootSchemaReferencesShouldReturnDiagnosticWarning() { - var filePath = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid():N}.json"); + var filePath = Path.Join(Path.GetTempPath(), $"{Guid.NewGuid():N}.json"); await File.WriteAllTextAsync(filePath, """ { "openapi": "3.0.0", @@ -82,11 +82,11 @@ await File.WriteAllTextAsync(filePath, """ } } } -"""); +""", TestContext.Current.CancellationToken); try { - var result = await OpenApiDocument.LoadAsync(filePath); + var result = await OpenApiDocument.LoadAsync(filePath, token: TestContext.Current.CancellationToken); Assert.NotNull(result.Document); Assert.Empty(result.Diagnostic.Errors); @@ -102,7 +102,7 @@ await File.WriteAllTextAsync(filePath, """ [Fact] public async Task UsesSettingsBaseUrl() { - var tempFilePathReferee = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); + var tempFilePathReferee = Path.GetTempFileName(); await File.WriteAllTextAsync(tempFilePathReferee, """ { @@ -147,8 +147,8 @@ await File.WriteAllTextAsync(tempFilePathReferee, } } } -"""); - var tempFilePathReferrer = Path.Combine(Path.GetTempPath(), Path.GetTempFileName()); +""", TestContext.Current.CancellationToken); + var tempFilePathReferrer = Path.GetTempFileName(); await File.WriteAllTextAsync(tempFilePathReferrer, $$$""" { @@ -193,18 +193,18 @@ await File.WriteAllTextAsync(tempFilePathReferrer, } } } -"""); +""", TestContext.Current.CancellationToken); // read referrer document to a memory stream using var stream = new MemoryStream(); using var reader = new StreamReader(tempFilePathReferrer); - await reader.BaseStream.CopyToAsync(stream); + await reader.BaseStream.CopyToAsync(stream, TestContext.Current.CancellationToken); stream.Position = 0; var baseUri = new Uri(tempFilePathReferrer); var settings = new OpenApiReaderSettings { BaseUrl = baseUri, }; - var readResult = await OpenApiDocument.LoadAsync(stream, settings: settings); + var readResult = await OpenApiDocument.LoadAsync(stream, settings: settings, cancellationToken: TestContext.Current.CancellationToken); Assert.Equal(OpenApiConstants.Json, readResult.Diagnostic.Format); Assert.NotNull(readResult.Document); Assert.NotNull(readResult.Document.Components); @@ -237,7 +237,7 @@ public async Task CanLoadANonSeekableStreamInJsonAndDetectFormat() using var nonSeekableStream = new NonSeekableStream(memoryStream); // When - var (document, _) = await OpenApiDocument.LoadAsync(nonSeekableStream); + var (document, _) = await OpenApiDocument.LoadAsync(nonSeekableStream, cancellationToken: TestContext.Current.CancellationToken); // Then Assert.NotNull(document); @@ -254,7 +254,7 @@ public async Task CanLoadANonSeekableStreamInYamlAndDetectFormat() settings.AddYamlReader(); // When - var (document, _) = await OpenApiDocument.LoadAsync(nonSeekableStream, settings: settings); + var (document, _) = await OpenApiDocument.LoadAsync(nonSeekableStream, settings: settings, cancellationToken: TestContext.Current.CancellationToken); // Then Assert.NotNull(document); @@ -269,7 +269,7 @@ public async Task CanLoadAnAsyncOnlyStreamInJsonAndDetectFormat() await using var nonSeekableStream = new AsyncOnlyStream(memoryStream); // When - var (document, _) = await OpenApiDocument.LoadAsync(nonSeekableStream); + var (document, _) = await OpenApiDocument.LoadAsync(nonSeekableStream, cancellationToken: TestContext.Current.CancellationToken); // Then Assert.NotNull(document); @@ -286,7 +286,7 @@ public async Task CanLoadAnAsyncOnlyStreamInYamlAndDetectFormat() settings.AddYamlReader(); // When - var (document, _) = await OpenApiDocument.LoadAsync(nonSeekableStream, settings: settings); + var (document, _) = await OpenApiDocument.LoadAsync(nonSeekableStream, settings: settings, cancellationToken: TestContext.Current.CancellationToken); // Then Assert.NotNull(document); @@ -301,7 +301,7 @@ public async Task CanLoadANonSeekableStreamInJsonAndDetectFormatWhenPrecededBySp using var nonSeekableStream = new NonSeekableStream(memoryStream); // When - var (document, _) = await OpenApiDocument.LoadAsync(nonSeekableStream); + var (document, _) = await OpenApiDocument.LoadAsync(nonSeekableStream, cancellationToken: TestContext.Current.CancellationToken); // Then Assert.NotNull(document); diff --git a/test/Microsoft.OpenApi.Tests/Reader/OpenApiParameterCookieStyleDeserializationTests.cs b/test/Microsoft.OpenApi.Tests/Reader/OpenApiParameterCookieStyleDeserializationTests.cs index 6a98dea42..0ce7ec4eb 100644 --- a/test/Microsoft.OpenApi.Tests/Reader/OpenApiParameterCookieStyleDeserializationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Reader/OpenApiParameterCookieStyleDeserializationTests.cs @@ -179,7 +179,7 @@ public async Task SerializeAndDeserializeCookieParameterRoundTrip() }; // Act - var json = await document.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2); + var json = await document.SerializeAsJsonAsync(OpenApiSpecVersion.OpenApi3_2, TestContext.Current.CancellationToken); var result = OpenApiDocument.Parse(json, "json", SettingsFixture.ReaderSettings); // Assert diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiComponentsValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiComponentsValidationTests.cs index 68f89a2a2..8993c17a5 100644 --- a/test/Microsoft.OpenApi.Tests/Validations/OpenApiComponentsValidationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiComponentsValidationTests.cs @@ -2,7 +2,11 @@ // Licensed under the MIT license. using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; using Xunit; namespace Microsoft.OpenApi.Validations.Tests @@ -32,8 +36,65 @@ public void ValidateKeyMustMatchRegularExpressionInComponents() Assert.False(result); Assert.NotNull(errors); var error = Assert.Single(errors); - Assert.Equal(string.Format(SRResource.Validation_ComponentsKeyMustMatchRegularExpr, key, "responses", OpenApiComponentsRules.KeyRegex.ToString()), + Assert.Equal(string.Format(SRResource.Validation_ComponentsKeyMustMatchRegularExpr, key, "responses", OpenApiComponentsRules.KeyPattern), error.Message); } + + [Theory] + [InlineData("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_", true)] + [InlineData("", false)] + [InlineData("a b", false)] + [InlineData("a/b", false)] + [InlineData("é", false)] + [InlineData("12", false)] + [InlineData("a\n", true)] + [InlineData("\n", false)] + [InlineData("a\n\n", false)] + [InlineData("a\r\n", false)] + [InlineData("a\nb", false)] + [InlineData("a\0", false)] + public void ValidateComponentKeyPreservesRegexBehavior(string key, bool isValid) + { + var components = new OpenApiComponents + { + Schemas = new Dictionary + { + { key, new OpenApiSchema() } + } + }; + + var rules = new ValidationRuleSet(); + rules.Add(typeof(OpenApiComponents), OpenApiComponentsRules.KeyMustBeRegularExpression); + var errors = components.Validate(rules); + + Assert.Equal(isValid, !errors.Any()); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task LoadAsyncValidatesLongComponentKeys(bool isValid) + { + var key = new string('a', 1_000_000) + (isValid ? string.Empty : "!"); + var json = """ + {"openapi":"3.1.0","info":{"title":"Test","version":"1.0"},"paths":{},"components":{"schemas":{ + """ + JsonSerializer.Serialize(key) + ":{\"type\":\"string\"}}}}"; + using var stream = new MemoryStream(Encoding.UTF8.GetBytes(json)); + + var result = await OpenApiDocument.LoadAsync(stream, cancellationToken: TestContext.Current.CancellationToken); + + Assert.NotNull(result.Document); + Assert.NotNull(result.Diagnostic); + if (isValid) + { + Assert.Empty(result.Diagnostic.Errors); + } + else + { + var error = Assert.Single(result.Diagnostic.Errors); + Assert.Equal(string.Format(SRResource.Validation_ComponentsKeyMustMatchRegularExpr, + key, "schemas", OpenApiComponentsRules.KeyPattern), error.Message); + } + } } } diff --git a/test/Microsoft.OpenApi.Tests/Validations/OpenApiSchemaValidationTests.cs b/test/Microsoft.OpenApi.Tests/Validations/OpenApiSchemaValidationTests.cs index 8a5751666..7f5cd47e2 100644 --- a/test/Microsoft.OpenApi.Tests/Validations/OpenApiSchemaValidationTests.cs +++ b/test/Microsoft.OpenApi.Tests/Validations/OpenApiSchemaValidationTests.cs @@ -392,5 +392,204 @@ public void ValidateOneOfSchemaPropertyNameContainsPropertySpecifiedInTheDiscrim //Assert Assert.Empty(errors); } + + [Fact] + public void ValidateSchemaDiscriminatorReturnsExistingErrorForSelfCycleWithoutValidDiscriminator() + { + // Arrange + var schema = new OpenApiSchema + { + Type = JsonSchemaType.Object, + Discriminator = new() + { + PropertyName = "type" + } + }; + schema.OneOf = [schema]; + + var components = new OpenApiComponents + { + Schemas = new Dictionary + { + ["Person"] = schema + } + }; + + // Act + var validator = new OpenApiValidator(ValidationRuleSet.GetDefaultRuleSet()); + var walker = new OpenApiWalker(validator); + walker.Walk(components); + + // Assert + Assert.Equivalent(new List + { + new(nameof(OpenApiSchemaRules.ValidateSchemaDiscriminator), "#/schemas/Person/discriminator", + string.Format(SRResource.Validation_SchemaRequiredFieldListMustContainThePropertySpecifiedInTheDiscriminator, + string.Empty, "type")) + }, validator.Errors); + } + + [Fact] +#pragma warning disable CS0618 + public void TraverseSchemaElementsReturnsFalseForMultiNodeCycleWithoutDiscriminator() + { + // Arrange + var first = new OpenApiSchema(); + var second = new OpenApiSchema(); + var third = new OpenApiSchema(); + + first.OneOf = [second]; + second.AnyOf = [third]; + third.AllOf = [first]; + + // Act + var result = OpenApiSchemaRules.TraverseSchemaElements("type", [first]); + + // Assert + Assert.False(result); + } +#pragma warning restore CS0618 + + [Fact] +#pragma warning disable CS0618 + public void ValidateChildSchemaAgainstDiscriminatorReturnsTrueForDeepAcyclicTraversal() + { + // Arrange + const string discriminatorName = "type"; + var root = CreateSchemaChain(5000, discriminatorName); + + // Act + var result = OpenApiSchemaRules.ValidateChildSchemaAgainstDiscriminator(root, discriminatorName); + + // Assert + Assert.True(result); + } +#pragma warning restore CS0618 + + [Fact] + public void ValidateSchemaDiscriminatorCanFindValidLaterOneOfChild() + { + // Arrange + var components = new OpenApiComponents + { + Schemas = new Dictionary + { + ["Person"] = new OpenApiSchema + { + Type = JsonSchemaType.Object, + Discriminator = new() + { + PropertyName = "type" + }, + OneOf = + [ + new OpenApiSchema(), + new OpenApiSchema + { + Properties = new Dictionary + { + ["type"] = new OpenApiSchema + { + Type = JsonSchemaType.String + } + } + } + ] + } + } + }; + + // Act + var validator = new OpenApiValidator(ValidationRuleSet.GetDefaultRuleSet()); + var walker = new OpenApiWalker(validator); + walker.Walk(components); + + // Assert + Assert.Empty(validator.Errors); + } + + [Fact] + public void ValidateSchemaDiscriminatorChecksOneOfAnyOfAndAllOf() + { + // Arrange + var components = new OpenApiComponents + { + Schemas = new Dictionary + { + ["Person"] = new OpenApiSchema + { + Type = JsonSchemaType.Object, + Discriminator = new() + { + PropertyName = "type" + }, + OneOf = + [ + new OpenApiSchema() + ], + AnyOf = + [ + new OpenApiSchema() + ], + AllOf = + [ + new OpenApiSchema + { + Properties = new Dictionary + { + ["type"] = new OpenApiSchema + { + Type = JsonSchemaType.String + } + } + } + ] + } + } + }; + + // Act + var validator = new OpenApiValidator(ValidationRuleSet.GetDefaultRuleSet()); + var walker = new OpenApiWalker(validator); + walker.Walk(components); + + // Assert + Assert.Empty(validator.Errors); + } + + private static OpenApiSchema CreateSchemaChain(int depth, string discriminatorName) + { + var root = new OpenApiSchema(); + var current = root; + + for (var i = 0; i < depth; i++) + { + var next = new OpenApiSchema(); + switch (i % 3) + { + case 0: + current.OneOf = [next]; + break; + case 1: + current.AnyOf = [next]; + break; + default: + current.AllOf = [next]; + break; + } + + current = next; + } + + current.Properties = new Dictionary + { + [discriminatorName] = new OpenApiSchema + { + Type = JsonSchemaType.String + } + }; + + return root; + } } } diff --git a/test/Microsoft.OpenApi.Tests/Writers/OpenApiJsonWriterTests.cs b/test/Microsoft.OpenApi.Tests/Writers/OpenApiJsonWriterTests.cs index fa3b0163d..8065c16b3 100644 --- a/test/Microsoft.OpenApi.Tests/Writers/OpenApiJsonWriterTests.cs +++ b/test/Microsoft.OpenApi.Tests/Writers/OpenApiJsonWriterTests.cs @@ -58,7 +58,7 @@ public async Task WriteStringListAsJsonShouldMatchExpected(string[] stringValues } writer.WriteEndArray(); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); var parsedObject = JsonSerializer.Deserialize>(outputString.GetStringBuilder().ToString()); var expectedObject = diff --git a/test/Microsoft.OpenApi.Tests/Writers/OpenApiWriterAnyExtensionsTests.cs b/test/Microsoft.OpenApi.Tests/Writers/OpenApiWriterAnyExtensionsTests.cs index fb315feff..fb537ce39 100644 --- a/test/Microsoft.OpenApi.Tests/Writers/OpenApiWriterAnyExtensionsTests.cs +++ b/test/Microsoft.OpenApi.Tests/Writers/OpenApiWriterAnyExtensionsTests.cs @@ -173,13 +173,10 @@ public async Task WriteOpenApiDateTimeAsJsonWorksAsync(string inputString, bool Assert.Equal(expectedJson, json); } - public static IEnumerable BooleanInputs - { - get => - from input in new[] { true, false } - from shouldBeTerse in shouldProduceTerseOutputValues - select new object[] { input, shouldBeTerse }; - } + public static IEnumerable BooleanInputs { get; } = + from input in new[] { true, false } + from shouldBeTerse in shouldProduceTerseOutputValues + select new object[] { input, shouldBeTerse }; [Theory] [MemberData(nameof(BooleanInputs))] diff --git a/test/Microsoft.OpenApi.Tests/Writers/OpenApiWriterSpecialCharacterTests.cs b/test/Microsoft.OpenApi.Tests/Writers/OpenApiWriterSpecialCharacterTests.cs index 66819367c..16a416080 100644 --- a/test/Microsoft.OpenApi.Tests/Writers/OpenApiWriterSpecialCharacterTests.cs +++ b/test/Microsoft.OpenApi.Tests/Writers/OpenApiWriterSpecialCharacterTests.cs @@ -14,23 +14,20 @@ public class OpenApiWriterSpecialCharacterTests { static bool[] shouldProduceTerseOutputValues = new[] { true, false }; - public static IEnumerable StringWithSpecialCharacters - { - get => - from inputExpected in new[] { - new[]{ "Test\bTest", "\"Test\\bTest\"" }, - new[]{ "Test\fTest", "\"Test\\fTest\""}, - new[]{ "Test\nTest", "\"Test\\nTest\""}, - new[]{ "Test\rTest", "\"Test\\rTest\""}, - new[]{ "Test\tTest", "\"Test\\tTest\""}, - new[]{ "Test\\Test", "\"Test\\\\Test\""}, - new[]{ "Test\"Test", "\"Test\\\"Test\""}, - new[]{ "StringsWith\"Quotes\"", "\"StringsWith\\\"Quotes\\\"\""}, - new[]{ "0x1234", "\"0x1234\""}, - } - from shouldBeTerse in shouldProduceTerseOutputValues - select new object[] { inputExpected[0], inputExpected[1], shouldBeTerse }; - } + public static IEnumerable StringWithSpecialCharacters { get; } = + from inputExpected in new[] { + new[]{ "Test\bTest", "\"Test\\bTest\"" }, + new[]{ "Test\fTest", "\"Test\\fTest\""}, + new[]{ "Test\nTest", "\"Test\\nTest\""}, + new[]{ "Test\rTest", "\"Test\\rTest\""}, + new[]{ "Test\tTest", "\"Test\\tTest\""}, + new[]{ "Test\\Test", "\"Test\\\\Test\""}, + new[]{ "Test\"Test", "\"Test\\\"Test\""}, + new[]{ "StringsWith\"Quotes\"", "\"StringsWith\\\"Quotes\\\"\""}, + new[]{ "0x1234", "\"0x1234\""}, + } + from shouldBeTerse in shouldProduceTerseOutputValues + select new object[] { inputExpected[0], inputExpected[1], shouldBeTerse }; [Theory] [MemberData(nameof(StringWithSpecialCharacters))] diff --git a/test/Microsoft.OpenApi.Tests/Writers/OpenApiYamlWriterTests.cs b/test/Microsoft.OpenApi.Tests/Writers/OpenApiYamlWriterTests.cs index e80b5f7f9..97f18df28 100644 --- a/test/Microsoft.OpenApi.Tests/Writers/OpenApiYamlWriterTests.cs +++ b/test/Microsoft.OpenApi.Tests/Writers/OpenApiYamlWriterTests.cs @@ -70,7 +70,7 @@ public async Task WriteStringListAsYamlShouldMatchExpected(string[] stringValues } writer.WriteEndArray(); - await writer.FlushAsync(); + await writer.FlushAsync(TestContext.Current.CancellationToken); var actualYaml = outputString.GetStringBuilder() .ToString()