diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 9278758..ea2c34b 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,25 +3,19 @@ "isRoot": true, "tools": { "jetbrains.resharper.globaltools": { - "version": "2023.1.2", + "version": "2023.2.1", "commands": [ "jb" ] }, "regitlint": { - "version": "6.3.11", + "version": "6.3.12", "commands": [ "regitlint" ] }, - "codecov.tool": { - "version": "1.13.0", - "commands": [ - "codecov" - ] - }, "dotnet-reportgenerator-globaltool": { - "version": "5.1.20", + "version": "5.1.25", "commands": [ "reportgenerator" ] diff --git a/.editorconfig b/.editorconfig index 86cbbc3..5a036d1 100644 --- a/.editorconfig +++ b/.editorconfig @@ -8,7 +8,7 @@ charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true -[*.{config,csproj,css,js,json,props,ruleset,xslt}] +[*.{config,csproj,css,js,json,props,ruleset,xslt,html}] indent_size = 2 [*.{cs}] diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..b065d30 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: json-api-dotnet diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 103a247..0000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,21 +0,0 @@ -#### DESCRIPTION - -_Describe your bug or feature request here._ - -#### STEPS TO REPRODUCE - -_Consider to include your code here, such as models, DbContext, controllers, resource services, repositories, resource definitions etc. Please also include the request URL with body (if applicable) and the full exception stack trace (set `options.IncludeExceptionStackTraceInErrors` to `true`) in case of errors._ - -1. -2. -3. - -#### EXPECTED BEHAVIOR - -#### ACTUAL BEHAVIOR - -#### VERSIONS USED -- JsonApiDotNetCore.MongoDb version: -- JsonApiDotNetCore version: -- ASP.NET Core version: -- MongoDB version: diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..a45d71e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,32 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: 'bug' +assignees: '' + +--- + + + +#### DESCRIPTION + + +#### STEPS TO REPRODUCE + + +1. +2. +3. + +#### EXPECTED BEHAVIOR + + +#### ACTUAL BEHAVIOR + + +#### VERSIONS USED +- JsonApiDotNetCore.MongoDb version: +- JsonApiDotNetCore version: +- ASP.NET Core version: +- MongoDB version: diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..f629ca4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,22 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: 'enhancement' +assignees: '' + +--- + + + +**Is your feature request related to a problem? Please describe.** + + +**Describe the solution you'd like** + + +**Describe alternatives you've considered** + + +**Additional context** + diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md new file mode 100644 index 0000000..76b3998 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.md @@ -0,0 +1,39 @@ +--- +name: Question +about: Ask a question +title: '' +labels: 'question' +assignees: '' + +--- + + + +#### SUMMARY + + +#### DETAILS + + + +#### STEPS TO REPRODUCE + + +1. +2. +3. + +#### VERSIONS USED +- JsonApiDotNetCore.MongoDb version: +- JsonApiDotNetCore version: +- ASP.NET Core version: +- MongoDB version: diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..ecd4217 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,21 @@ +version: 2 +updates: +- package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + pull-request-branch-name: + separator: "-" +- package-ecosystem: nuget + directory: "/" + schedule: + interval: daily + pull-request-branch-name: + separator: "-" + open-pull-requests-limit: 25 + ignore: + # Block updates to all exposed dependencies of the NuGet packages we produce, as updating them would be a breaking change. + - dependency-name: 'JsonApiDotNetCore*' + # Block major updates of packages that require a matching .NET version. + - dependency-name: 'Microsoft.AspNetCore*' + update-types: ["version-update:semver-major"] diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..c56fa3d --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,253 @@ +# General links +# https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables +# https://docs.github.com/en/actions/learn-github-actions/contexts#github-context +# https://docs.github.com/en/webhooks-and-events/webhooks/webhook-events-and-payloads +# https://docs.github.com/en/actions/learn-github-actions/expressions +# https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions +# https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs + +name: Build + +on: + push: + branches: [ 'master', 'release/**' ] + pull_request: + branches: [ 'master', 'release/**' ] + release: + types: [published] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true + DOTNET_CLI_TELEMETRY_OPTOUT: true + +jobs: + build-and-test: + timeout-minutes: 60 + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + runs-on: ${{ matrix.os }} + permissions: + contents: read + steps: + - name: Setup .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 6.0.x + - name: Setup PowerShell (Ubuntu) + if: matrix.os == 'ubuntu-latest' + run: | + dotnet tool install --global PowerShell + - name: Find latest PowerShell version (Windows) + if: matrix.os == 'windows-latest' + shell: pwsh + run: | + $packageName = "powershell" + $outputText = dotnet tool search $packageName --take 1 + $outputLine = ("" + $outputText) + $indexOfVersionLine = $outputLine.IndexOf($packageName) + $latestVersion = $outputLine.substring($indexOfVersionLine + $packageName.length).trim().split(" ")[0].trim() + + Write-Output "Found PowerShell version: $latestVersion" + Write-Output "POWERSHELL_LATEST_VERSION=$latestVersion" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + - name: Setup PowerShell (Windows) + if: matrix.os == 'windows-latest' + shell: cmd + run: | + set DOWNLOAD_LINK=https://github.com/PowerShell/PowerShell/releases/download/v%POWERSHELL_LATEST_VERSION%/PowerShell-%POWERSHELL_LATEST_VERSION%-win-x64.msi + set OUTPUT_PATH=%RUNNER_TEMP%\PowerShell-%POWERSHELL_LATEST_VERSION%-win-x64.msi + echo Downloading from: %DOWNLOAD_LINK% to: %OUTPUT_PATH% + curl --location --output %OUTPUT_PATH% %DOWNLOAD_LINK% + msiexec.exe /package %OUTPUT_PATH% /quiet USE_MU=1 ENABLE_MU=1 ADD_PATH=1 DISABLE_TELEMETRY=1 + - name: Setup PowerShell (macOS) + if: matrix.os == 'macos-latest' + run: | + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + brew install --cask powershell + - name: Show installed versions + shell: pwsh + run: | + Write-Host "$(pwsh --version) is installed at $PSHOME" + Write-Host "Active .NET SDK: $(dotnet --version)" + - name: Git checkout + uses: actions/checkout@v4 + - name: Restore tools + run: | + dotnet tool restore + - name: Restore packages + run: | + dotnet restore + - name: Calculate version suffix + shell: pwsh + run: | + if ($env:GITHUB_REF_TYPE -eq 'tag') { + # Get the version prefix/suffix from the git tag. For example: 'v1.0.0-preview1-final' => '1.0.0' and 'preview1-final' + $segments = $env:GITHUB_REF_NAME -split "-" + $versionPrefix = $segments[0].TrimStart('v') + $versionSuffix = $segments.Length -eq 1 ? '' : $segments[1..$($segments.Length - 1)] -join '-' + + [xml]$xml = Get-Content Directory.Build.props + $configuredVersionPrefix = $xml.Project.PropertyGroup[0].JsonApiDotNetCoreMongoDbVersionPrefix + if ($configuredVersionPrefix -ne $versionPrefix) { + Write-Error "Version prefix from git release tag '$versionPrefix' does not match version prefix '$configuredVersionPrefix' stored in Directory.Build.props." + # To recover from this: + # - Delete the GitHub release + # - Run: git push --delete origin the-invalid-tag-name + # - Adjust JsonApiDotNetCoreVersionPrefix in Directory.Build.props, commit and push + # - Recreate the GitHub release + } + } + else { + # Get the version suffix from the auto-incrementing build number. For example: '123' => 'master-00123' + $revision = "{0:D5}" -f [convert]::ToInt32($env:GITHUB_RUN_NUMBER, 10) + $branchName = ![string]::IsNullOrEmpty($env:GITHUB_HEAD_REF) ? $env:GITHUB_HEAD_REF : $env:GITHUB_REF_NAME + $safeName = $branchName.Replace('/', '-').Replace('_', '-') + $versionSuffix = "$safeName-$revision" + } + Write-Output "Using version suffix: $versionSuffix" + Write-Output "PACKAGE_VERSION_SUFFIX=$versionSuffix" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + - name: Build + shell: pwsh + run: | + dotnet build --no-restore --configuration Release /p:VersionSuffix=$env:PACKAGE_VERSION_SUFFIX + - name: Test + run: | + dotnet test --no-build --configuration Release --collect:"XPlat Code Coverage" --logger "GitHubActions;summary.includeSkippedTests=true" -- RunConfiguration.CollectSourceInformation=true DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.DeterministicReport=true + - name: Upload coverage to codecov.io + if: matrix.os == 'ubuntu-latest' + uses: codecov/codecov-action@v3 + - name: Generate packages + shell: pwsh + run: | + dotnet pack --no-build --configuration Release --output $env:GITHUB_WORKSPACE/artifacts/packages /p:VersionSuffix=$env:PACKAGE_VERSION_SUFFIX + - name: Upload packages to artifacts + if: matrix.os == 'ubuntu-latest' + uses: actions/upload-artifact@v3 + with: + name: packages + path: artifacts/packages + + inspect-code: + timeout-minutes: 60 + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + runs-on: ${{ matrix.os }} + permissions: + contents: read + steps: + - name: Setup .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 6.0.x + - name: Git checkout + uses: actions/checkout@v4 + - name: Restore tools + run: | + dotnet tool restore + - name: InspectCode + shell: pwsh + run: | + $inspectCodeOutputPath = Join-Path $env:RUNNER_TEMP 'jetbrains-inspectcode-results.xml' + Write-Output "INSPECT_CODE_OUTPUT_PATH=$inspectCodeOutputPath" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + dotnet jb inspectcode JsonApiDotNetCore.MongoDb.sln --build --output="$inspectCodeOutputPath" --profile=WarningSeverities.DotSettings --properties:Configuration=Release --properties:ContinuousIntegrationBuild=false --severity=WARNING --verbosity=WARN -dsl=GlobalAll -dsl=GlobalPerProduct -dsl=SolutionPersonal -dsl=ProjectPersonal + - name: Verify outcome + shell: pwsh + run: | + [xml]$xml = Get-Content $env:INSPECT_CODE_OUTPUT_PATH + if ($xml.report.Issues -and $xml.report.Issues.Project) { + foreach ($project in $xml.report.Issues.Project) { + if ($project.Issue.Count -gt 0) { + $project.ForEach({ + Write-Output "`nProject $($project.Name)" + $failed = $true + + $_.Issue.ForEach({ + $issueType = $xml.report.IssueTypes.SelectSingleNode("IssueType[@Id='$($_.TypeId)']") + $severity = $_.Severity ?? $issueType.Severity + + Write-Output "[$severity] $($_.File):$($_.Line) $($_.TypeId): $($_.Message)" + }) + }) + } + } + + if ($failed) { + Write-Error "One or more projects failed code inspection." + } + } + + cleanup-code: + timeout-minutes: 60 + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + runs-on: ${{ matrix.os }} + permissions: + contents: read + steps: + - name: Setup .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 6.0.x + - name: Git checkout + uses: actions/checkout@v4 + with: + fetch-depth: 2 + - name: Restore tools + run: | + dotnet tool restore + - name: Restore packages + run: | + dotnet restore + - name: CleanupCode (on PR diff) + if: github.event_name == 'pull_request' + shell: pwsh + run: | + # Not using the environment variables for SHAs, because they may be outdated. This may happen on force-push after the build is queued, but before it starts. + # The below works because HEAD is detached (at the merge commit), so HEAD~1 is at the base branch. When a PR contains no commits, this job will not run. + $headCommitHash = git rev-parse HEAD + $baseCommitHash = git rev-parse HEAD~1 + + Write-Output "Running code cleanup on commit range $baseCommitHash..$headCommitHash in pull request." + dotnet regitlint -s JsonApiDotNetCore.MongoDb.sln --print-command --skip-tool-check --max-runs=5 --jb-profile="JADNC Full Cleanup" --jb --properties:Configuration=Release --jb --verbosity=WARN -f commits -a $headCommitHash -b $baseCommitHash --fail-on-diff --print-diff + - name: CleanupCode (on branch) + if: github.event_name == 'push' || github.event_name == 'release' + shell: pwsh + run: | + Write-Output "Running code cleanup on all files." + dotnet regitlint -s JsonApiDotNetCore.MongoDb.sln --print-command --skip-tool-check --jb-profile="JADNC Full Cleanup" --jb --properties:Configuration=Release --jb --verbosity=WARN --fail-on-diff --print-diff + + publish: + timeout-minutes: 60 + runs-on: ubuntu-latest + needs: [ build-and-test, inspect-code, cleanup-code ] + if: ${{ !github.event.pull_request.head.repo.fork }} + permissions: + packages: write + contents: write + steps: + - name: Download artifacts + uses: actions/download-artifact@v3 + - name: Publish to GitHub Packages + if: github.event_name == 'push' || github.event_name == 'release' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + shell: pwsh + run: | + dotnet nuget add source --username 'json-api-dotnet' --password "$env:GITHUB_TOKEN" --store-password-in-clear-text --name 'github' 'https://nuget.pkg.github.com/json-api-dotnet/index.json' + dotnet nuget push "$env:GITHUB_WORKSPACE/packages/*.nupkg" --api-key "$env:GITHUB_TOKEN" --source 'github' + - name: Publish to NuGet + if: github.event_name == 'release' && startsWith(github.ref, 'refs/tags/v') + env: + NUGET_ORG_API_KEY: ${{ secrets.NUGET_ORG_API_KEY }} + shell: pwsh + run: | + dotnet nuget push "$env:GITHUB_WORKSPACE/packages/*.nupkg" --api-key "$env:NUGET_ORG_API_KEY" --source 'nuget.org' diff --git a/Build.ps1 b/Build.ps1 index 623cfa7..4854651 100644 --- a/Build.ps1 +++ b/Build.ps1 @@ -1,115 +1,26 @@ -function CheckLastExitCode { - param ([int[]]$SuccessCodes = @(0), [scriptblock]$CleanupScript=$null) +$versionSuffix="pre" - if ($SuccessCodes -notcontains $LastExitCode) { - throw "Executable returned exit code $LastExitCode" +function VerifySuccessExitCode { + if ($LastExitCode -ne 0) { + throw "Command failed with exit code $LastExitCode." } } -function RunInspectCode { - $outputPath = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), 'jetbrains-inspectcode-results.xml') - dotnet jb inspectcode JsonApiDotNetCore.MongoDb.sln --no-build --output="$outputPath" --profile=WarningSeverities.DotSettings --properties:Configuration=Release --severity=WARNING --verbosity=WARN -dsl=GlobalAll -dsl=GlobalPerProduct -dsl=SolutionPersonal -dsl=ProjectPersonal - CheckLastExitCode - - [xml]$xml = Get-Content "$outputPath" - if ($xml.report.Issues -and $xml.report.Issues.Project) { - foreach ($project in $xml.report.Issues.Project) { - if ($project.Issue.Count -gt 0) { - $project.ForEach({ - Write-Output "`nProject $($project.Name)" - $failed = $true - - $_.Issue.ForEach({ - $issueType = $xml.report.IssueTypes.SelectSingleNode("IssueType[@Id='$($_.TypeId)']") - $severity = $_.Severity ?? $issueType.Severity - - Write-Output "[$severity] $($_.File):$($_.Line) $($_.TypeId): $($_.Message)" - }) - }) - } - } - - if ($failed) { - throw "One or more projects failed code inspection."; - } - } -} - -function RunCleanupCode { - # When running in cibuild for a pull request, this reformats only the files changed in the PR and fails if the reformat produces changes. - - if ($env:APPVEYOR_PULL_REQUEST_HEAD_COMMIT) { - # In the past, we used $env:APPVEYOR_PULL_REQUEST_HEAD_COMMIT for the merge commit hash. That is the pinned hash at the time the build is enqueued. - # When a force-push happens after that, while the build hasn't yet started, this hash becomes invalid during the build, resulting in a lookup error. - # To prevent failing the build for unobvious reasons we use HEAD, which is always a detached head (the PR merge result). - - $headCommitHash = git rev-parse HEAD - CheckLastExitCode - - $baseCommitHash = git rev-parse "$env:APPVEYOR_REPO_BRANCH" - CheckLastExitCode - - if ($baseCommitHash -ne $headCommitHash) { - Write-Output "Running code cleanup on commit range $baseCommitHash..$headCommitHash in pull request." - dotnet regitlint -s JsonApiDotNetCore.MongoDb.sln --print-command --skip-tool-check --jb-profile="JADNC Full Cleanup" --jb --properties:Configuration=Release --jb --verbosity=WARN -f commits -a $headCommitHash -b $baseCommitHash --fail-on-diff --print-diff - CheckLastExitCode - } - } -} - -function ReportCodeCoverage { - if ($env:APPVEYOR) { - if ($IsWindows) { - dotnet codecov -f "**\coverage.cobertura.xml" - } - } - else { - dotnet reportgenerator -reports:**\coverage.cobertura.xml -targetdir:artifacts\coverage - } - - CheckLastExitCode -} - -function CreateNuGetPackage { - if ($env:APPVEYOR_REPO_TAG -eq $true) { - # Get the version suffix from the repo tag. Example: v1.0.0-preview1-final => preview1-final - $segments = $env:APPVEYOR_REPO_TAG_NAME -split "-" - $suffixSegments = $segments[1..2] - $versionSuffix = $suffixSegments -join "-" - } - else { - # Get the version suffix from the auto-incrementing build number. Example: "123" => "master-0123". - if ($env:APPVEYOR_BUILD_NUMBER) { - $revision = "{0:D4}" -f [convert]::ToInt32($env:APPVEYOR_BUILD_NUMBER, 10) - $versionSuffix = "$($env:APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH ?? $env:APPVEYOR_REPO_BRANCH)-$revision" - } - else { - $versionSuffix = "pre-0001" - } - } - - if ([string]::IsNullOrWhitespace($versionSuffix)) { - dotnet pack --no-restore --no-build --configuration Release --output .\artifacts - } - else { - dotnet pack --no-restore --no-build --configuration Release --output .\artifacts --version-suffix=$versionSuffix - } - - CheckLastExitCode -} +Write-Host "$(pwsh --version)" +Write-Host "Active .NET SDK: $(dotnet --version)" +Write-Host "Using version suffix: $versionSuffix" dotnet tool restore -CheckLastExitCode - -dotnet build -c Release -CheckLastExitCode +VerifySuccessExitCode -RunInspectCode -RunCleanupCode +dotnet build --configuration Release /p:VersionSuffix=$versionSuffix +VerifySuccessExitCode -dotnet test -c Release --no-build --collect:"XPlat Code Coverage" -CheckLastExitCode +dotnet test --no-build --configuration Release --collect:"XPlat Code Coverage" -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.DeterministicReport=true +VerifySuccessExitCode -ReportCodeCoverage +dotnet reportgenerator -reports:**\coverage.cobertura.xml -targetdir:artifacts\coverage -filefilters:-*.g.cs +VerifySuccessExitCode -CreateNuGetPackage +dotnet pack --no-build --configuration Release --output artifacts/packages /p:VersionSuffix=$versionSuffix +VerifySuccessExitCode diff --git a/CSharpGuidelinesAnalyzer.config b/CSharpGuidelinesAnalyzer.config index acd0856..89b568e 100644 --- a/CSharpGuidelinesAnalyzer.config +++ b/CSharpGuidelinesAnalyzer.config @@ -1,5 +1,5 @@ - + diff --git a/Directory.Build.props b/Directory.Build.props index c56c8eb..b5169a9 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,37 +1,53 @@ - - net6.0 - 6.0.* - 5.3.0 - 2.20.0 - 5.3.1 - $(MSBuildThisFileDirectory)CodingGuidelines.ruleset - 9999 - enable - enable - false - false + + $(NoWarn);AV2210 - - - - - - - $(NoWarn);1591;NU5104 + $(NoWarn);1591 true true - - $(NoWarn);AV2210 + + true - + + net6.0 + 5.4.0 + 2.20.0 + + 4.1.0 + + + 6.0.* + 34.0.* + 3.8.* 6.0.* - 17.6.* + 1.1.* + 6.12.* + 2.3.* + 1.3.* + 2023.2.* + 1.1.* + 17.7.* + 2.5.* + + + + + + + + + + enable + enable + false + false + $(MSBuildThisFileDirectory)CodingGuidelines.ruleset + 5.4.0 diff --git a/LICENSE b/LICENSE index 509975f..7bfce65 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,22 @@ -The MIT License (MIT) Copyright (c) 2020 Alvaro Nicoli -Permission is hereby granted, free of charge, to any person -obtaining a copy of this software and associated documentation -files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of the Software, -and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: +MIT License -The above copyright notice and this permission notice shall be included -in all copies or substantial portions of the Software. +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -USE OR OTHER DEALINGS IN THE SOFTWARE. +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/NuGet.config b/NuGet.config deleted file mode 100644 index aacab5d..0000000 --- a/NuGet.config +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/README.md b/README.md index a1f85f9..8e93d08 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Plug-n-play implementation of `IResourceRepository` allowing you to use [MongoDB](https://www.mongodb.com/) with your [JsonApiDotNetCore](https://github.com/json-api-dotnet/JsonApiDotNetCore) APIs. -[![Build](https://ci.appveyor.com/api/projects/status/dadm2kr2y0353mji/branch/master?svg=true)](https://ci.appveyor.com/project/json-api-dotnet/jsonapidotnetcore-mongodb/branch/master) +[![Build](https://github.com/json-api-dotnet/JsonApiDotNetCore.MongoDb/actions/workflows/build.yml/badge.svg?branch=master)](https://github.com/json-api-dotnet/JsonApiDotNetCore.MongoDb/actions/workflows/build.yml?query=branch%3Amaster) [![Coverage](https://codecov.io/gh/json-api-dotnet/JsonApiDotNetCore.MongoDb/branch/master/graph/badge.svg?token=QPVf8rii7l)](https://codecov.io/gh/json-api-dotnet/JsonApiDotNetCore.MongoDb) [![NuGet](https://img.shields.io/nuget/v/JsonApiDotNetCore.MongoDb.svg)](https://www.nuget.org/packages/JsonApiDotNetCore.MongoDb/) @@ -93,15 +93,24 @@ Have a question, found a bug or want to submit code changes? See our [contributi ## Trying out the latest build -After each commit to the master branch, a new prerelease NuGet package is automatically published to AppVeyor at https://ci.appveyor.com/nuget/jsonapidotnetcore-mongodb. To try it out, follow the next steps: - -* In Visual Studio: **Tools**, **NuGet Package Manager**, **Package Manager Settings**, **Package Sources** - * Click **+** - * Name: **AppVeyor JADNC MongoDb**, Source: **https://ci.appveyor.com/nuget/jsonapidotnetcore-mongodb** - * Click **Update**, **Ok** -* Open the NuGet package manager console (**Tools**, **NuGet Package Manager**, **Package Manager Console**) - * Select **AppVeyor JADNC MongoDb** as package source - * Run command: `Install-Package JonApiDotNetCore -pre` +After each commit to the master branch, a new pre-release NuGet package is automatically published to [GitHub Packages](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-nuget-registry). +To try it out, follow the steps below: + +1. [Create a Personal Access Token (classic)](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-personal-access-token-classic) with at least `read:packages` scope. +1. Add our package source to your local user-specific `nuget.config` file by running: + ```bash + dotnet nuget add source https://nuget.pkg.github.com/json-api-dotnet/index.json --name github-json-api --username YOUR-GITHUB-USERNAME --password YOUR-PAT-CLASSIC + ``` + In the command above: + - Replace YOUR-GITHUB-USERNAME with the username you use to login your GitHub account. + - Replace YOUR-PAT-CLASSIC with the token your created above. + + :warning: If the above command doesn't give you access in the next step, remove the package source by running: + ```bash + dotnet nuget remove source github-json-api + ``` + and retry with the `--store-password-in-clear-text` switch added. +1. Restart your IDE, open your project, and browse the list of packages from the github-json-api feed (make sure pre-release packages are included). ## Development @@ -129,7 +138,7 @@ And then to run the API: dotnet run --project src/Examples/GettingStarted ``` -Alternatively, to build and validate the code, run all tests, generate code coverage and produce the NuGet package: +Alternatively, to build, run all tests, generate code coverage and NuGet packages: ```bash pwsh Build.ps1 diff --git a/appveyor.yml b/appveyor.yml index 18aa690..1ad1455 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,54 +1,12 @@ -image: - - Ubuntu2004 - - Visual Studio 2022 +image: Visual Studio 2022 version: '{build}' branches: only: - master - - develop - - unstable - /release\/.+/ -pull_requests: - do_not_increment_build_number: true - -nuget: - disable_publish_on_pr: true - -matrix: - fast_finish: true - -for: -- - matrix: - only: - - image: Visual Studio 2022 - artifacts: - - path: .\**\artifacts\**\*.nupkg - name: NuGet - deploy: - - provider: NuGet - skip_symbols: false - api_key: - secure: hlP/zkfkHzmutSXPYAiINmPdv+QEj3TpAjKewHEkCtQnHnA2tSo+Xey0g6FVM6S5 - on: - branch: master - appveyor_repo_tag: true - - provider: NuGet - skip_symbols: false - api_key: - secure: hlP/zkfkHzmutSXPYAiINmPdv+QEj3TpAjKewHEkCtQnHnA2tSo+Xey0g6FVM6S5 - on: - branch: /release\/.+/ - appveyor_repo_tag: true - -build_script: -- pwsh: | - Write-Output ".NET version:" - dotnet --version - - .\Build.ps1 - -test: off \ No newline at end of file +build: off +test: off +deploy: off diff --git a/cleanupcode.ps1 b/cleanupcode.ps1 index 717f369..23f2a36 100644 --- a/cleanupcode.ps1 +++ b/cleanupcode.ps1 @@ -28,12 +28,12 @@ if ($revision) { if ($baseCommitHash -eq $headCommitHash) { Write-Output "Running code cleanup on staged/unstaged files." - dotnet regitlint -s JsonApiDotNetCore.MongoDb.sln --print-command --skip-tool-check --jb-profile="JADNC Full Cleanup" --jb --properties:Configuration=Release --jb --verbosity=WARN -f staged,modified + dotnet regitlint -s JsonApiDotNetCore.MongoDb.sln --print-command --skip-tool-check --max-runs=5 --jb-profile="JADNC Full Cleanup" --jb --properties:Configuration=Release --jb --verbosity=WARN -f staged,modified VerifySuccessExitCode } else { Write-Output "Running code cleanup on commit range $baseCommitHash..$headCommitHash, including staged/unstaged files." - dotnet regitlint -s JsonApiDotNetCore.MongoDb.sln --print-command --skip-tool-check --jb-profile="JADNC Full Cleanup" --jb --properties:Configuration=Release --jb --verbosity=WARN -f staged,modified,commits -a $headCommitHash -b $baseCommitHash + dotnet regitlint -s JsonApiDotNetCore.MongoDb.sln --print-command --skip-tool-check --max-runs=5 --jb-profile="JADNC Full Cleanup" --jb --properties:Configuration=Release --jb --verbosity=WARN -f staged,modified,commits -a $headCommitHash -b $baseCommitHash VerifySuccessExitCode } } diff --git a/inspectcode.ps1 b/inspectcode.ps1 index 0c55d7b..dc81b09 100644 --- a/inspectcode.ps1 +++ b/inspectcode.ps1 @@ -4,16 +4,16 @@ dotnet tool restore -if ($LASTEXITCODE -ne 0) { - throw "Tool restore failed with exit code $LASTEXITCODE" +if ($LastExitCode -ne 0) { + throw "Tool restore failed with exit code $LastExitCode" } $outputPath = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), 'jetbrains-inspectcode-results.xml') $resultPath = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), 'jetbrains-inspectcode-results.html') dotnet jb inspectcode JsonApiDotNetCore.MongoDb.sln --build --output="$outputPath" --profile=WarningSeverities.DotSettings --properties:Configuration=Release --severity=WARNING --verbosity=WARN -dsl=GlobalAll -dsl=GlobalPerProduct -dsl=SolutionPersonal -dsl=ProjectPersonal -if ($LASTEXITCODE -ne 0) { - throw "Code inspection failed with exit code $LASTEXITCODE" +if ($LastExitCode -ne 0) { + throw "Code inspection failed with exit code $LastExitCode" } [xml]$xml = Get-Content "$outputPath" diff --git a/src/JsonApiDotNetCore.MongoDb/ArgumentGuard.cs b/src/JsonApiDotNetCore.MongoDb/ArgumentGuard.cs index c7c9908..5063b63 100644 --- a/src/JsonApiDotNetCore.MongoDb/ArgumentGuard.cs +++ b/src/JsonApiDotNetCore.MongoDb/ArgumentGuard.cs @@ -3,7 +3,6 @@ using SysNotNull = System.Diagnostics.CodeAnalysis.NotNullAttribute; #pragma warning disable AV1008 // Class should not be static -#pragma warning disable AV1553 // Do not use optional parameters with default value null for strings, collections or tasks namespace JsonApiDotNetCore.MongoDb; diff --git a/src/JsonApiDotNetCore.MongoDb/AtomicOperations/MongoTransaction.cs b/src/JsonApiDotNetCore.MongoDb/AtomicOperations/MongoTransaction.cs index 7aa1388..3514e87 100644 --- a/src/JsonApiDotNetCore.MongoDb/AtomicOperations/MongoTransaction.cs +++ b/src/JsonApiDotNetCore.MongoDb/AtomicOperations/MongoTransaction.cs @@ -4,7 +4,7 @@ namespace JsonApiDotNetCore.MongoDb.AtomicOperations; -/// +/// [PublicAPI] public sealed class MongoTransaction : IOperationsTransaction { diff --git a/src/JsonApiDotNetCore.MongoDb/JsonApiDotNetCore.MongoDb.csproj b/src/JsonApiDotNetCore.MongoDb/JsonApiDotNetCore.MongoDb.csproj index 7b9f43a..50df50f 100644 --- a/src/JsonApiDotNetCore.MongoDb/JsonApiDotNetCore.MongoDb.csproj +++ b/src/JsonApiDotNetCore.MongoDb/JsonApiDotNetCore.MongoDb.csproj @@ -21,10 +21,6 @@ embedded - - true - - @@ -35,9 +31,9 @@ - - - - + + + + diff --git a/src/JsonApiDotNetCore.MongoDb/Queries/Internal/HideRelationshipsSparseFieldSetCache.cs b/src/JsonApiDotNetCore.MongoDb/Queries/Internal/HideRelationshipsSparseFieldSetCache.cs index 505bf89..aeebe64 100644 --- a/src/JsonApiDotNetCore.MongoDb/Queries/Internal/HideRelationshipsSparseFieldSetCache.cs +++ b/src/JsonApiDotNetCore.MongoDb/Queries/Internal/HideRelationshipsSparseFieldSetCache.cs @@ -7,7 +7,7 @@ namespace JsonApiDotNetCore.MongoDb.Queries.Internal; -/// +/// public sealed class HideRelationshipsSparseFieldSetCache : ISparseFieldSetCache { private readonly SparseFieldSetCache _innerCache; diff --git a/src/JsonApiDotNetCore.MongoDb/Repositories/MongoDataAccess.cs b/src/JsonApiDotNetCore.MongoDb/Repositories/MongoDataAccess.cs index fcbff6d..8db3558 100644 --- a/src/JsonApiDotNetCore.MongoDb/Repositories/MongoDataAccess.cs +++ b/src/JsonApiDotNetCore.MongoDb/Repositories/MongoDataAccess.cs @@ -2,7 +2,7 @@ namespace JsonApiDotNetCore.MongoDb.Repositories; -/// +/// public sealed class MongoDataAccess : IMongoDataAccess { /// diff --git a/src/JsonApiDotNetCore.MongoDb/Repositories/MongoQueryExpressionValidator.cs b/src/JsonApiDotNetCore.MongoDb/Repositories/MongoQueryExpressionValidator.cs index 5e17cff..fe8ce6c 100644 --- a/src/JsonApiDotNetCore.MongoDb/Repositories/MongoQueryExpressionValidator.cs +++ b/src/JsonApiDotNetCore.MongoDb/Repositories/MongoQueryExpressionValidator.cs @@ -50,7 +50,7 @@ private void ValidateExpression(QueryExpression? expression) } } - public override QueryExpression? VisitResourceFieldChain(ResourceFieldChainExpression expression, object? argument) + public override QueryExpression VisitResourceFieldChain(ResourceFieldChainExpression expression, object? argument) { if (expression.Fields.Count > 1 || expression.Fields.First() is RelationshipAttribute) { diff --git a/src/JsonApiDotNetCore.MongoDb/Repositories/MongoRepository.cs b/src/JsonApiDotNetCore.MongoDb/Repositories/MongoRepository.cs index ea673c0..f983cef 100644 --- a/src/JsonApiDotNetCore.MongoDb/Repositories/MongoRepository.cs +++ b/src/JsonApiDotNetCore.MongoDb/Repositories/MongoRepository.cs @@ -98,7 +98,7 @@ protected virtual IMongoQueryable ApplyQueryLayer(QueryLayer queryLay IQueryable source = GetAll(); // @formatter:wrap_chained_method_calls chop_always - // @formatter:keep_existing_linebreaks true + // @formatter:wrap_before_first_method_call true QueryableHandlerExpression[] queryableHandlers = _constraintProviders .SelectMany(provider => provider.GetConstraints()) @@ -107,7 +107,7 @@ protected virtual IMongoQueryable ApplyQueryLayer(QueryLayer queryLay .OfType() .ToArray(); - // @formatter:keep_existing_linebreaks restore + // @formatter:wrap_before_first_method_call restore // @formatter:wrap_chained_method_calls restore foreach (QueryableHandlerExpression queryableHandler in queryableHandlers) @@ -131,17 +131,16 @@ private void AssertNoRelationshipsInSparseFieldSets() ResourceType resourceType = _resourceGraph.GetResourceType(); // @formatter:wrap_chained_method_calls chop_always - // @formatter:keep_existing_linebreaks true + // @formatter:wrap_before_first_method_call true bool hasRelationshipSelectors = _constraintProviders .SelectMany(provider => provider.GetConstraints()) .Select(expressionInScope => expressionInScope.Expression) .OfType() - .Any(fieldTable => - fieldTable.Table.Keys.Any(targetResourceType => !resourceType.Equals(targetResourceType)) || + .Any(fieldTable => fieldTable.Table.Keys.Any(targetResourceType => !resourceType.Equals(targetResourceType)) || fieldTable.Table.Values.Any(fieldSet => fieldSet.Fields.Any(field => field is RelationshipAttribute))); - // @formatter:keep_existing_linebreaks restore + // @formatter:wrap_before_first_method_call restore // @formatter:wrap_chained_method_calls restore if (hasRelationshipSelectors) diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/AtomicOperationsFixture.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/AtomicOperationsFixture.cs index c9029bb..e727573 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/AtomicOperationsFixture.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/AtomicOperationsFixture.cs @@ -8,15 +8,10 @@ namespace JsonApiDotNetCoreMongoDbTests.IntegrationTests.AtomicOperations; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] public sealed class AtomicOperationsFixture : IAsyncLifetime { - internal IntegrationTestContext TestContext { get; } + internal IntegrationTestContext TestContext { get; } = new(); public AtomicOperationsFixture() { - TestContext = new IntegrationTestContext - { - StartMongoDbInSingleNodeReplicaSetMode = true - }; - TestContext.UseController(); TestContext.ConfigureServicesAfterStartup(services => services.AddSingleton()); diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/BaseForAtomicOperationsTestsThatChangeOptions.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/BaseForAtomicOperationsTestsThatChangeOptions.cs new file mode 100644 index 0000000..9aa74e8 --- /dev/null +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/BaseForAtomicOperationsTestsThatChangeOptions.cs @@ -0,0 +1,85 @@ +using JsonApiDotNetCore.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace JsonApiDotNetCoreMongoDbTests.IntegrationTests.AtomicOperations; + +public abstract class BaseForAtomicOperationsTestsThatChangeOptions : IDisposable +{ + private readonly JsonApiOptionsScope _optionsScope; + + protected BaseForAtomicOperationsTestsThatChangeOptions(AtomicOperationsFixture fixture) + { + var options = (JsonApiOptions)fixture.TestContext.Factory.Services.GetRequiredService(); + _optionsScope = new JsonApiOptionsScope(options); + } + + public void Dispose() + { + _optionsScope.Dispose(); + } + + private sealed class JsonApiOptionsScope : IDisposable + { + private readonly JsonApiOptions _options; + private readonly JsonApiOptions _backupValues; + + public JsonApiOptionsScope(IJsonApiOptions options) + { + _options = (JsonApiOptions)options; + + _backupValues = new JsonApiOptions + { + Namespace = options.Namespace, + DefaultAttrCapabilities = options.DefaultAttrCapabilities, + DefaultHasOneCapabilities = options.DefaultHasOneCapabilities, + DefaultHasManyCapabilities = options.DefaultHasManyCapabilities, + IncludeJsonApiVersion = options.IncludeJsonApiVersion, + IncludeExceptionStackTraceInErrors = options.IncludeExceptionStackTraceInErrors, + IncludeRequestBodyInErrors = options.IncludeRequestBodyInErrors, + UseRelativeLinks = options.UseRelativeLinks, + TopLevelLinks = options.TopLevelLinks, + ResourceLinks = options.ResourceLinks, + RelationshipLinks = options.RelationshipLinks, + IncludeTotalResourceCount = options.IncludeTotalResourceCount, + DefaultPageSize = options.DefaultPageSize, + MaximumPageSize = options.MaximumPageSize, + MaximumPageNumber = options.MaximumPageNumber, + ValidateModelState = options.ValidateModelState, + ClientIdGeneration = options.ClientIdGeneration, + AllowUnknownQueryStringParameters = options.AllowUnknownQueryStringParameters, + AllowUnknownFieldsInRequestBody = options.AllowUnknownFieldsInRequestBody, + EnableLegacyFilterNotation = options.EnableLegacyFilterNotation, + MaximumIncludeDepth = options.MaximumIncludeDepth, + MaximumOperationsPerRequest = options.MaximumOperationsPerRequest, + TransactionIsolationLevel = options.TransactionIsolationLevel + }; + } + + public void Dispose() + { + _options.Namespace = _backupValues.Namespace; + _options.DefaultAttrCapabilities = _backupValues.DefaultAttrCapabilities; + _options.DefaultHasOneCapabilities = _backupValues.DefaultHasOneCapabilities; + _options.DefaultHasManyCapabilities = _backupValues.DefaultHasManyCapabilities; + _options.IncludeJsonApiVersion = _backupValues.IncludeJsonApiVersion; + _options.IncludeExceptionStackTraceInErrors = _backupValues.IncludeExceptionStackTraceInErrors; + _options.IncludeRequestBodyInErrors = _backupValues.IncludeRequestBodyInErrors; + _options.UseRelativeLinks = _backupValues.UseRelativeLinks; + _options.TopLevelLinks = _backupValues.TopLevelLinks; + _options.ResourceLinks = _backupValues.ResourceLinks; + _options.RelationshipLinks = _backupValues.RelationshipLinks; + _options.IncludeTotalResourceCount = _backupValues.IncludeTotalResourceCount; + _options.DefaultPageSize = _backupValues.DefaultPageSize; + _options.MaximumPageSize = _backupValues.MaximumPageSize; + _options.MaximumPageNumber = _backupValues.MaximumPageNumber; + _options.ValidateModelState = _backupValues.ValidateModelState; + _options.ClientIdGeneration = _backupValues.ClientIdGeneration; + _options.AllowUnknownQueryStringParameters = _backupValues.AllowUnknownQueryStringParameters; + _options.AllowUnknownFieldsInRequestBody = _backupValues.AllowUnknownFieldsInRequestBody; + _options.EnableLegacyFilterNotation = _backupValues.EnableLegacyFilterNotation; + _options.MaximumIncludeDepth = _backupValues.MaximumIncludeDepth; + _options.MaximumOperationsPerRequest = _backupValues.MaximumOperationsPerRequest; + _options.TransactionIsolationLevel = _backupValues.TransactionIsolationLevel; + } + } +} diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceTests.cs index a103a42..4a7e660 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceTests.cs @@ -1,8 +1,6 @@ using System.Net; using FluentAssertions; -using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Serialization.Objects; -using Microsoft.Extensions.DependencyInjection; using MongoDB.Bson; using TestBuildingBlocks; using Xunit; @@ -18,9 +16,6 @@ public sealed class AtomicCreateResourceTests public AtomicCreateResourceTests(AtomicOperationsFixture fixture) { _testContext = fixture.TestContext; - - var options = (JsonApiOptions)fixture.TestContext.Factory.Services.GetRequiredService(); - options.AllowClientGeneratedIds = false; } [Fact] diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithClientGeneratedIdTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithClientGeneratedIdTests.cs index 4f83dfb..bfd4a4e 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithClientGeneratedIdTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithClientGeneratedIdTests.cs @@ -9,17 +9,18 @@ namespace JsonApiDotNetCoreMongoDbTests.IntegrationTests.AtomicOperations.Creating; [Collection("AtomicOperationsFixture")] -public sealed class AtomicCreateResourceWithClientGeneratedIdTests +public sealed class AtomicCreateResourceWithClientGeneratedIdTests : BaseForAtomicOperationsTestsThatChangeOptions { private readonly IntegrationTestContext _testContext; private readonly OperationsFakers _fakers = new(); public AtomicCreateResourceWithClientGeneratedIdTests(AtomicOperationsFixture fixture) + : base(fixture) { _testContext = fixture.TestContext; var options = (JsonApiOptions)fixture.TestContext.Factory.Services.GetRequiredService(); - options.AllowClientGeneratedIds = true; + options.ClientIdGeneration = ClientIdGenerationMode.Required; } [Fact] diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Mixed/MaximumOperationsPerRequestTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Mixed/MaximumOperationsPerRequestTests.cs index c9205a3..b3f8d35 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Mixed/MaximumOperationsPerRequestTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Mixed/MaximumOperationsPerRequestTests.cs @@ -8,11 +8,12 @@ namespace JsonApiDotNetCoreMongoDbTests.IntegrationTests.AtomicOperations.Mixed; [Collection("AtomicOperationsFixture")] -public sealed class MaximumOperationsPerRequestTests +public sealed class MaximumOperationsPerRequestTests : BaseForAtomicOperationsTestsThatChangeOptions { private readonly IntegrationTestContext _testContext; public MaximumOperationsPerRequestTests(AtomicOperationsFixture fixture) + : base(fixture) { _testContext = fixture.TestContext; } diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/OperationsFakers.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/OperationsFakers.cs index b26135e..9804a9e 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/OperationsFakers.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/OperationsFakers.cs @@ -2,57 +2,48 @@ using Bogus; using TestBuildingBlocks; -// @formatter:wrap_chained_method_calls chop_always -// @formatter:keep_existing_linebreaks true +// @formatter:wrap_chained_method_calls chop_if_long +// @formatter:wrap_before_first_method_call true namespace JsonApiDotNetCoreMongoDbTests.IntegrationTests.AtomicOperations; internal sealed class OperationsFakers : FakerContainer { - private static readonly Lazy> LazyLanguageIsoCodes = - new(() => CultureInfo - .GetCultures(CultureTypes.NeutralCultures) - .Where(culture => !string.IsNullOrEmpty(culture.Name)) - .Select(culture => culture.Name) - .ToArray()); - - private readonly Lazy> _lazyPlaylistFaker = new(() => - new Faker() - .UseSeed(GetFakerSeed()) - .RuleFor(playlist => playlist.Name, faker => faker.Lorem.Sentence())); - - private readonly Lazy> _lazyMusicTrackFaker = new(() => - new Faker() - .UseSeed(GetFakerSeed()) - .RuleFor(musicTrack => musicTrack.Title, faker => faker.Lorem.Word()) - .RuleFor(musicTrack => musicTrack.LengthInSeconds, faker => faker.Random.Decimal(3 * 60, 5 * 60)) - .RuleFor(musicTrack => musicTrack.Genre, faker => faker.Lorem.Word()) - .RuleFor(musicTrack => musicTrack.ReleasedAt, faker => faker.Date.PastOffset() - .TruncateToWholeMilliseconds())); - - private readonly Lazy> _lazyLyricFaker = new(() => - new Faker() - .UseSeed(GetFakerSeed()) - .RuleFor(lyric => lyric.Text, faker => faker.Lorem.Text()) - .RuleFor(lyric => lyric.Format, "LRC")); - - private readonly Lazy> _lazyTextLanguageFaker = new(() => - new Faker() - .UseSeed(GetFakerSeed()) - .RuleFor(textLanguage => textLanguage.IsoCode, faker => faker.PickRandom(LazyLanguageIsoCodes.Value))); - - private readonly Lazy> _lazyPerformerFaker = new(() => - new Faker() - .UseSeed(GetFakerSeed()) - .RuleFor(performer => performer.ArtistName, faker => faker.Name.FullName()) - .RuleFor(performer => performer.BornAt, faker => faker.Date.PastOffset() - .TruncateToWholeMilliseconds())); - - private readonly Lazy> _lazyRecordCompanyFaker = new(() => - new Faker() - .UseSeed(GetFakerSeed()) - .RuleFor(recordCompany => recordCompany.Name, faker => faker.Company.CompanyName()) - .RuleFor(recordCompany => recordCompany.CountryOfResidence, faker => faker.Address.Country())); + private static readonly Lazy> LazyLanguageIsoCodes = new(() => CultureInfo + .GetCultures(CultureTypes.NeutralCultures) + .Where(culture => !string.IsNullOrEmpty(culture.Name)) + .Select(culture => culture.Name) + .ToArray()); + + private readonly Lazy> _lazyPlaylistFaker = new(() => new Faker() + .UseSeed(GetFakerSeed()) + .RuleFor(playlist => playlist.Name, faker => faker.Lorem.Sentence())); + + private readonly Lazy> _lazyMusicTrackFaker = new(() => new Faker() + .UseSeed(GetFakerSeed()) + .RuleFor(musicTrack => musicTrack.Title, faker => faker.Lorem.Word()) + .RuleFor(musicTrack => musicTrack.LengthInSeconds, faker => faker.Random.Decimal(3 * 60, 5 * 60)) + .RuleFor(musicTrack => musicTrack.Genre, faker => faker.Lorem.Word()) + .RuleFor(musicTrack => musicTrack.ReleasedAt, faker => faker.Date.PastOffset().TruncateToWholeMilliseconds())); + + private readonly Lazy> _lazyLyricFaker = new(() => new Faker() + .UseSeed(GetFakerSeed()) + .RuleFor(lyric => lyric.Text, faker => faker.Lorem.Text()) + .RuleFor(lyric => lyric.Format, "LRC")); + + private readonly Lazy> _lazyTextLanguageFaker = new(() => new Faker() + .UseSeed(GetFakerSeed()) + .RuleFor(textLanguage => textLanguage.IsoCode, faker => faker.PickRandom(LazyLanguageIsoCodes.Value))); + + private readonly Lazy> _lazyPerformerFaker = new(() => new Faker() + .UseSeed(GetFakerSeed()) + .RuleFor(performer => performer.ArtistName, faker => faker.Name.FullName()) + .RuleFor(performer => performer.BornAt, faker => faker.Date.PastOffset().TruncateToWholeMilliseconds())); + + private readonly Lazy> _lazyRecordCompanyFaker = new(() => new Faker() + .UseSeed(GetFakerSeed()) + .RuleFor(recordCompany => recordCompany.Name, faker => faker.Company.CompanyName()) + .RuleFor(recordCompany => recordCompany.CountryOfResidence, faker => faker.Address.Country())); public Faker Playlist => _lazyPlaylistFaker.Value; public Faker MusicTrack => _lazyMusicTrackFaker.Value; diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Transactions/AtomicTransactionConsistencyTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Transactions/AtomicTransactionConsistencyTests.cs index 6445fe8..4d89d74 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Transactions/AtomicTransactionConsistencyTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Transactions/AtomicTransactionConsistencyTests.cs @@ -19,8 +19,6 @@ public AtomicTransactionConsistencyTests(IntegrationTestContext(); - testContext.StartMongoDbInSingleNodeReplicaSetMode = true; - testContext.ConfigureServicesAfterStartup(services => { services.AddSingleton(); diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/Meta/MetaFakers.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/Meta/MetaFakers.cs index 8c85a0c..5f29687 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/Meta/MetaFakers.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/Meta/MetaFakers.cs @@ -1,17 +1,16 @@ using Bogus; using TestBuildingBlocks; -// @formatter:wrap_chained_method_calls chop_always -// @formatter:keep_existing_linebreaks true +// @formatter:wrap_chained_method_calls chop_if_long +// @formatter:wrap_before_first_method_call true namespace JsonApiDotNetCoreMongoDbTests.IntegrationTests.Meta; internal sealed class MetaFakers : FakerContainer { - private readonly Lazy> _lazySupportTicketFaker = new(() => - new Faker() - .UseSeed(GetFakerSeed()) - .RuleFor(supportTicket => supportTicket.Description, faker => faker.Lorem.Paragraph())); + private readonly Lazy> _lazySupportTicketFaker = new(() => new Faker() + .UseSeed(GetFakerSeed()) + .RuleFor(supportTicket => supportTicket.Description, faker => faker.Lorem.Paragraph())); public Faker SupportTicket => _lazySupportTicketFaker.Value; } diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/QueryStringFakers.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/QueryStringFakers.cs index e0b6004..3026b37 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/QueryStringFakers.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/QueryStringFakers.cs @@ -1,33 +1,30 @@ using Bogus; using TestBuildingBlocks; -// @formatter:wrap_chained_method_calls chop_always -// @formatter:keep_existing_linebreaks true +// @formatter:wrap_chained_method_calls chop_if_long +// @formatter:wrap_before_first_method_call true namespace JsonApiDotNetCoreMongoDbTests.IntegrationTests.QueryStrings; internal sealed class QueryStringFakers : FakerContainer { - private readonly Lazy> _lazyBlogFaker = new(() => - new Faker() - .UseSeed(GetFakerSeed()) - .RuleFor(blog => blog.Title, faker => faker.Lorem.Word()) - .RuleFor(blog => blog.PlatformName, faker => faker.Company.CompanyName())); + private readonly Lazy> _lazyBlogFaker = new(() => new Faker() + .UseSeed(GetFakerSeed()) + .RuleFor(blog => blog.Title, faker => faker.Lorem.Word()) + .RuleFor(blog => blog.PlatformName, faker => faker.Company.CompanyName())); - private readonly Lazy> _lazyBlogPostFaker = new(() => - new Faker() - .UseSeed(GetFakerSeed()) - .RuleFor(blogPost => blogPost.Caption, faker => faker.Lorem.Sentence()) - .RuleFor(blogPost => blogPost.Url, faker => faker.Internet.Url())); + private readonly Lazy> _lazyBlogPostFaker = new(() => new Faker() + .UseSeed(GetFakerSeed()) + .RuleFor(blogPost => blogPost.Caption, faker => faker.Lorem.Sentence()) + .RuleFor(blogPost => blogPost.Url, faker => faker.Internet.Url())); - private readonly Lazy> _lazyWebAccountFaker = new(() => - new Faker() - .UseSeed(GetFakerSeed()) - .RuleFor(webAccount => webAccount.UserName, faker => faker.Person.UserName) - .RuleFor(webAccount => webAccount.Password, faker => faker.Internet.Password()) - .RuleFor(webAccount => webAccount.DisplayName, faker => faker.Person.FullName) - .RuleFor(webAccount => webAccount.DateOfBirth, faker => faker.Person.DateOfBirth.TruncateToWholeMilliseconds()) - .RuleFor(webAccount => webAccount.EmailAddress, faker => faker.Internet.Email())); + private readonly Lazy> _lazyWebAccountFaker = new(() => new Faker() + .UseSeed(GetFakerSeed()) + .RuleFor(webAccount => webAccount.UserName, faker => faker.Person.UserName) + .RuleFor(webAccount => webAccount.Password, faker => faker.Internet.Password()) + .RuleFor(webAccount => webAccount.DisplayName, faker => faker.Person.FullName) + .RuleFor(webAccount => webAccount.DateOfBirth, faker => faker.Person.DateOfBirth.TruncateToWholeMilliseconds()) + .RuleFor(webAccount => webAccount.EmailAddress, faker => faker.Internet.Email())); public Faker Blog => _lazyBlogFaker.Value; public Faker BlogPost => _lazyBlogPostFaker.Value; diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Creating/CreateResourceTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Creating/CreateResourceTests.cs index 571b318..7797e79 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Creating/CreateResourceTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Creating/CreateResourceTests.cs @@ -1,8 +1,6 @@ using System.Net; using FluentAssertions; -using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Serialization.Objects; -using Microsoft.Extensions.DependencyInjection; using TestBuildingBlocks; using Xunit; @@ -20,9 +18,6 @@ public CreateResourceTests(IntegrationTestContext(); testContext.UseController(); testContext.UseController(); - - var options = (JsonApiOptions)testContext.Factory.Services.GetRequiredService(); - options.AllowClientGeneratedIds = false; } [Fact] diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Creating/CreateResourceWithClientGeneratedIdTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Creating/CreateResourceWithClientGeneratedIdTests.cs index 06f0ef8..9993e03 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Creating/CreateResourceWithClientGeneratedIdTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Creating/CreateResourceWithClientGeneratedIdTests.cs @@ -26,7 +26,7 @@ public CreateResourceWithClientGeneratedIdTests(IntegrationTestContext(); - options.AllowClientGeneratedIds = true; + options.ClientIdGeneration = ClientIdGenerationMode.Required; } [Fact] diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/ReadWriteFakers.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/ReadWriteFakers.cs index 6742754..504b9c3 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/ReadWriteFakers.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/ReadWriteFakers.cs @@ -2,45 +2,38 @@ using MongoDB.Bson; using TestBuildingBlocks; -// @formatter:wrap_chained_method_calls chop_always -// @formatter:keep_existing_linebreaks true +// @formatter:wrap_chained_method_calls chop_if_long +// @formatter:wrap_before_first_method_call true namespace JsonApiDotNetCoreMongoDbTests.IntegrationTests.ReadWrite; internal sealed class ReadWriteFakers : FakerContainer { - private readonly Lazy> _lazyWorkItemFaker = new(() => - new Faker() - .UseSeed(GetFakerSeed()) - .RuleFor(workItem => workItem.Description, faker => faker.Lorem.Sentence()) - .RuleFor(workItem => workItem.DueAt, faker => faker.Date.Future() - .TruncateToWholeMilliseconds()) - .RuleFor(workItem => workItem.Priority, faker => faker.PickRandom())); - - private readonly Lazy> _lazyWorkTagFaker = new(() => - new Faker() - .UseSeed(GetFakerSeed()) - .RuleFor(workTag => workTag.Text, faker => faker.Lorem.Word()) - .RuleFor(workTag => workTag.IsBuiltIn, faker => faker.Random.Bool())); - - private readonly Lazy> _lazyUserAccountFaker = new(() => - new Faker() - .UseSeed(GetFakerSeed()) - .RuleFor(userAccount => userAccount.FirstName, faker => faker.Name.FirstName()) - .RuleFor(userAccount => userAccount.LastName, faker => faker.Name.LastName())); - - private readonly Lazy> _lazyWorkItemGroupFaker = new(() => - new Faker() - .UseSeed(GetFakerSeed()) - .RuleFor(group => group.Name, faker => faker.Lorem.Word()) - .RuleFor(group => group.IsPublic, faker => faker.Random.Bool())); - - private readonly Lazy> _lazyRgbColorFaker = new(() => - new Faker() - .UseSeed(GetFakerSeed()) - .RuleFor(color => color.Id, faker => ObjectId.GenerateNewId(faker.Date.Past()) - .ToString()) - .RuleFor(color => color.DisplayName, faker => faker.Commerce.Color())); + private readonly Lazy> _lazyWorkItemFaker = new(() => new Faker() + .UseSeed(GetFakerSeed()) + .RuleFor(workItem => workItem.Description, faker => faker.Lorem.Sentence()) + .RuleFor(workItem => workItem.DueAt, faker => faker.Date.Future().TruncateToWholeMilliseconds()) + .RuleFor(workItem => workItem.Priority, faker => faker.PickRandom())); + + private readonly Lazy> _lazyWorkTagFaker = new(() => new Faker() + .UseSeed(GetFakerSeed()) + .RuleFor(workTag => workTag.Text, faker => faker.Lorem.Word()) + .RuleFor(workTag => workTag.IsBuiltIn, faker => faker.Random.Bool())); + + private readonly Lazy> _lazyUserAccountFaker = new(() => new Faker() + .UseSeed(GetFakerSeed()) + .RuleFor(userAccount => userAccount.FirstName, faker => faker.Name.FirstName()) + .RuleFor(userAccount => userAccount.LastName, faker => faker.Name.LastName())); + + private readonly Lazy> _lazyWorkItemGroupFaker = new(() => new Faker() + .UseSeed(GetFakerSeed()) + .RuleFor(group => group.Name, faker => faker.Lorem.Word()) + .RuleFor(group => group.IsPublic, faker => faker.Random.Bool())); + + private readonly Lazy> _lazyRgbColorFaker = new(() => new Faker() + .UseSeed(GetFakerSeed()) + .RuleFor(color => color.Id, faker => ObjectId.GenerateNewId(faker.Date.Past()).ToString()) + .RuleFor(color => color.DisplayName, faker => faker.Commerce.Color())); public Faker WorkItem => _lazyWorkItemFaker.Value; public Faker WorkTag => _lazyWorkTagFaker.Value; diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ResourceDefinitions/Reading/MoonDefinition.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ResourceDefinitions/Reading/MoonDefinition.cs index 26dcdb5..55340ea 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ResourceDefinitions/Reading/MoonDefinition.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ResourceDefinitions/Reading/MoonDefinition.cs @@ -29,6 +29,8 @@ public override QueryStringParameterHandlers OnRegisterQueryableHandlersFo private static IQueryable FilterByRadius(IQueryable source, StringValues parameterValue) { + // Workaround for https://youtrack.jetbrains.com/issue/RSRP-493256/Incorrect-possible-null-assignment + // ReSharper disable once AssignNullToNotNullAttribute bool isFilterOnLargerThan = bool.Parse(parameterValue.ToString()); return isFilterOnLargerThan ? source.Where(moon => moon.SolarRadius > 1m) : source.Where(moon => moon.SolarRadius <= 1m); } diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ResourceDefinitions/Reading/StarDefinition.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ResourceDefinitions/Reading/StarDefinition.cs index 455a089..5ca6551 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ResourceDefinitions/Reading/StarDefinition.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ResourceDefinitions/Reading/StarDefinition.cs @@ -52,12 +52,14 @@ public override PaginationExpression OnApplyPagination(PaginationExpression? exi { base.OnApplySparseFieldSet(existingSparseFieldSet); - // @formatter:keep_existing_linebreaks true + // @formatter:wrap_chained_method_calls chop_always + // @formatter:wrap_before_first_method_call true return existingSparseFieldSet .Including(star => star.Kind, ResourceGraph) .Excluding(star => star.IsVisibleFromEarth, ResourceGraph); - // @formatter:keep_existing_linebreaks restore + // @formatter:wrap_before_first_method_call restore + // @formatter:wrap_chained_method_calls restore } } diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ResourceDefinitions/Reading/UniverseFakers.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ResourceDefinitions/Reading/UniverseFakers.cs index 1fbd3c2..61164d4 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ResourceDefinitions/Reading/UniverseFakers.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ResourceDefinitions/Reading/UniverseFakers.cs @@ -1,34 +1,31 @@ using Bogus; using TestBuildingBlocks; -// @formatter:wrap_chained_method_calls chop_always -// @formatter:keep_existing_linebreaks true +// @formatter:wrap_chained_method_calls chop_if_long +// @formatter:wrap_before_first_method_call true namespace JsonApiDotNetCoreMongoDbTests.IntegrationTests.ResourceDefinitions.Reading; internal sealed class UniverseFakers : FakerContainer { - private readonly Lazy> _lazyStarFaker = new(() => - new Faker() - .UseSeed(GetFakerSeed()) - .RuleFor(star => star.Name, faker => faker.Random.Word()) - .RuleFor(star => star.Kind, faker => faker.PickRandom()) - .RuleFor(star => star.SolarRadius, faker => faker.Random.Decimal(.01M, 1000M)) - .RuleFor(star => star.SolarMass, faker => faker.Random.Decimal(.001M, 100M)) - .RuleFor(star => star.IsVisibleFromEarth, faker => faker.Random.Bool())); + private readonly Lazy> _lazyStarFaker = new(() => new Faker() + .UseSeed(GetFakerSeed()) + .RuleFor(star => star.Name, faker => faker.Random.Word()) + .RuleFor(star => star.Kind, faker => faker.PickRandom()) + .RuleFor(star => star.SolarRadius, faker => faker.Random.Decimal(.01M, 1000M)) + .RuleFor(star => star.SolarMass, faker => faker.Random.Decimal(.001M, 100M)) + .RuleFor(star => star.IsVisibleFromEarth, faker => faker.Random.Bool())); - private readonly Lazy> _lazyPlanetFaker = new(() => - new Faker() - .UseSeed(GetFakerSeed()) - .RuleFor(planet => planet.PublicName, faker => faker.Random.Word()) - .RuleFor(planet => planet.HasRingSystem, faker => faker.Random.Bool()) - .RuleFor(planet => planet.SolarMass, faker => faker.Random.Decimal(.001M, 100M))); + private readonly Lazy> _lazyPlanetFaker = new(() => new Faker() + .UseSeed(GetFakerSeed()) + .RuleFor(planet => planet.PublicName, faker => faker.Random.Word()) + .RuleFor(planet => planet.HasRingSystem, faker => faker.Random.Bool()) + .RuleFor(planet => planet.SolarMass, faker => faker.Random.Decimal(.001M, 100M))); - private readonly Lazy> _lazyMoonFaker = new(() => - new Faker() - .UseSeed(GetFakerSeed()) - .RuleFor(moon => moon.Name, faker => faker.Random.Word()) - .RuleFor(moon => moon.SolarRadius, faker => faker.Random.Decimal(.01M, 1000M))); + private readonly Lazy> _lazyMoonFaker = new(() => new Faker() + .UseSeed(GetFakerSeed()) + .RuleFor(moon => moon.Name, faker => faker.Random.Word()) + .RuleFor(moon => moon.SolarRadius, faker => faker.Random.Decimal(.01M, 1000M))); public Faker Star => _lazyStarFaker.Value; public Faker Planet => _lazyPlanetFaker.Value; diff --git a/test/JsonApiDotNetCoreMongoDbTests/JsonApiDotNetCoreMongoDbTests.csproj b/test/JsonApiDotNetCoreMongoDbTests/JsonApiDotNetCoreMongoDbTests.csproj index dbfe9fa..b8d099f 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/JsonApiDotNetCoreMongoDbTests.csproj +++ b/test/JsonApiDotNetCoreMongoDbTests/JsonApiDotNetCoreMongoDbTests.csproj @@ -10,7 +10,8 @@ - + + diff --git a/test/TestBuildingBlocks/IntegrationTest.cs b/test/TestBuildingBlocks/IntegrationTest.cs index a42877a..50fc3fd 100644 --- a/test/TestBuildingBlocks/IntegrationTest.cs +++ b/test/TestBuildingBlocks/IntegrationTest.cs @@ -18,7 +18,7 @@ public abstract class IntegrationTest : IAsyncLifetime static IntegrationTest() { - int maxConcurrentTestRuns = Environment.GetEnvironmentVariable("APPVEYOR") != null ? 32 : 64; + int maxConcurrentTestRuns = OperatingSystem.IsWindows() && Environment.GetEnvironmentVariable("CI") != null ? 32 : 64; ThrottleSemaphore = new SemaphoreSlim(maxConcurrentTestRuns); } diff --git a/test/TestBuildingBlocks/IntegrationTestContext.cs b/test/TestBuildingBlocks/IntegrationTestContext.cs index 826447d..f97fd98 100644 --- a/test/TestBuildingBlocks/IntegrationTestContext.cs +++ b/test/TestBuildingBlocks/IntegrationTestContext.cs @@ -1,5 +1,5 @@ -using System.Runtime.InteropServices; using System.Text.Json; +using EphemeralMongo; using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.MongoDb.Configuration; @@ -11,7 +11,6 @@ using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Mongo2Go; using MongoDB.Driver; namespace TestBuildingBlocks; @@ -32,7 +31,7 @@ public class IntegrationTestContext : Integration where TStartup : class where TMongoDbContextShim : MongoDbContextShim { - private readonly Lazy _runner; + private readonly Lazy _runner; private readonly Lazy> _lazyFactory; private readonly TestControllerProvider _testControllerProvider = new(); @@ -49,31 +48,15 @@ protected override JsonSerializerOptions SerializerOptions public WebApplicationFactory Factory => _lazyFactory.Value; - /// - /// Set this to true to enable transactions support in MongoDB. - /// - public bool StartMongoDbInSingleNodeReplicaSetMode { get; set; } - public IntegrationTestContext() { - _runner = new Lazy(StartMongoDb); + _runner = new Lazy(StartMongoDb); _lazyFactory = new Lazy>(CreateFactory); } - private MongoDbRunner StartMongoDb() + private IMongoRunner StartMongoDb() { - // Increasing maxTransactionLockRequestTimeoutMillis (default=5) as workaround for occasional - // "Unable to acquire lock" error when running tests locally. - string arguments = "--quiet --setParameter maxTransactionLockRequestTimeoutMillis=40"; - - if (!StartMongoDbInSingleNodeReplicaSetMode) - { - // MongoDbRunner watches console output to detect when the replica set has stabilized. So we can only fully - // suppress console output if not running in this mode. - arguments += RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? " --logappend --logpath NUL" : " --logpath /dev/null"; - } - - return MongoDbRunner.Start(singleNodeReplSet: StartMongoDbInSingleNodeReplicaSetMode, additionalMongodArguments: arguments); + return MongoRunnerProvider.Instance.Get(); } public void UseController() @@ -176,9 +159,10 @@ public void ConfigureServicesAfterStartup(Action? servicesCo protected override IHostBuilder CreateHostBuilder() { // @formatter:wrap_chained_method_calls chop_always - // @formatter:keep_existing_linebreaks true + // @formatter:wrap_before_first_method_call true - return Host.CreateDefaultBuilder(null) + return Host + .CreateDefaultBuilder(null) .ConfigureWebHostDefaults(webBuilder => { webBuilder.ConfigureServices(services => @@ -195,7 +179,7 @@ protected override IHostBuilder CreateHostBuilder() }); // @formatter:keep_existing_linebreaks restore - // @formatter:wrap_chained_method_calls restore + // @formatter:wrap_before_first_method_call restore } } } diff --git a/test/TestBuildingBlocks/MongoRunnerProvider.cs b/test/TestBuildingBlocks/MongoRunnerProvider.cs new file mode 100644 index 0000000..9565815 --- /dev/null +++ b/test/TestBuildingBlocks/MongoRunnerProvider.cs @@ -0,0 +1,101 @@ +using EphemeralMongo; + +#pragma warning disable AV1553 // Do not use optional parameters with default value null for strings, collections or tasks + +namespace TestBuildingBlocks; + +// Based on https://gist.github.com/asimmon/612b2d54f1a0d2b4e1115590d456e0be. +internal sealed class MongoRunnerProvider +{ + public static readonly MongoRunnerProvider Instance = new(); + + private readonly object _lockObject = new(); + private IMongoRunner? _runner; + private int _useCounter; + + private MongoRunnerProvider() + { + } + + public IMongoRunner Get() + { + lock (_lockObject) + { + if (_runner == null) + { + var runnerOptions = new MongoRunnerOptions + { + // Single-node replica set mode is required for transaction support in MongoDB. + UseSingleNodeReplicaSet = true, + KillMongoProcessesWhenCurrentProcessExits = true, + AdditionalArguments = "--quiet" + }; + + _runner = MongoRunner.Run(runnerOptions); + } + + _useCounter++; + return new MongoRunnerWrapper(this, _runner); + } + } + + private void Detach() + { + lock (_lockObject) + { + if (_runner != null) + { + _useCounter--; + + if (_useCounter == 0) + { + _runner.Dispose(); + _runner = null; + } + } + } + } + + private sealed class MongoRunnerWrapper : IMongoRunner + { + private readonly MongoRunnerProvider _owner; + private IMongoRunner? _underlyingMongoRunner; + + public string ConnectionString => _underlyingMongoRunner?.ConnectionString ?? throw new ObjectDisposedException(nameof(IMongoRunner)); + + public MongoRunnerWrapper(MongoRunnerProvider owner, IMongoRunner underlyingMongoRunner) + { + _owner = owner; + _underlyingMongoRunner = underlyingMongoRunner; + } + + public void Import(string database, string collection, string inputFilePath, string? additionalArguments = null, bool drop = false) + { + if (_underlyingMongoRunner == null) + { + throw new ObjectDisposedException(nameof(IMongoRunner)); + } + + _underlyingMongoRunner.Import(database, collection, inputFilePath, additionalArguments, drop); + } + + public void Export(string database, string collection, string outputFilePath, string? additionalArguments = null) + { + if (_underlyingMongoRunner == null) + { + throw new ObjectDisposedException(nameof(IMongoRunner)); + } + + _underlyingMongoRunner.Export(database, collection, outputFilePath, additionalArguments); + } + + public void Dispose() + { + if (_underlyingMongoRunner != null) + { + _underlyingMongoRunner = null; + _owner.Detach(); + } + } + } +} diff --git a/test/TestBuildingBlocks/TestBuildingBlocks.csproj b/test/TestBuildingBlocks/TestBuildingBlocks.csproj index 629c236..ecb2125 100644 --- a/test/TestBuildingBlocks/TestBuildingBlocks.csproj +++ b/test/TestBuildingBlocks/TestBuildingBlocks.csproj @@ -8,13 +8,17 @@ - + - - + + + + + + + - - - + +