From 70f151c38c00fb346cf8941d80e9b3659f07bdb7 Mon Sep 17 00:00:00 2001 From: Bart Koelman Date: Tue, 14 Dec 2021 12:07:08 +0100 Subject: [PATCH 1/7] Update to .NET 6 --- .config/dotnet-tools.json | 6 ++-- Build.ps1 | 13 +++++---- CodingGuidelines.ruleset | 2 +- Directory.Build.props | 27 +++++++++--------- JsonApiDotNetCore.MongoDb.sln | 7 +++-- appveyor.yml | 4 +-- cleanupcode.ps1 | 6 ++-- codecov.yml | 13 +++++++++ inspectcode.ps1 | 8 +----- logo.png | Bin 0 -> 16356 bytes .../GettingStarted/GettingStarted.csproj | 2 +- .../JsonApiDotNetCoreMongoDbExample.csproj | 2 +- .../JsonApiDotNetCore.MongoDb.csproj | 20 ++++++++++--- .../Creating/AtomicCreateResourceTests.cs | 4 +-- .../Resources/AtomicUpdateResourceTests.cs | 4 +-- ...sonApiDotNetCoreMongoDbExampleTests.csproj | 14 ++++----- .../HttpResponseMessageExtensions.cs | 4 +-- .../ObjectAssertionsExtensions.cs | 2 +- 18 files changed, 80 insertions(+), 58 deletions(-) create mode 100644 codecov.yml create mode 100644 logo.png diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 191e32f..8546a5a 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,13 +3,13 @@ "isRoot": true, "tools": { "jetbrains.resharper.globaltools": { - "version": "2021.1.3", + "version": "2021.3.0", "commands": [ "jb" ] }, "regitlint": { - "version": "2.1.4", + "version": "6.0.6", "commands": [ "regitlint" ] @@ -21,7 +21,7 @@ ] }, "dotnet-reportgenerator-globaltool": { - "version": "4.8.11", + "version": "5.0.0", "commands": [ "reportgenerator" ] diff --git a/Build.ps1 b/Build.ps1 index bb8ab69..e973ea3 100644 --- a/Build.ps1 +++ b/Build.ps1 @@ -8,7 +8,8 @@ function CheckLastExitCode { function RunInspectCode { $outputPath = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), 'jetbrains-inspectcode-results.xml') - dotnet jb inspectcode JsonApiDotNetCore.MongoDb.sln --output="$outputPath" --profile=WarningSeverities.DotSettings --properties:Configuration=Release --severity=WARNING --verbosity=WARN -dsl=GlobalAll -dsl=SolutionPersonal -dsl=ProjectPersonal + # passing --build instead of --no-build as workaround for https://youtrack.jetbrains.com/issue/RSRP-487054 + 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 CheckLastExitCode [xml]$xml = Get-Content "$outputPath" @@ -47,7 +48,7 @@ function RunCleanupCode { $mergeCommitHash = git rev-parse "HEAD" $targetCommitHash = git rev-parse "$env:APPVEYOR_REPO_BRANCH" - dotnet regitlint -s JsonApiDotNetCore.MongoDb.sln --print-command --jb --profile --jb --profile='\"JADNC Full Cleanup\"' --jb --properties:Configuration=Release --jb --verbosity=WARN -f commits -a $mergeCommitHash -b $targetCommitHash --fail-on-diff --print-diff + dotnet regitlint -s JsonApiDotNetCore.MongoDb.sln --print-command --disable-jb-path-hack --jb --profile='\"JADNC Full Cleanup\"' --jb --properties:Configuration=Release --jb --verbosity=WARN -f commits -a $mergeCommitHash -b $targetCommitHash --fail-on-diff --print-diff CheckLastExitCode } } @@ -73,10 +74,10 @@ function CreateNuGetPackage { $versionSuffix = $suffixSegments -join "-" } else { - # Get the version suffix from the auto-incrementing build number. Example: "123" => "pre-0123". + # 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 = "pre-$revision" + $versionSuffix = "$($env:APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH ?? $env:APPVEYOR_REPO_BRANCH)-$revision" } else { $versionSuffix = "pre-0001" @@ -84,10 +85,10 @@ function CreateNuGetPackage { } if ([string]::IsNullOrWhitespace($versionSuffix)) { - dotnet pack .\src\JsonApiDotNetCore.MongoDb -c Release -o .\artifacts + dotnet pack --no-restore --no-build --configuration Release --output .\artifacts } else { - dotnet pack .\src\JsonApiDotNetCore.MongoDb -c Release -o .\artifacts --version-suffix=$versionSuffix + dotnet pack --no-restore --no-build --configuration Release --output .\artifacts --version-suffix=$versionSuffix } CheckLastExitCode diff --git a/CodingGuidelines.ruleset b/CodingGuidelines.ruleset index 9447b10..05545fb 100644 --- a/CodingGuidelines.ruleset +++ b/CodingGuidelines.ruleset @@ -26,6 +26,6 @@ - + \ No newline at end of file diff --git a/Directory.Build.props b/Directory.Build.props index b7265fa..2684885 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,16 +1,20 @@ - netcoreapp3.1 - 3.1.* - 4.2.* - 2.12.* - $(SolutionDir)CodingGuidelines.ruleset + net6.0 + 6.0.* + 4.2.0 + 2.14.* + 5.0.0 + $(MSBuildThisFileDirectory)CodingGuidelines.ruleset + 9999 + false + false - - - + + + @@ -21,11 +25,8 @@ - 33.0.2 - 3.0.3 - 5.10.3 + 3.1.0 4.16.1 - 2.4.* - 16.10.0 + 17.0.0 diff --git a/JsonApiDotNetCore.MongoDb.sln b/JsonApiDotNetCore.MongoDb.sln index 4a2fce5..1e7504a 100644 --- a/JsonApiDotNetCore.MongoDb.sln +++ b/JsonApiDotNetCore.MongoDb.sln @@ -1,6 +1,7 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30804.86 + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31919.166 MinimumVisualStudioVersion = 15.0.26124.0 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{7E29AA10-F938-4CF8-9CAB-7ACD2D6DC784}" EndProject diff --git a/appveyor.yml b/appveyor.yml index a9427c0..fcbd840 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,6 +1,6 @@ image: - Ubuntu - - Visual Studio 2019 + - Visual Studio 2022 version: '{build}' @@ -24,7 +24,7 @@ for: - matrix: only: - - image: Visual Studio 2019 + - image: Visual Studio 2022 artifacts: - path: .\**\artifacts\**\*.nupkg name: NuGet diff --git a/cleanupcode.ps1 b/cleanupcode.ps1 index 17b143e..2d9cdca 100644 --- a/cleanupcode.ps1 +++ b/cleanupcode.ps1 @@ -8,10 +8,10 @@ if ($LASTEXITCODE -ne 0) { throw "Tool restore failed with exit code $LASTEXITCODE" } -dotnet build -c Release +dotnet restore if ($LASTEXITCODE -ne 0) { - throw "Build failed with exit code $LASTEXITCODE" + throw "Package restore failed with exit code $LASTEXITCODE" } -dotnet regitlint -s JsonApiDotNetCore.MongoDb.sln --print-command --jb --profile --jb --profile='\"JADNC Full Cleanup\"' --jb --properties:Configuration=Release --jb --verbosity=WARN +dotnet regitlint -s JsonApiDotNetCore.MongoDb.sln --print-command --disable-jb-path-hack --jb --profile='\"JADNC Full Cleanup\"' --jb --properties:Configuration=Release --jb --verbosity=WARN diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..32a5184 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,13 @@ +coverage: + status: + project: + default: + target: auto + threshold: 10% + informational: true + patch: + default: + informational: true + +github_checks: + annotations: false diff --git a/inspectcode.ps1 b/inspectcode.ps1 index 2f983ca..0c55d7b 100644 --- a/inspectcode.ps1 +++ b/inspectcode.ps1 @@ -8,15 +8,9 @@ if ($LASTEXITCODE -ne 0) { throw "Tool restore failed with exit code $LASTEXITCODE" } -dotnet build -c Release - -if ($LASTEXITCODE -ne 0) { - throw "Build 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 --output="$outputPath" --profile=WarningSeverities.DotSettings --properties:Configuration=Release --severity=WARNING --verbosity=WARN -dsl=GlobalAll -dsl=SolutionPersonal -dsl=ProjectPersonal +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" diff --git a/logo.png b/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..78f1acd5214eb049971fcd5f81dc6222f7904d20 GIT binary patch literal 16356 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 VPtP;)V=^B~R#Hi#R?H;m{{Y9ir3nB4 literal 0 HcmV?d00001 diff --git a/src/Examples/GettingStarted/GettingStarted.csproj b/src/Examples/GettingStarted/GettingStarted.csproj index 1d4e58f..a5b25ea 100644 --- a/src/Examples/GettingStarted/GettingStarted.csproj +++ b/src/Examples/GettingStarted/GettingStarted.csproj @@ -1,6 +1,6 @@ - $(NetCoreAppVersion) + $(TargetFrameworkName) diff --git a/src/Examples/JsonApiDotNetCoreMongoDbExample/JsonApiDotNetCoreMongoDbExample.csproj b/src/Examples/JsonApiDotNetCoreMongoDbExample/JsonApiDotNetCoreMongoDbExample.csproj index 1d4e58f..a5b25ea 100644 --- a/src/Examples/JsonApiDotNetCoreMongoDbExample/JsonApiDotNetCoreMongoDbExample.csproj +++ b/src/Examples/JsonApiDotNetCoreMongoDbExample/JsonApiDotNetCoreMongoDbExample.csproj @@ -1,6 +1,6 @@ - $(NetCoreAppVersion) + $(TargetFrameworkName) diff --git a/src/JsonApiDotNetCore.MongoDb/JsonApiDotNetCore.MongoDb.csproj b/src/JsonApiDotNetCore.MongoDb/JsonApiDotNetCore.MongoDb.csproj index 33134cf..8be375b 100644 --- a/src/JsonApiDotNetCore.MongoDb/JsonApiDotNetCore.MongoDb.csproj +++ b/src/JsonApiDotNetCore.MongoDb/JsonApiDotNetCore.MongoDb.csproj @@ -1,27 +1,39 @@ - + - 4.2.0 - $(NetCoreAppVersion) + $(TargetFrameworkName) + true true - jsonapi;json:api;dotnet;core;MongoDB + $(JsonApiDotNetCoreMongoDbVersionPrefix) + jsonapi;json:api;dotnet;asp.net;rest;web-api;MongoDB Persistence layer implementation for use of MongoDB in APIs using JsonApiDotNetCore. + json-api-dotnet https://github.com/json-api-dotnet/JsonApiDotNetCore.MongoDb MIT false + See https://github.com/json-api-dotnet/JsonApiDotNetCore.MongoDb/releases. + logo.png true true embedded + + + True + + + + + diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceTests.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceTests.cs index 3d90ce9..30491ed 100644 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceTests.cs +++ b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceTests.cs @@ -81,7 +81,7 @@ await _testContext.RunOnDatabaseAsync(async db => Performer performerInDatabase = await db.GetCollection().AsQueryable().FirstWithIdAsync(newPerformerId); performerInDatabase.ArtistName.Should().Be(newArtistName); - performerInDatabase.BornAt.Should().BeCloseTo(newBornAt); + performerInDatabase.BornAt.Should().BeCloseTo(newBornAt, TimeSpan.FromMilliseconds(20)); }); } @@ -164,7 +164,7 @@ await _testContext.RunOnDatabaseAsync(async db => trackInDatabase.Title.Should().Be(newTracks[index].Title); trackInDatabase.LengthInSeconds.Should().BeApproximately(newTracks[index].LengthInSeconds); trackInDatabase.Genre.Should().Be(newTracks[index].Genre); - trackInDatabase.ReleasedAt.Should().BeCloseTo(newTracks[index].ReleasedAt); + trackInDatabase.ReleasedAt.Should().BeCloseTo(newTracks[index].ReleasedAt, TimeSpan.FromMilliseconds(20)); } }); } diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateResourceTests.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateResourceTests.cs index d029961..5c1be0a 100644 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateResourceTests.cs +++ b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateResourceTests.cs @@ -193,7 +193,7 @@ await _testContext.RunOnDatabaseAsync(async db => trackInDatabase.Title.Should().Be(existingTrack.Title); trackInDatabase.LengthInSeconds.Should().BeApproximately(existingTrack.LengthInSeconds); trackInDatabase.Genre.Should().Be(newGenre); - trackInDatabase.ReleasedAt.Should().BeCloseTo(existingTrack.ReleasedAt); + trackInDatabase.ReleasedAt.Should().BeCloseTo(existingTrack.ReleasedAt, TimeSpan.FromMilliseconds(20)); }); } @@ -253,7 +253,7 @@ await _testContext.RunOnDatabaseAsync(async db => trackInDatabase.Title.Should().Be(newTitle); trackInDatabase.LengthInSeconds.Should().BeApproximately(newLengthInSeconds); trackInDatabase.Genre.Should().Be(newGenre); - trackInDatabase.ReleasedAt.Should().BeCloseTo(newReleasedAt); + trackInDatabase.ReleasedAt.Should().BeCloseTo(newReleasedAt, TimeSpan.FromMilliseconds(20)); }); } diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/JsonApiDotNetCoreMongoDbExampleTests.csproj b/test/JsonApiDotNetCoreMongoDbExampleTests/JsonApiDotNetCoreMongoDbExampleTests.csproj index dd43933..91c845e 100644 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/JsonApiDotNetCoreMongoDbExampleTests.csproj +++ b/test/JsonApiDotNetCoreMongoDbExampleTests/JsonApiDotNetCoreMongoDbExampleTests.csproj @@ -1,6 +1,6 @@ - $(NetCoreAppVersion) + $(TargetFrameworkName) @@ -14,14 +14,14 @@ - + - - - + + + - - + + diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/TestBuildingBlocks/HttpResponseMessageExtensions.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/TestBuildingBlocks/HttpResponseMessageExtensions.cs index aa0aa17..8da0cb4 100644 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/TestBuildingBlocks/HttpResponseMessageExtensions.cs +++ b/test/JsonApiDotNetCoreMongoDbExampleTests/TestBuildingBlocks/HttpResponseMessageExtensions.cs @@ -21,9 +21,9 @@ public sealed class HttpResponseMessageAssertions : ReferenceTypeAssertions "response"; - public HttpResponseMessageAssertions(HttpResponseMessage instance) + public HttpResponseMessageAssertions(HttpResponseMessage subject) + : base(subject) { - Subject = instance; } // ReSharper disable once UnusedMethodReturnValue.Global diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/TestBuildingBlocks/ObjectAssertionsExtensions.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/TestBuildingBlocks/ObjectAssertionsExtensions.cs index bce7b9a..4c3853f 100644 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/TestBuildingBlocks/ObjectAssertionsExtensions.cs +++ b/test/JsonApiDotNetCoreMongoDbExampleTests/TestBuildingBlocks/ObjectAssertionsExtensions.cs @@ -30,7 +30,7 @@ public static void BeCloseTo(this ObjectAssertions source, DateTimeOffset? expec } // We lose a little bit of precision (milliseconds) on roundtrip through MongoDB database. - value.Should().BeCloseTo(expected.Value, because: because, becauseArgs: becauseArgs); + value.Should().BeCloseTo(expected.Value, TimeSpan.FromMilliseconds(20), because, becauseArgs); } } From ead3a58a5616ffc2105752e976ec5c6266fee8e8 Mon Sep 17 00:00:00 2001 From: Bart Koelman Date: Tue, 14 Dec 2021 12:17:26 +0100 Subject: [PATCH 2/7] Reformat solution --- Directory.Build.props | 1 + JsonApiDotNetCore.MongoDb.sln.DotSettings | 13 +- .../Controllers/BooksController.cs | 12 +- src/Examples/GettingStarted/Models/Book.cs | 21 +- src/Examples/GettingStarted/Program.cs | 24 +- src/Examples/GettingStarted/Startup.cs | 118 ++- .../Controllers/OperationsController.cs | 14 +- .../Controllers/TodoItemsController.cs | 12 +- .../Models/TodoItem.cs | 22 +- .../Program.cs | 25 +- .../Startups/EmptyStartup.cs | 25 +- .../Startups/Startup.cs | 91 +- .../ArgumentGuard.cs | 20 +- .../AtomicOperations/MongoTransaction.cs | 75 +- .../MongoTransactionFactory.cs | 55 +- .../ServiceCollectionExtensions.cs | 25 +- ...ComparisonInFilterNotSupportedException.cs | 27 +- .../UnsupportedRelationshipException.cs | 25 +- .../MongoQueryableBuilder.cs | 53 +- .../MongoWhereClauseBuilder.cs | 56 +- .../Repositories/IMongoDataAccess.cs | 34 +- .../Repositories/MongoDataAccess.cs | 48 +- .../Repositories/MongoEntityType.cs | 174 ++-- .../Repositories/MongoModel.cs | 78 +- .../Repositories/MongoProperty.cs | 57 +- .../MongoQueryExpressionValidator.cs | 70 +- .../Repositories/MongoRepository.cs | 432 +++++----- .../Resources/MongoIdentifiable.cs | 41 +- ...ationshipsResponseResourceObjectBuilder.cs | 35 +- .../AtomicOperationsFixture.cs | 30 +- .../AtomicOperationsTestCollection.cs | 15 +- .../Creating/AtomicCreateResourceTests.cs | 316 ++++--- ...reateResourceWithClientGeneratedIdTests.cs | 247 +++--- ...eateResourceWithToManyRelationshipTests.cs | 114 ++- ...reateResourceWithToOneRelationshipTests.cs | 91 +- .../Deleting/AtomicDeleteResourceTests.cs | 218 +++-- .../LocalIds/AtomicLocalIdTests.cs | 227 +++-- .../AtomicOperations/Lyric.cs | 34 +- .../Meta/AtomicResourceMetaTests.cs | 169 ++-- .../Meta/MusicTrackMetaDefinition.cs | 26 +- .../Meta/TextLanguageMetaDefinition.cs | 30 +- .../Mixed/MaximumOperationsPerRequestTests.cs | 84 +- .../AtomicOperations/MusicTrack.cs | 67 +- .../AtomicOperations/MusicTracksController.cs | 11 +- .../AtomicOperations/OperationsFakers.cs | 108 ++- .../AtomicOperations/Performer.cs | 18 +- .../AtomicOperations/Playlist.cs | 32 +- .../AtomicOperations/PlaylistMusicTrack.cs | 18 +- .../AtomicOperations/RecordCompany.cs | 30 +- .../AtomicOperations/TextLanguage.cs | 35 +- .../Transactions/AtomicRollbackTests.cs | 303 ++++--- .../AtomicTransactionConsistencyTests.cs | 183 ++-- .../Transactions/LyricRepository.cs | 41 +- .../Transactions/MusicTrackRepository.cs | 22 +- .../Transactions/PerformerRepository.cs | 92 +- .../AtomicAddToToManyRelationshipTests.cs | 183 ++-- ...AtomicRemoveFromToManyRelationshipTests.cs | 187 ++--- .../AtomicReplaceToManyRelationshipTests.cs | 183 ++-- .../AtomicUpdateToOneRelationshipTests.cs | 99 ++- .../AtomicReplaceToManyRelationshipTests.cs | 103 ++- .../Resources/AtomicUpdateResourceTests.cs | 492 ++++++----- .../AtomicUpdateToOneRelationshipTests.cs | 97 ++- .../Meta/ResourceMetaTests.cs | 70 +- .../IntegrationTests/Meta/SupportFakers.cs | 18 +- .../IntegrationTests/Meta/SupportTicket.cs | 13 +- .../Meta/SupportTicketDefinition.cs | 33 +- .../Meta/SupportTicketsController.cs | 11 +- .../Meta/TopLevelCountTests.cs | 185 ++--- .../QueryStrings/AccountPreferences.cs | 13 +- .../QueryStrings/Appointment.cs | 22 +- .../IntegrationTests/QueryStrings/Blog.cs | 35 +- .../IntegrationTests/QueryStrings/BlogPost.cs | 66 +- .../QueryStrings/BlogPostLabel.cs | 17 +- .../QueryStrings/BlogPostsController.cs | 11 +- .../QueryStrings/BlogsController.cs | 11 +- .../IntegrationTests/QueryStrings/Calendar.cs | 24 +- .../QueryStrings/CalendarsController.cs | 11 +- .../IntegrationTests/QueryStrings/Comment.cs | 30 +- .../QueryStrings/CommentsController.cs | 11 +- .../Filtering/FilterDataTypeTests.cs | 544 ++++++------ .../Filtering/FilterDepthTests.cs | 216 +++-- .../Filtering/FilterOperatorTests.cs | 603 +++++++------- .../QueryStrings/Filtering/FilterTests.cs | 60 +- .../Filtering/FilterableResource.cs | 109 ++- .../FilterableResourcesController.cs | 12 +- .../QueryStrings/Includes/IncludeTests.cs | 57 +- .../IntegrationTests/QueryStrings/Label.cs | 28 +- .../QueryStrings/LabelColor.cs | 15 +- .../PaginationWithTotalCountTests.cs | 184 ++-- .../Pagination/RangeValidationTests.cs | 104 ++- .../QueryStrings/QueryStringFakers.cs | 112 ++- .../QueryStrings/Sorting/SortTests.cs | 310 ++++--- .../SparseFieldSets/ResourceCaptureStore.cs | 24 +- .../ResultCapturingRepository.cs | 44 +- .../SparseFieldSets/SparseFieldSetTests.cs | 430 +++++----- .../QueryStrings/WebAccount.cs | 43 +- .../QueryStrings/WebAccountsController.cs | 11 +- .../ReadWrite/Creating/CreateResourceTests.cs | 311 ++++--- ...reateResourceWithClientGeneratedIdTests.cs | 209 +++-- ...eateResourceWithToManyRelationshipTests.cs | 92 +- ...reateResourceWithToOneRelationshipTests.cs | 83 +- .../ReadWrite/Deleting/DeleteResourceTests.cs | 89 +- .../Fetching/FetchRelationshipTests.cs | 131 ++- .../ReadWrite/Fetching/FetchResourceTests.cs | 293 ++++--- .../ReadWrite/ModelWithIntId.cs | 13 +- .../ReadWrite/ModelWithIntIdsController.cs | 11 +- .../ReadWrite/ReadWriteFakers.cs | 78 +- .../IntegrationTests/ReadWrite/RgbColor.cs | 19 +- .../ReadWrite/RgbColorsController.cs | 11 +- .../AddToToManyRelationshipTests.cs | 189 ++--- .../RemoveFromToManyRelationshipTests.cs | 184 ++-- .../ReplaceToManyRelationshipTests.cs | 197 +++-- .../UpdateToOneRelationshipTests.cs | 79 +- .../ReplaceToManyRelationshipTests.cs | 217 +++-- .../Updating/Resources/UpdateResourceTests.cs | 552 ++++++------ .../Resources/UpdateToOneRelationshipTests.cs | 89 +- .../IntegrationTests/ReadWrite/UserAccount.cs | 24 +- .../ReadWrite/UserAccountsController.cs | 11 +- .../IntegrationTests/ReadWrite/WorkItem.cs | 98 ++- .../ReadWrite/WorkItemGroup.cs | 37 +- .../ReadWrite/WorkItemGroupsController.cs | 11 +- .../ReadWrite/WorkItemPriority.cs | 15 +- .../IntegrationTests/ReadWrite/WorkItemTag.cs | 17 +- .../ReadWrite/WorkItemToWorkItem.cs | 17 +- .../ReadWrite/WorkItemsController.cs | 11 +- .../IntegrationTests/ReadWrite/WorkTag.cs | 17 +- .../ResourceDefinitions/CallableResource.cs | 51 +- .../CallableResourceDefinition.cs | 159 ++-- .../CallableResourcesController.cs | 11 +- .../ResourceDefinitions/IUserRolesService.cs | 9 +- .../ResourceDefinitionQueryCallbackTests.cs | 784 +++++++++--------- .../IntegrationTests/TestableStartup.cs | 21 +- .../ServiceCollectionExtensions.cs | 13 +- .../TestBuildingBlocks/FakerContainer.cs | 87 +- .../HttpResponseMessageExtensions.cs | 77 +- .../TestBuildingBlocks/IntegrationTest.cs | 148 ++-- .../IntegrationTestConfiguration.cs | 25 +- .../IntegrationTestContext.cs | 228 +++-- .../MongoDatabaseExtensions.cs | 38 +- .../MongoQueryableExtensions.cs | 40 +- .../NeverSameResourceChangeTracker.cs | 37 +- .../ObjectAssertionsExtensions.cs | 64 +- 142 files changed, 6802 insertions(+), 7170 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 2684885..b861da1 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -7,6 +7,7 @@ 5.0.0 $(MSBuildThisFileDirectory)CodingGuidelines.ruleset 9999 + enable false false diff --git a/JsonApiDotNetCore.MongoDb.sln.DotSettings b/JsonApiDotNetCore.MongoDb.sln.DotSettings index e7bfd79..a3f03e1 100644 --- a/JsonApiDotNetCore.MongoDb.sln.DotSettings +++ b/JsonApiDotNetCore.MongoDb.sln.DotSettings @@ -27,6 +27,7 @@ JsonApiDotNetCore.MongoDb.ArgumentGuard.NotNull($EXPR$, $NAME$); SUGGESTION SUGGESTION SUGGESTION + WARNING SUGGESTION SUGGESTION SUGGESTION @@ -56,6 +57,10 @@ JsonApiDotNetCore.MongoDb.ArgumentGuard.NotNull($EXPR$, $NAME$); DO_NOT_SHOW HINT SUGGESTION + WARNING + WARNING + WARNING + WARNING SUGGESTION WARNING SUGGESTION @@ -69,6 +74,7 @@ JsonApiDotNetCore.MongoDb.ArgumentGuard.NotNull($EXPR$, $NAME$); SUGGESTION SUGGESTION SUGGESTION + WARNING WARNING WARNING WARNING @@ -76,9 +82,12 @@ JsonApiDotNetCore.MongoDb.ArgumentGuard.NotNull($EXPR$, $NAME$); SUGGESTION WARNING HINT + WARNING WARNING + WARNING + WARNING WARNING - <?xml version="1.0" encoding="utf-16"?><Profile name="JADNC Full Cleanup"><XMLReformatCode>True</XMLReformatCode><CSCodeStyleAttributes ArrangeTypeAccessModifier="True" ArrangeTypeMemberAccessModifier="True" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="True" ArrangeBraces="True" ArrangeAttributes="True" ArrangeArgumentsStyle="True" ArrangeCodeBodyStyle="True" ArrangeVarStyle="True" ArrangeTrailingCommas="True" ArrangeObjectCreation="True" ArrangeDefaultValue="True" /><CssAlphabetizeProperties>True</CssAlphabetizeProperties><JsInsertSemicolon>True</JsInsertSemicolon><FormatAttributeQuoteDescriptor>True</FormatAttributeQuoteDescriptor><CorrectVariableKindsDescriptor>True</CorrectVariableKindsDescriptor><VariablesToInnerScopesDescriptor>True</VariablesToInnerScopesDescriptor><StringToTemplatesDescriptor>True</StringToTemplatesDescriptor><JsReformatCode>True</JsReformatCode><JsFormatDocComments>True</JsFormatDocComments><RemoveRedundantQualifiersTs>True</RemoveRedundantQualifiersTs><OptimizeImportsTs>True</OptimizeImportsTs><OptimizeReferenceCommentsTs>True</OptimizeReferenceCommentsTs><PublicModifierStyleTs>True</PublicModifierStyleTs><ExplicitAnyTs>True</ExplicitAnyTs><TypeAnnotationStyleTs>True</TypeAnnotationStyleTs><RelativePathStyleTs>True</RelativePathStyleTs><AsInsteadOfCastTs>True</AsInsteadOfCastTs><HtmlReformatCode>True</HtmlReformatCode><AspOptimizeRegisterDirectives>True</AspOptimizeRegisterDirectives><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSUseAutoProperty>True</CSUseAutoProperty><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSMakeAutoPropertyGetOnly>True</CSMakeAutoPropertyGetOnly><CSArrangeQualifiers>True</CSArrangeQualifiers><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CssReformatCode>True</CssReformatCode><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSharpFormatDocComments>True</CSharpFormatDocComments><CSReorderTypeMembers>True</CSReorderTypeMembers><XAMLCollapseEmptyTags>False</XAMLCollapseEmptyTags></Profile> + <?xml version="1.0" encoding="utf-16"?><Profile name="JADNC Full Cleanup"><XMLReformatCode>True</XMLReformatCode><CSCodeStyleAttributes ArrangeTypeAccessModifier="True" ArrangeTypeMemberAccessModifier="True" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="True" ArrangeBraces="True" ArrangeAttributes="True" ArrangeArgumentsStyle="True" ArrangeCodeBodyStyle="True" ArrangeVarStyle="True" ArrangeTrailingCommas="True" ArrangeObjectCreation="True" ArrangeDefaultValue="True" ArrangeNamespaces="True" /><CssAlphabetizeProperties>True</CssAlphabetizeProperties><JsInsertSemicolon>True</JsInsertSemicolon><FormatAttributeQuoteDescriptor>True</FormatAttributeQuoteDescriptor><CorrectVariableKindsDescriptor>True</CorrectVariableKindsDescriptor><VariablesToInnerScopesDescriptor>True</VariablesToInnerScopesDescriptor><StringToTemplatesDescriptor>True</StringToTemplatesDescriptor><JsReformatCode>True</JsReformatCode><JsFormatDocComments>True</JsFormatDocComments><RemoveRedundantQualifiersTs>True</RemoveRedundantQualifiersTs><OptimizeImportsTs>True</OptimizeImportsTs><OptimizeReferenceCommentsTs>True</OptimizeReferenceCommentsTs><PublicModifierStyleTs>True</PublicModifierStyleTs><ExplicitAnyTs>True</ExplicitAnyTs><TypeAnnotationStyleTs>True</TypeAnnotationStyleTs><RelativePathStyleTs>True</RelativePathStyleTs><AsInsteadOfCastTs>True</AsInsteadOfCastTs><HtmlReformatCode>True</HtmlReformatCode><AspOptimizeRegisterDirectives>True</AspOptimizeRegisterDirectives><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSUseAutoProperty>True</CSUseAutoProperty><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSMakeAutoPropertyGetOnly>True</CSMakeAutoPropertyGetOnly><CSArrangeQualifiers>True</CSArrangeQualifiers><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CssReformatCode>True</CssReformatCode><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSharpFormatDocComments>True</CSharpFormatDocComments><CSReorderTypeMembers>True</CSReorderTypeMembers><XAMLCollapseEmptyTags>False</XAMLCollapseEmptyTags></Profile> JADNC Full Cleanup Required Required @@ -86,10 +95,12 @@ JsonApiDotNetCore.MongoDb.ArgumentGuard.NotNull($EXPR$, $NAME$); Required Conditional False + False 1 1 1 1 + False True True True diff --git a/src/Examples/GettingStarted/Controllers/BooksController.cs b/src/Examples/GettingStarted/Controllers/BooksController.cs index e9a0b28..7b3e60b 100644 --- a/src/Examples/GettingStarted/Controllers/BooksController.cs +++ b/src/Examples/GettingStarted/Controllers/BooksController.cs @@ -2,15 +2,13 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Services; -using Microsoft.Extensions.Logging; -namespace GettingStarted.Controllers +namespace GettingStarted.Controllers; + +public sealed class BooksController : JsonApiController { - public sealed class BooksController : JsonApiController + public BooksController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) + : base(options, loggerFactory, resourceService) { - public BooksController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) - : base(options, loggerFactory, resourceService) - { - } } } diff --git a/src/Examples/GettingStarted/Models/Book.cs b/src/Examples/GettingStarted/Models/Book.cs index 063b2f7..95ac0a5 100644 --- a/src/Examples/GettingStarted/Models/Book.cs +++ b/src/Examples/GettingStarted/Models/Book.cs @@ -2,18 +2,17 @@ using JsonApiDotNetCore.MongoDb.Resources; using JsonApiDotNetCore.Resources.Annotations; -namespace GettingStarted.Models +namespace GettingStarted.Models; + +[UsedImplicitly(ImplicitUseTargetFlags.Members)] +public sealed class Book : MongoIdentifiable { - [UsedImplicitly(ImplicitUseTargetFlags.Members)] - public sealed class Book : MongoIdentifiable - { - [Attr] - public string Title { get; set; } + [Attr] + public string Title { get; set; } - [Attr] - public int PublishYear { get; set; } + [Attr] + public int PublishYear { get; set; } - [Attr] - public string Author { get; set; } - } + [Attr] + public string Author { get; set; } } diff --git a/src/Examples/GettingStarted/Program.cs b/src/Examples/GettingStarted/Program.cs index 68bca0a..7661aaa 100644 --- a/src/Examples/GettingStarted/Program.cs +++ b/src/Examples/GettingStarted/Program.cs @@ -1,21 +1,17 @@ -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; +namespace GettingStarted; -namespace GettingStarted +internal static class Program { - internal static class Program + public static void Main(string[] args) { - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } + CreateHostBuilder(args).Build().Run(); + } - private static IHostBuilder CreateHostBuilder(string[] args) + private static IHostBuilder CreateHostBuilder(string[] args) + { + return Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder => { - return Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); - } + webBuilder.UseStartup(); + }); } } diff --git a/src/Examples/GettingStarted/Startup.cs b/src/Examples/GettingStarted/Startup.cs index fdb6aab..7b30784 100644 --- a/src/Examples/GettingStarted/Startup.cs +++ b/src/Examples/GettingStarted/Startup.cs @@ -2,83 +2,79 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.MongoDb.Configuration; using JsonApiDotNetCore.MongoDb.Repositories; -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; using MongoDB.Driver; using Newtonsoft.Json; -namespace GettingStarted +namespace GettingStarted; + +public sealed class Startup { - public sealed class Startup + private readonly IConfiguration _configuration; + + public Startup(IConfiguration configuration) { - private readonly IConfiguration _configuration; + _configuration = configuration; + } - public Startup(IConfiguration configuration) + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddSingleton(_ => { - _configuration = configuration; - } + var client = new MongoClient(_configuration.GetSection("DatabaseSettings:ConnectionString").Value); + return client.GetDatabase(_configuration.GetSection("DatabaseSettings:Database").Value); + }); - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) + services.AddJsonApi(ConfigureJsonApiOptions, resources: builder => { - services.AddSingleton(_ => - { - var client = new MongoClient(_configuration.GetSection("DatabaseSettings:ConnectionString").Value); - return client.GetDatabase(_configuration.GetSection("DatabaseSettings:Database").Value); - }); + builder.Add(); + }); - services.AddJsonApi(ConfigureJsonApiOptions, resources: builder => - { - builder.Add(); - }); - - services.AddJsonApiMongoDb(); + services.AddJsonApiMongoDb(); - services.AddResourceRepository>(); - } + services.AddResourceRepository>(); + } - private void ConfigureJsonApiOptions(JsonApiOptions options) - { - options.Namespace = "api"; - options.UseRelativeLinks = true; - options.IncludeTotalResourceCount = true; - options.SerializerSettings.Formatting = Formatting.Indented; - } + private void ConfigureJsonApiOptions(JsonApiOptions options) + { + options.Namespace = "api"; + options.UseRelativeLinks = true; + options.IncludeTotalResourceCount = true; + options.SerializerSettings.Formatting = Formatting.Indented; + } - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app) - { - CreateSampleData(app.ApplicationServices.GetService()); + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app) + { + CreateSampleData(app.ApplicationServices.GetService()); - app.UseRouting(); - app.UseJsonApi(); - app.UseEndpoints(endpoints => endpoints.MapControllers()); - } + app.UseRouting(); + app.UseJsonApi(); + app.UseEndpoints(endpoints => endpoints.MapControllers()); + } - private static void CreateSampleData(IMongoDatabase db) + private static void CreateSampleData(IMongoDatabase db) + { + db.GetCollection(nameof(Book)).InsertMany(new[] { - db.GetCollection(nameof(Book)).InsertMany(new[] + new Book + { + Title = "Frankenstein", + PublishYear = 1818, + Author = "Mary Shelley" + }, + new Book + { + Title = "Robinson Crusoe", + PublishYear = 1719, + Author = "Daniel Defoe" + }, + new Book { - new Book - { - Title = "Frankenstein", - PublishYear = 1818, - Author = "Mary Shelley" - }, - new Book - { - Title = "Robinson Crusoe", - PublishYear = 1719, - Author = "Daniel Defoe" - }, - new Book - { - Title = "Gulliver's Travels", - PublishYear = 1726, - Author = "Jonathan Swift" - } - }); - } + Title = "Gulliver's Travels", + PublishYear = 1726, + Author = "Jonathan Swift" + } + }); } } diff --git a/src/Examples/JsonApiDotNetCoreMongoDbExample/Controllers/OperationsController.cs b/src/Examples/JsonApiDotNetCoreMongoDbExample/Controllers/OperationsController.cs index b7d6ea3..6ecdb47 100644 --- a/src/Examples/JsonApiDotNetCoreMongoDbExample/Controllers/OperationsController.cs +++ b/src/Examples/JsonApiDotNetCoreMongoDbExample/Controllers/OperationsController.cs @@ -3,16 +3,14 @@ using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Middleware; using JsonApiDotNetCore.Resources; -using Microsoft.Extensions.Logging; -namespace JsonApiDotNetCoreMongoDbExample.Controllers +namespace JsonApiDotNetCoreMongoDbExample.Controllers; + +public sealed class OperationsController : JsonApiOperationsController { - public sealed class OperationsController : JsonApiOperationsController + public OperationsController(IJsonApiOptions options, ILoggerFactory loggerFactory, IOperationsProcessor processor, IJsonApiRequest request, + ITargetedFields targetedFields) + : base(options, loggerFactory, processor, request, targetedFields) { - public OperationsController(IJsonApiOptions options, ILoggerFactory loggerFactory, IOperationsProcessor processor, IJsonApiRequest request, - ITargetedFields targetedFields) - : base(options, loggerFactory, processor, request, targetedFields) - { - } } } diff --git a/src/Examples/JsonApiDotNetCoreMongoDbExample/Controllers/TodoItemsController.cs b/src/Examples/JsonApiDotNetCoreMongoDbExample/Controllers/TodoItemsController.cs index 584b901..1b5f5fc 100644 --- a/src/Examples/JsonApiDotNetCoreMongoDbExample/Controllers/TodoItemsController.cs +++ b/src/Examples/JsonApiDotNetCoreMongoDbExample/Controllers/TodoItemsController.cs @@ -2,15 +2,13 @@ using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Services; using JsonApiDotNetCoreMongoDbExample.Models; -using Microsoft.Extensions.Logging; -namespace JsonApiDotNetCoreMongoDbExample.Controllers +namespace JsonApiDotNetCoreMongoDbExample.Controllers; + +public sealed class TodoItemsController : JsonApiController { - public sealed class TodoItemsController : JsonApiController + public TodoItemsController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) + : base(options, loggerFactory, resourceService) { - public TodoItemsController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) - : base(options, loggerFactory, resourceService) - { - } } } diff --git a/src/Examples/JsonApiDotNetCoreMongoDbExample/Models/TodoItem.cs b/src/Examples/JsonApiDotNetCoreMongoDbExample/Models/TodoItem.cs index 42962c2..4d3bbd5 100644 --- a/src/Examples/JsonApiDotNetCoreMongoDbExample/Models/TodoItem.cs +++ b/src/Examples/JsonApiDotNetCoreMongoDbExample/Models/TodoItem.cs @@ -1,20 +1,18 @@ -using System; using JetBrains.Annotations; using JsonApiDotNetCore.MongoDb.Resources; using JsonApiDotNetCore.Resources.Annotations; -namespace JsonApiDotNetCoreMongoDbExample.Models +namespace JsonApiDotNetCoreMongoDbExample.Models; + +[UsedImplicitly(ImplicitUseTargetFlags.Members)] +public sealed class TodoItem : MongoIdentifiable { - [UsedImplicitly(ImplicitUseTargetFlags.Members)] - public sealed class TodoItem : MongoIdentifiable - { - [Attr] - public string Description { get; set; } + [Attr] + public string Description { get; set; } - [Attr] - public DateTimeOffset CreatedAt { get; set; } + [Attr] + public DateTimeOffset CreatedAt { get; set; } - [Attr] - public DateTimeOffset? CompletedAt { get; set; } - } + [Attr] + public DateTimeOffset? CompletedAt { get; set; } } diff --git a/src/Examples/JsonApiDotNetCoreMongoDbExample/Program.cs b/src/Examples/JsonApiDotNetCoreMongoDbExample/Program.cs index a50092c..461d00c 100644 --- a/src/Examples/JsonApiDotNetCoreMongoDbExample/Program.cs +++ b/src/Examples/JsonApiDotNetCoreMongoDbExample/Program.cs @@ -1,22 +1,19 @@ using JsonApiDotNetCoreMongoDbExample.Startups; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; -namespace JsonApiDotNetCoreMongoDbExample +namespace JsonApiDotNetCoreMongoDbExample; + +internal static class Program { - internal static class Program + public static void Main(string[] args) { - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } + CreateHostBuilder(args).Build().Run(); + } - private static IHostBuilder CreateHostBuilder(string[] args) + private static IHostBuilder CreateHostBuilder(string[] args) + { + return Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder => { - return Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); - } + webBuilder.UseStartup(); + }); } } diff --git a/src/Examples/JsonApiDotNetCoreMongoDbExample/Startups/EmptyStartup.cs b/src/Examples/JsonApiDotNetCoreMongoDbExample/Startups/EmptyStartup.cs index e89762d..68bda56 100644 --- a/src/Examples/JsonApiDotNetCoreMongoDbExample/Startups/EmptyStartup.cs +++ b/src/Examples/JsonApiDotNetCoreMongoDbExample/Startups/EmptyStartup.cs @@ -1,21 +1,16 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.DependencyInjection; +namespace JsonApiDotNetCoreMongoDbExample.Startups; -namespace JsonApiDotNetCoreMongoDbExample.Startups +/// +/// Empty startup class, required for integration tests. Changes in ASP.NET Core 3 no longer allow Startup class to be defined in test projects. See +/// https://github.com/aspnet/AspNetCore/issues/15373. +/// +public abstract class EmptyStartup { - /// - /// Empty startup class, required for integration tests. Changes in ASP.NET Core 3 no longer allow Startup class to be defined in test projects. See - /// https://github.com/aspnet/AspNetCore/issues/15373. - /// - public abstract class EmptyStartup + public virtual void ConfigureServices(IServiceCollection services) { - public virtual void ConfigureServices(IServiceCollection services) - { - } + } - public virtual void Configure(IApplicationBuilder app, IWebHostEnvironment environment) - { - } + public virtual void Configure(IApplicationBuilder app, IWebHostEnvironment environment) + { } } diff --git a/src/Examples/JsonApiDotNetCoreMongoDbExample/Startups/Startup.cs b/src/Examples/JsonApiDotNetCoreMongoDbExample/Startups/Startup.cs index 6902c42..3d54693 100644 --- a/src/Examples/JsonApiDotNetCoreMongoDbExample/Startups/Startup.cs +++ b/src/Examples/JsonApiDotNetCoreMongoDbExample/Startups/Startup.cs @@ -3,64 +3,59 @@ using JsonApiDotNetCore.MongoDb.Repositories; using JsonApiDotNetCore.Repositories; using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; using MongoDB.Driver; using Newtonsoft.Json; using Newtonsoft.Json.Converters; -namespace JsonApiDotNetCoreMongoDbExample.Startups +namespace JsonApiDotNetCoreMongoDbExample.Startups; + +public sealed class Startup : EmptyStartup { - public sealed class Startup : EmptyStartup + private readonly IConfiguration _configuration; + + public Startup(IConfiguration configuration) { - private readonly IConfiguration _configuration; + _configuration = configuration; + } - public Startup(IConfiguration configuration) - { - _configuration = configuration; - } + // This method gets called by the runtime. Use this method to add services to the container. + public override void ConfigureServices(IServiceCollection services) + { + services.AddSingleton(); - // This method gets called by the runtime. Use this method to add services to the container. - public override void ConfigureServices(IServiceCollection services) + services.AddSingleton(_ => { - services.AddSingleton(); - - services.AddSingleton(_ => - { - var client = new MongoClient(_configuration.GetSection("DatabaseSettings:ConnectionString").Value); - return client.GetDatabase(_configuration.GetSection("DatabaseSettings:Database").Value); - }); - - services.AddJsonApi(ConfigureJsonApiOptions, facade => facade.AddCurrentAssembly()); - services.AddJsonApiMongoDb(); - - services.AddScoped(typeof(IResourceReadRepository<>), typeof(MongoRepository<>)); - services.AddScoped(typeof(IResourceReadRepository<,>), typeof(MongoRepository<,>)); - services.AddScoped(typeof(IResourceWriteRepository<>), typeof(MongoRepository<>)); - services.AddScoped(typeof(IResourceWriteRepository<,>), typeof(MongoRepository<,>)); - services.AddScoped(typeof(IResourceRepository<>), typeof(MongoRepository<>)); - services.AddScoped(typeof(IResourceRepository<,>), typeof(MongoRepository<,>)); - } + var client = new MongoClient(_configuration.GetSection("DatabaseSettings:ConnectionString").Value); + return client.GetDatabase(_configuration.GetSection("DatabaseSettings:Database").Value); + }); + + services.AddJsonApi(ConfigureJsonApiOptions, facade => facade.AddCurrentAssembly()); + services.AddJsonApiMongoDb(); + + services.AddScoped(typeof(IResourceReadRepository<>), typeof(MongoRepository<>)); + services.AddScoped(typeof(IResourceReadRepository<,>), typeof(MongoRepository<,>)); + services.AddScoped(typeof(IResourceWriteRepository<>), typeof(MongoRepository<>)); + services.AddScoped(typeof(IResourceWriteRepository<,>), typeof(MongoRepository<,>)); + services.AddScoped(typeof(IResourceRepository<>), typeof(MongoRepository<>)); + services.AddScoped(typeof(IResourceRepository<,>), typeof(MongoRepository<,>)); + } - private void ConfigureJsonApiOptions(JsonApiOptions options) - { - options.IncludeExceptionStackTraceInErrors = true; - options.Namespace = "api/v1"; - options.DefaultPageSize = new PageSize(5); - options.IncludeTotalResourceCount = true; - options.ValidateModelState = true; - options.SerializerSettings.Formatting = Formatting.Indented; - options.SerializerSettings.Converters.Add(new StringEnumConverter()); - } + private void ConfigureJsonApiOptions(JsonApiOptions options) + { + options.IncludeExceptionStackTraceInErrors = true; + options.Namespace = "api/v1"; + options.DefaultPageSize = new PageSize(5); + options.IncludeTotalResourceCount = true; + options.ValidateModelState = true; + options.SerializerSettings.Formatting = Formatting.Indented; + options.SerializerSettings.Converters.Add(new StringEnumConverter()); + } - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public override void Configure(IApplicationBuilder app, IWebHostEnvironment environment) - { - app.UseRouting(); - app.UseJsonApi(); - app.UseEndpoints(endpoints => endpoints.MapControllers()); - } + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public override void Configure(IApplicationBuilder app, IWebHostEnvironment environment) + { + app.UseRouting(); + app.UseJsonApi(); + app.UseEndpoints(endpoints => endpoints.MapControllers()); } } diff --git a/src/JsonApiDotNetCore.MongoDb/ArgumentGuard.cs b/src/JsonApiDotNetCore.MongoDb/ArgumentGuard.cs index 08392ec..893a993 100644 --- a/src/JsonApiDotNetCore.MongoDb/ArgumentGuard.cs +++ b/src/JsonApiDotNetCore.MongoDb/ArgumentGuard.cs @@ -1,21 +1,19 @@ -using System; using JetBrains.Annotations; #pragma warning disable AV1008 // Class should not be static -namespace JsonApiDotNetCore.MongoDb +namespace JsonApiDotNetCore.MongoDb; + +internal static class ArgumentGuard { - internal static class ArgumentGuard + [AssertionMethod] + [ContractAnnotation("value: null => halt")] + public static void NotNull([CanBeNull] [NoEnumeration] T value, [NotNull] [InvokerParameterName] string name) + where T : class { - [AssertionMethod] - [ContractAnnotation("value: null => halt")] - public static void NotNull([CanBeNull] [NoEnumeration] T value, [NotNull] [InvokerParameterName] string name) - where T : class + if (value is null) { - if (value is null) - { - throw new ArgumentNullException(name); - } + throw new ArgumentNullException(name); } } } diff --git a/src/JsonApiDotNetCore.MongoDb/AtomicOperations/MongoTransaction.cs b/src/JsonApiDotNetCore.MongoDb/AtomicOperations/MongoTransaction.cs index 86ed9b4..f185215 100644 --- a/src/JsonApiDotNetCore.MongoDb/AtomicOperations/MongoTransaction.cs +++ b/src/JsonApiDotNetCore.MongoDb/AtomicOperations/MongoTransaction.cs @@ -1,57 +1,54 @@ -using System.Threading; -using System.Threading.Tasks; using JetBrains.Annotations; using JsonApiDotNetCore.AtomicOperations; using JsonApiDotNetCore.MongoDb.Repositories; -namespace JsonApiDotNetCore.MongoDb.AtomicOperations +namespace JsonApiDotNetCore.MongoDb.AtomicOperations; + +/// +[PublicAPI] +public sealed class MongoTransaction : IOperationsTransaction { - /// - [PublicAPI] - public sealed class MongoTransaction : IOperationsTransaction - { - private readonly IMongoDataAccess _mongoDataAccess; - private readonly bool _ownsTransaction; + private readonly IMongoDataAccess _mongoDataAccess; + private readonly bool _ownsTransaction; - /// - public string TransactionId => _mongoDataAccess.TransactionId; + /// + public string TransactionId => _mongoDataAccess.TransactionId; - public MongoTransaction(IMongoDataAccess mongoDataAccess, bool ownsTransaction) - { - ArgumentGuard.NotNull(mongoDataAccess, nameof(mongoDataAccess)); + public MongoTransaction(IMongoDataAccess mongoDataAccess, bool ownsTransaction) + { + ArgumentGuard.NotNull(mongoDataAccess, nameof(mongoDataAccess)); - _mongoDataAccess = mongoDataAccess; - _ownsTransaction = ownsTransaction; - } + _mongoDataAccess = mongoDataAccess; + _ownsTransaction = ownsTransaction; + } - /// - public Task BeforeProcessOperationAsync(CancellationToken cancellationToken) - { - return Task.CompletedTask; - } + /// + public Task BeforeProcessOperationAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } - /// - public Task AfterProcessOperationAsync(CancellationToken cancellationToken) - { - return Task.CompletedTask; - } + /// + public Task AfterProcessOperationAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } - /// - public async Task CommitAsync(CancellationToken cancellationToken) + /// + public async Task CommitAsync(CancellationToken cancellationToken) + { + if (_ownsTransaction) { - if (_ownsTransaction) - { - await _mongoDataAccess.ActiveSession.CommitTransactionAsync(cancellationToken); - } + await _mongoDataAccess.ActiveSession.CommitTransactionAsync(cancellationToken); } + } - /// - public async ValueTask DisposeAsync() + /// + public async ValueTask DisposeAsync() + { + if (_ownsTransaction) { - if (_ownsTransaction) - { - await _mongoDataAccess.DisposeAsync(); - } + await _mongoDataAccess.DisposeAsync(); } } } diff --git a/src/JsonApiDotNetCore.MongoDb/AtomicOperations/MongoTransactionFactory.cs b/src/JsonApiDotNetCore.MongoDb/AtomicOperations/MongoTransactionFactory.cs index 7a4c198..ba3f9a9 100644 --- a/src/JsonApiDotNetCore.MongoDb/AtomicOperations/MongoTransactionFactory.cs +++ b/src/JsonApiDotNetCore.MongoDb/AtomicOperations/MongoTransactionFactory.cs @@ -1,42 +1,39 @@ -using System.Threading; -using System.Threading.Tasks; using JsonApiDotNetCore.AtomicOperations; using JsonApiDotNetCore.MongoDb.Repositories; -namespace JsonApiDotNetCore.MongoDb.AtomicOperations +namespace JsonApiDotNetCore.MongoDb.AtomicOperations; + +/// +/// Provides transaction support for atomic:operation requests using MongoDB. +/// +public sealed class MongoTransactionFactory : IOperationsTransactionFactory { - /// - /// Provides transaction support for atomic:operation requests using MongoDB. - /// - public sealed class MongoTransactionFactory : IOperationsTransactionFactory + private readonly IMongoDataAccess _mongoDataAccess; + + public MongoTransactionFactory(IMongoDataAccess mongoDataAccess) { - private readonly IMongoDataAccess _mongoDataAccess; + ArgumentGuard.NotNull(mongoDataAccess, nameof(mongoDataAccess)); - public MongoTransactionFactory(IMongoDataAccess mongoDataAccess) - { - ArgumentGuard.NotNull(mongoDataAccess, nameof(mongoDataAccess)); + _mongoDataAccess = mongoDataAccess; + } - _mongoDataAccess = mongoDataAccess; - } + /// + public async Task BeginTransactionAsync(CancellationToken cancellationToken) + { + bool transactionCreated = await CreateOrJoinTransactionAsync(cancellationToken); + return new MongoTransaction(_mongoDataAccess, transactionCreated); + } - /// - public async Task BeginTransactionAsync(CancellationToken cancellationToken) - { - bool transactionCreated = await CreateOrJoinTransactionAsync(cancellationToken); - return new MongoTransaction(_mongoDataAccess, transactionCreated); - } + private async Task CreateOrJoinTransactionAsync(CancellationToken cancellationToken) + { + _mongoDataAccess.ActiveSession ??= await _mongoDataAccess.MongoDatabase.Client.StartSessionAsync(cancellationToken: cancellationToken); - private async Task CreateOrJoinTransactionAsync(CancellationToken cancellationToken) + if (_mongoDataAccess.ActiveSession.IsInTransaction) { - _mongoDataAccess.ActiveSession ??= await _mongoDataAccess.MongoDatabase.Client.StartSessionAsync(cancellationToken: cancellationToken); - - if (_mongoDataAccess.ActiveSession.IsInTransaction) - { - return false; - } - - _mongoDataAccess.ActiveSession.StartTransaction(); - return true; + return false; } + + _mongoDataAccess.ActiveSession.StartTransaction(); + return true; } } diff --git a/src/JsonApiDotNetCore.MongoDb/Configuration/ServiceCollectionExtensions.cs b/src/JsonApiDotNetCore.MongoDb/Configuration/ServiceCollectionExtensions.cs index 62474fa..e847d0e 100644 --- a/src/JsonApiDotNetCore.MongoDb/Configuration/ServiceCollectionExtensions.cs +++ b/src/JsonApiDotNetCore.MongoDb/Configuration/ServiceCollectionExtensions.cs @@ -6,21 +6,20 @@ using JsonApiDotNetCore.Serialization.Building; using Microsoft.Extensions.DependencyInjection; -namespace JsonApiDotNetCore.MongoDb.Configuration +namespace JsonApiDotNetCore.MongoDb.Configuration; + +public static class ServiceCollectionExtensions { - public static class ServiceCollectionExtensions + /// + /// Expands JsonApiDotNetCore configuration for usage with MongoDB. + /// + [PublicAPI] + public static IServiceCollection AddJsonApiMongoDb(this IServiceCollection services) { - /// - /// Expands JsonApiDotNetCore configuration for usage with MongoDB. - /// - [PublicAPI] - public static IServiceCollection AddJsonApiMongoDb(this IServiceCollection services) - { - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); - return services; - } + return services; } } diff --git a/src/JsonApiDotNetCore.MongoDb/Errors/AttributeComparisonInFilterNotSupportedException.cs b/src/JsonApiDotNetCore.MongoDb/Errors/AttributeComparisonInFilterNotSupportedException.cs index f6f85c7..52dd27e 100644 --- a/src/JsonApiDotNetCore.MongoDb/Errors/AttributeComparisonInFilterNotSupportedException.cs +++ b/src/JsonApiDotNetCore.MongoDb/Errors/AttributeComparisonInFilterNotSupportedException.cs @@ -3,21 +3,20 @@ using JsonApiDotNetCore.Errors; using JsonApiDotNetCore.Serialization.Objects; -namespace JsonApiDotNetCore.MongoDb.Errors +namespace JsonApiDotNetCore.MongoDb.Errors; + +/// +/// The error that is thrown when a filter compares two attributes. This is not supported by MongoDB.Driver. See +/// https://jira.mongodb.org/browse/CSHARP-1592. +/// +[PublicAPI] +public sealed class AttributeComparisonInFilterNotSupportedException : JsonApiException { - /// - /// The error that is thrown when a filter compares two attributes. This is not supported by MongoDB.Driver. See - /// https://jira.mongodb.org/browse/CSHARP-1592. - /// - [PublicAPI] - public sealed class AttributeComparisonInFilterNotSupportedException : JsonApiException - { - public AttributeComparisonInFilterNotSupportedException() - : base(new Error(HttpStatusCode.BadRequest) - { - Title = "Comparing attributes against each other is not supported when using MongoDB." - }) + public AttributeComparisonInFilterNotSupportedException() + : base(new Error(HttpStatusCode.BadRequest) { - } + Title = "Comparing attributes against each other is not supported when using MongoDB." + }) + { } } diff --git a/src/JsonApiDotNetCore.MongoDb/Errors/UnsupportedRelationshipException.cs b/src/JsonApiDotNetCore.MongoDb/Errors/UnsupportedRelationshipException.cs index 63c8f0f..e7b2a60 100644 --- a/src/JsonApiDotNetCore.MongoDb/Errors/UnsupportedRelationshipException.cs +++ b/src/JsonApiDotNetCore.MongoDb/Errors/UnsupportedRelationshipException.cs @@ -3,20 +3,19 @@ using JsonApiDotNetCore.Errors; using JsonApiDotNetCore.Serialization.Objects; -namespace JsonApiDotNetCore.MongoDb.Errors +namespace JsonApiDotNetCore.MongoDb.Errors; + +/// +/// The error that is thrown when the user attempts to fetch, create or update a relationship. +/// +[PublicAPI] +public sealed class UnsupportedRelationshipException : JsonApiException { - /// - /// The error that is thrown when the user attempts to fetch, create or update a relationship. - /// - [PublicAPI] - public sealed class UnsupportedRelationshipException : JsonApiException - { - public UnsupportedRelationshipException() - : base(new Error(HttpStatusCode.BadRequest) - { - Title = "Relationships are not supported when using MongoDB." - }) + public UnsupportedRelationshipException() + : base(new Error(HttpStatusCode.BadRequest) { - } + Title = "Relationships are not supported when using MongoDB." + }) + { } } diff --git a/src/JsonApiDotNetCore.MongoDb/Queries/Internal/QueryableBuilding/MongoQueryableBuilder.cs b/src/JsonApiDotNetCore.MongoDb/Queries/Internal/QueryableBuilding/MongoQueryableBuilder.cs index 6660e26..e469a6c 100644 --- a/src/JsonApiDotNetCore.MongoDb/Queries/Internal/QueryableBuilding/MongoQueryableBuilder.cs +++ b/src/JsonApiDotNetCore.MongoDb/Queries/Internal/QueryableBuilding/MongoQueryableBuilder.cs @@ -1,4 +1,3 @@ -using System; using System.Linq.Expressions; using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; @@ -8,36 +7,34 @@ using JsonApiDotNetCore.Resources; using Microsoft.EntityFrameworkCore.Metadata; -namespace JsonApiDotNetCore.MongoDb.Queries.Internal.QueryableBuilding +namespace JsonApiDotNetCore.MongoDb.Queries.Internal.QueryableBuilding; + +/// +/// Drives conversion from into system trees. +/// +[PublicAPI] +public sealed class MongoQueryableBuilder : QueryableBuilder { - /// - /// Drives conversion from into system trees. - /// - [PublicAPI] - public sealed class MongoQueryableBuilder : QueryableBuilder - { - private readonly Type _elementType; - private readonly Type _extensionType; - private readonly LambdaParameterNameFactory _nameFactory; - private readonly LambdaScopeFactory _lambdaScopeFactory; + private readonly Type _elementType; + private readonly Type _extensionType; + private readonly LambdaParameterNameFactory _nameFactory; + private readonly LambdaScopeFactory _lambdaScopeFactory; - public MongoQueryableBuilder(Expression source, Type elementType, Type extensionType, LambdaParameterNameFactory nameFactory, - IResourceFactory resourceFactory, IResourceContextProvider resourceContextProvider, IModel entityModel, - LambdaScopeFactory lambdaScopeFactory = null) - : base(source, elementType, extensionType, nameFactory, resourceFactory, resourceContextProvider, entityModel, lambdaScopeFactory) - { - _elementType = elementType; - _extensionType = extensionType; - _nameFactory = nameFactory; - _lambdaScopeFactory = lambdaScopeFactory ?? new LambdaScopeFactory(nameFactory); - } + public MongoQueryableBuilder(Expression source, Type elementType, Type extensionType, LambdaParameterNameFactory nameFactory, + IResourceFactory resourceFactory, IResourceContextProvider resourceContextProvider, IModel entityModel, LambdaScopeFactory lambdaScopeFactory = null) + : base(source, elementType, extensionType, nameFactory, resourceFactory, resourceContextProvider, entityModel, lambdaScopeFactory) + { + _elementType = elementType; + _extensionType = extensionType; + _nameFactory = nameFactory; + _lambdaScopeFactory = lambdaScopeFactory ?? new LambdaScopeFactory(nameFactory); + } - protected override Expression ApplyFilter(Expression source, FilterExpression filter) - { - using LambdaScope lambdaScope = _lambdaScopeFactory.CreateScope(_elementType); + protected override Expression ApplyFilter(Expression source, FilterExpression filter) + { + using LambdaScope lambdaScope = _lambdaScopeFactory.CreateScope(_elementType); - var builder = new MongoWhereClauseBuilder(source, lambdaScope, _extensionType, _nameFactory); - return builder.ApplyWhere(filter); - } + var builder = new MongoWhereClauseBuilder(source, lambdaScope, _extensionType, _nameFactory); + return builder.ApplyWhere(filter); } } diff --git a/src/JsonApiDotNetCore.MongoDb/Queries/Internal/QueryableBuilding/MongoWhereClauseBuilder.cs b/src/JsonApiDotNetCore.MongoDb/Queries/Internal/QueryableBuilding/MongoWhereClauseBuilder.cs index da560be..18c0793 100644 --- a/src/JsonApiDotNetCore.MongoDb/Queries/Internal/QueryableBuilding/MongoWhereClauseBuilder.cs +++ b/src/JsonApiDotNetCore.MongoDb/Queries/Internal/QueryableBuilding/MongoWhereClauseBuilder.cs @@ -1,48 +1,46 @@ -using System; using System.Linq.Expressions; using JetBrains.Annotations; using JsonApiDotNetCore.Queries.Expressions; using JsonApiDotNetCore.Queries.Internal.QueryableBuilding; -namespace JsonApiDotNetCore.MongoDb.Queries.Internal.QueryableBuilding +namespace JsonApiDotNetCore.MongoDb.Queries.Internal.QueryableBuilding; + +/// +[PublicAPI] +public class MongoWhereClauseBuilder : WhereClauseBuilder { - /// - [PublicAPI] - public class MongoWhereClauseBuilder : WhereClauseBuilder + public MongoWhereClauseBuilder(Expression source, LambdaScope lambdaScope, Type extensionType, LambdaParameterNameFactory nameFactory) + : base(source, lambdaScope, extensionType, nameFactory) + { + } + + public override Expression VisitLiteralConstant(LiteralConstantExpression expression, Type expressionType) { - public MongoWhereClauseBuilder(Expression source, LambdaScope lambdaScope, Type extensionType, LambdaParameterNameFactory nameFactory) - : base(source, lambdaScope, extensionType, nameFactory) + if (expressionType == typeof(DateTime) || expressionType == typeof(DateTime?)) { + DateTime? dateTime = TryParseDateTimeAsUtc(expression.Value, expressionType); + return Expression.Constant(dateTime); } - public override Expression VisitLiteralConstant(LiteralConstantExpression expression, Type expressionType) - { - if (expressionType == typeof(DateTime) || expressionType == typeof(DateTime?)) - { - DateTime? dateTime = TryParseDateTimeAsUtc(expression.Value, expressionType); - return Expression.Constant(dateTime); - } + return base.VisitLiteralConstant(expression, expressionType); + } - return base.VisitLiteralConstant(expression, expressionType); - } + private static DateTime? TryParseDateTimeAsUtc(string value, Type expressionType) + { + object convertedValue = Convert.ChangeType(value, expressionType); - private static DateTime? TryParseDateTimeAsUtc(string value, Type expressionType) + if (convertedValue is DateTime dateTime) { - object convertedValue = Convert.ChangeType(value, expressionType); - - if (convertedValue is DateTime dateTime) + // DateTime values in MongoDB are always stored in UTC, so any ambiguous filter value passed + // must be interpreted as such for correct comparison. + if (dateTime.Kind == DateTimeKind.Unspecified) { - // DateTime values in MongoDB are always stored in UTC, so any ambiguous filter value passed - // must be interpreted as such for correct comparison. - if (dateTime.Kind == DateTimeKind.Unspecified) - { - return DateTime.SpecifyKind(dateTime, DateTimeKind.Utc); - } - - return dateTime; + return DateTime.SpecifyKind(dateTime, DateTimeKind.Utc); } - return null; + return dateTime; } + + return null; } } diff --git a/src/JsonApiDotNetCore.MongoDb/Repositories/IMongoDataAccess.cs b/src/JsonApiDotNetCore.MongoDb/Repositories/IMongoDataAccess.cs index fe160fa..706637c 100644 --- a/src/JsonApiDotNetCore.MongoDb/Repositories/IMongoDataAccess.cs +++ b/src/JsonApiDotNetCore.MongoDb/Repositories/IMongoDataAccess.cs @@ -1,26 +1,24 @@ -using System; using MongoDB.Driver; -namespace JsonApiDotNetCore.MongoDb.Repositories +namespace JsonApiDotNetCore.MongoDb.Repositories; + +/// +/// Provides access to the MongoDB Driver and the optionally active session. +/// +public interface IMongoDataAccess : IAsyncDisposable { /// - /// Provides access to the MongoDB Driver and the optionally active session. + /// Provides access to the underlying MongoDB database, which data changes can be applied on. /// - public interface IMongoDataAccess : IAsyncDisposable - { - /// - /// Provides access to the underlying MongoDB database, which data changes can be applied on. - /// - IMongoDatabase MongoDatabase { get; } + IMongoDatabase MongoDatabase { get; } - /// - /// Provides access to the active session, if any. - /// - IClientSessionHandle ActiveSession { get; set; } + /// + /// Provides access to the active session, if any. + /// + IClientSessionHandle ActiveSession { get; set; } - /// - /// Identifies the current transaction, if any. - /// - string TransactionId { get; } - } + /// + /// Identifies the current transaction, if any. + /// + string TransactionId { get; } } diff --git a/src/JsonApiDotNetCore.MongoDb/Repositories/MongoDataAccess.cs b/src/JsonApiDotNetCore.MongoDb/Repositories/MongoDataAccess.cs index 69ccf70..54c8d04 100644 --- a/src/JsonApiDotNetCore.MongoDb/Repositories/MongoDataAccess.cs +++ b/src/JsonApiDotNetCore.MongoDb/Repositories/MongoDataAccess.cs @@ -1,40 +1,38 @@ -using System.Threading.Tasks; using MongoDB.Driver; -namespace JsonApiDotNetCore.MongoDb.Repositories +namespace JsonApiDotNetCore.MongoDb.Repositories; + +/// +public sealed class MongoDataAccess : IMongoDataAccess { /// - public sealed class MongoDataAccess : IMongoDataAccess - { - /// - public IMongoDatabase MongoDatabase { get; } + public IMongoDatabase MongoDatabase { get; } - /// - public IClientSessionHandle ActiveSession { get; set; } + /// + public IClientSessionHandle ActiveSession { get; set; } - /// - public string TransactionId => ActiveSession != null && ActiveSession.IsInTransaction ? ActiveSession.GetHashCode().ToString() : null; + /// + public string TransactionId => ActiveSession != null && ActiveSession.IsInTransaction ? ActiveSession.GetHashCode().ToString() : null; - public MongoDataAccess(IMongoDatabase mongoDatabase) - { - ArgumentGuard.NotNull(mongoDatabase, nameof(mongoDatabase)); + public MongoDataAccess(IMongoDatabase mongoDatabase) + { + ArgumentGuard.NotNull(mongoDatabase, nameof(mongoDatabase)); - MongoDatabase = mongoDatabase; - } + MongoDatabase = mongoDatabase; + } - /// - public async ValueTask DisposeAsync() + /// + public async ValueTask DisposeAsync() + { + if (ActiveSession != null) { - if (ActiveSession != null) + if (ActiveSession.IsInTransaction) { - if (ActiveSession.IsInTransaction) - { - await ActiveSession.AbortTransactionAsync(); - } - - ActiveSession.Dispose(); - ActiveSession = null; + await ActiveSession.AbortTransactionAsync(); } + + ActiveSession.Dispose(); + ActiveSession = null; } } } diff --git a/src/JsonApiDotNetCore.MongoDb/Repositories/MongoEntityType.cs b/src/JsonApiDotNetCore.MongoDb/Repositories/MongoEntityType.cs index b30e221..dbced20 100644 --- a/src/JsonApiDotNetCore.MongoDb/Repositories/MongoEntityType.cs +++ b/src/JsonApiDotNetCore.MongoDb/Repositories/MongoEntityType.cs @@ -1,97 +1,93 @@ -using System; -using System.Collections.Generic; -using System.Linq; using JsonApiDotNetCore.Configuration; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; -namespace JsonApiDotNetCore.MongoDb.Repositories +namespace JsonApiDotNetCore.MongoDb.Repositories; + +internal sealed class MongoEntityType : IEntityType { - internal sealed class MongoEntityType : IEntityType + private readonly ResourceContext _resourceContext; + + public IModel Model { get; } + public Type ClrType => _resourceContext.ResourceType; + + public string Name => throw new NotImplementedException(); + public IEntityType BaseType => throw new NotImplementedException(); + public string DefiningNavigationName => throw new NotImplementedException(); + public IEntityType DefiningEntityType => throw new NotImplementedException(); + public object this[string name] => throw new NotImplementedException(); + + public MongoEntityType(ResourceContext resourceContext, MongoModel owner) + { + ArgumentGuard.NotNull(resourceContext, nameof(resourceContext)); + ArgumentGuard.NotNull(owner, nameof(owner)); + + _resourceContext = resourceContext; + Model = owner; + } + + public IEnumerable GetProperties() + { + return _resourceContext.Attributes.Select(attr => new MongoProperty(attr.Property, this)).ToArray(); + } + + public IAnnotation FindAnnotation(string name) + { + throw new NotImplementedException(); + } + + public IEnumerable GetAnnotations() + { + throw new NotImplementedException(); + } + + public IKey FindPrimaryKey() + { + throw new NotImplementedException(); + } + + public IKey FindKey(IReadOnlyList properties) + { + throw new NotImplementedException(); + } + + public IEnumerable GetKeys() + { + throw new NotImplementedException(); + } + + public IForeignKey FindForeignKey(IReadOnlyList properties, IKey principalKey, IEntityType principalEntityType) + { + throw new NotImplementedException(); + } + + public IEnumerable GetForeignKeys() + { + throw new NotImplementedException(); + } + + public IIndex FindIndex(IReadOnlyList properties) + { + throw new NotImplementedException(); + } + + public IEnumerable GetIndexes() + { + throw new NotImplementedException(); + } + + public IProperty FindProperty(string name) + { + throw new NotImplementedException(); + } + + public IServiceProperty FindServiceProperty(string name) + { + throw new NotImplementedException(); + } + + public IEnumerable GetServiceProperties() { - private readonly ResourceContext _resourceContext; - - public IModel Model { get; } - public Type ClrType => _resourceContext.ResourceType; - - public string Name => throw new NotImplementedException(); - public IEntityType BaseType => throw new NotImplementedException(); - public string DefiningNavigationName => throw new NotImplementedException(); - public IEntityType DefiningEntityType => throw new NotImplementedException(); - public object this[string name] => throw new NotImplementedException(); - - public MongoEntityType(ResourceContext resourceContext, MongoModel owner) - { - ArgumentGuard.NotNull(resourceContext, nameof(resourceContext)); - ArgumentGuard.NotNull(owner, nameof(owner)); - - _resourceContext = resourceContext; - Model = owner; - } - - public IEnumerable GetProperties() - { - return _resourceContext.Attributes.Select(attr => new MongoProperty(attr.Property, this)).ToArray(); - } - - public IAnnotation FindAnnotation(string name) - { - throw new NotImplementedException(); - } - - public IEnumerable GetAnnotations() - { - throw new NotImplementedException(); - } - - public IKey FindPrimaryKey() - { - throw new NotImplementedException(); - } - - public IKey FindKey(IReadOnlyList properties) - { - throw new NotImplementedException(); - } - - public IEnumerable GetKeys() - { - throw new NotImplementedException(); - } - - public IForeignKey FindForeignKey(IReadOnlyList properties, IKey principalKey, IEntityType principalEntityType) - { - throw new NotImplementedException(); - } - - public IEnumerable GetForeignKeys() - { - throw new NotImplementedException(); - } - - public IIndex FindIndex(IReadOnlyList properties) - { - throw new NotImplementedException(); - } - - public IEnumerable GetIndexes() - { - throw new NotImplementedException(); - } - - public IProperty FindProperty(string name) - { - throw new NotImplementedException(); - } - - public IServiceProperty FindServiceProperty(string name) - { - throw new NotImplementedException(); - } - - public IEnumerable GetServiceProperties() - { - throw new NotImplementedException(); - } + throw new NotImplementedException(); } } diff --git a/src/JsonApiDotNetCore.MongoDb/Repositories/MongoModel.cs b/src/JsonApiDotNetCore.MongoDb/Repositories/MongoModel.cs index 7271798..0157e82 100644 --- a/src/JsonApiDotNetCore.MongoDb/Repositories/MongoModel.cs +++ b/src/JsonApiDotNetCore.MongoDb/Repositories/MongoModel.cs @@ -1,49 +1,45 @@ -using System; -using System.Collections.Generic; -using System.Linq; using JsonApiDotNetCore.Configuration; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; -namespace JsonApiDotNetCore.MongoDb.Repositories +namespace JsonApiDotNetCore.MongoDb.Repositories; + +internal sealed class MongoModel : IModel { - internal sealed class MongoModel : IModel + private readonly IResourceContextProvider _resourceContextProvider; + + public object this[string name] => throw new NotImplementedException(); + + public MongoModel(IResourceContextProvider resourceContextProvider) + { + ArgumentGuard.NotNull(resourceContextProvider, nameof(resourceContextProvider)); + + _resourceContextProvider = resourceContextProvider; + } + + public IEnumerable GetEntityTypes() + { + IReadOnlyCollection resourceContexts = _resourceContextProvider.GetResourceContexts(); + return resourceContexts.Select(resourceContext => new MongoEntityType(resourceContext, this)).ToArray(); + } + + public IAnnotation FindAnnotation(string name) + { + throw new NotImplementedException(); + } + + public IEnumerable GetAnnotations() + { + throw new NotImplementedException(); + } + + public IEntityType FindEntityType(string name) + { + throw new NotImplementedException(); + } + + public IEntityType FindEntityType(string name, string definingNavigationName, IEntityType definingEntityType) { - private readonly IResourceContextProvider _resourceContextProvider; - - public object this[string name] => throw new NotImplementedException(); - - public MongoModel(IResourceContextProvider resourceContextProvider) - { - ArgumentGuard.NotNull(resourceContextProvider, nameof(resourceContextProvider)); - - _resourceContextProvider = resourceContextProvider; - } - - public IEnumerable GetEntityTypes() - { - IReadOnlyCollection resourceContexts = _resourceContextProvider.GetResourceContexts(); - return resourceContexts.Select(resourceContext => new MongoEntityType(resourceContext, this)).ToArray(); - } - - public IAnnotation FindAnnotation(string name) - { - throw new NotImplementedException(); - } - - public IEnumerable GetAnnotations() - { - throw new NotImplementedException(); - } - - public IEntityType FindEntityType(string name) - { - throw new NotImplementedException(); - } - - public IEntityType FindEntityType(string name, string definingNavigationName, IEntityType definingEntityType) - { - throw new NotImplementedException(); - } + throw new NotImplementedException(); } } diff --git a/src/JsonApiDotNetCore.MongoDb/Repositories/MongoProperty.cs b/src/JsonApiDotNetCore.MongoDb/Repositories/MongoProperty.cs index e50fb24..481b4c5 100644 --- a/src/JsonApiDotNetCore.MongoDb/Repositories/MongoProperty.cs +++ b/src/JsonApiDotNetCore.MongoDb/Repositories/MongoProperty.cs @@ -1,42 +1,39 @@ -using System; -using System.Collections.Generic; using System.Reflection; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; -namespace JsonApiDotNetCore.MongoDb.Repositories +namespace JsonApiDotNetCore.MongoDb.Repositories; + +internal sealed class MongoProperty : IProperty { - internal sealed class MongoProperty : IProperty - { - public IEntityType DeclaringEntityType { get; } - public PropertyInfo PropertyInfo { get; } + public IEntityType DeclaringEntityType { get; } + public PropertyInfo PropertyInfo { get; } - public string Name => throw new NotImplementedException(); - public Type ClrType => throw new NotImplementedException(); - public FieldInfo FieldInfo => throw new NotImplementedException(); - public ITypeBase DeclaringType => throw new NotImplementedException(); - public bool IsNullable => throw new NotImplementedException(); - public ValueGenerated ValueGenerated => throw new NotImplementedException(); - public bool IsConcurrencyToken => throw new NotImplementedException(); - public object this[string name] => throw new NotImplementedException(); + public string Name => throw new NotImplementedException(); + public Type ClrType => throw new NotImplementedException(); + public FieldInfo FieldInfo => throw new NotImplementedException(); + public ITypeBase DeclaringType => throw new NotImplementedException(); + public bool IsNullable => throw new NotImplementedException(); + public ValueGenerated ValueGenerated => throw new NotImplementedException(); + public bool IsConcurrencyToken => throw new NotImplementedException(); + public object this[string name] => throw new NotImplementedException(); - public MongoProperty(PropertyInfo propertyInfo, MongoEntityType owner) - { - ArgumentGuard.NotNull(owner, nameof(owner)); - ArgumentGuard.NotNull(propertyInfo, nameof(propertyInfo)); + public MongoProperty(PropertyInfo propertyInfo, MongoEntityType owner) + { + ArgumentGuard.NotNull(owner, nameof(owner)); + ArgumentGuard.NotNull(propertyInfo, nameof(propertyInfo)); - DeclaringEntityType = owner; - PropertyInfo = propertyInfo; - } + DeclaringEntityType = owner; + PropertyInfo = propertyInfo; + } - public IAnnotation FindAnnotation(string name) - { - throw new NotImplementedException(); - } + public IAnnotation FindAnnotation(string name) + { + throw new NotImplementedException(); + } - public IEnumerable GetAnnotations() - { - throw new NotImplementedException(); - } + public IEnumerable GetAnnotations() + { + throw new NotImplementedException(); } } diff --git a/src/JsonApiDotNetCore.MongoDb/Repositories/MongoQueryExpressionValidator.cs b/src/JsonApiDotNetCore.MongoDb/Repositories/MongoQueryExpressionValidator.cs index 5c3df17..6d3656a 100644 --- a/src/JsonApiDotNetCore.MongoDb/Repositories/MongoQueryExpressionValidator.cs +++ b/src/JsonApiDotNetCore.MongoDb/Repositories/MongoQueryExpressionValidator.cs @@ -1,59 +1,57 @@ -using System.Linq; using JsonApiDotNetCore.MongoDb.Errors; using JsonApiDotNetCore.Queries; using JsonApiDotNetCore.Queries.Expressions; using JsonApiDotNetCore.Resources.Annotations; -namespace JsonApiDotNetCore.MongoDb.Repositories +namespace JsonApiDotNetCore.MongoDb.Repositories; + +internal sealed class MongoQueryExpressionValidator : QueryExpressionRewriter { - internal sealed class MongoQueryExpressionValidator : QueryExpressionRewriter + public void Validate(QueryLayer layer) { - public void Validate(QueryLayer layer) - { - ArgumentGuard.NotNull(layer, nameof(layer)); - - bool hasIncludes = layer.Include?.Elements.Any() == true; - bool hasSparseRelationshipSets = layer.Projection?.Any(pair => pair.Key is RelationshipAttribute) == true; + ArgumentGuard.NotNull(layer, nameof(layer)); - if (hasIncludes || hasSparseRelationshipSets) - { - throw new UnsupportedRelationshipException(); - } + bool hasIncludes = layer.Include?.Elements.Any() == true; + bool hasSparseRelationshipSets = layer.Projection?.Any(pair => pair.Key is RelationshipAttribute) == true; - ValidateExpression(layer.Filter); - ValidateExpression(layer.Sort); - ValidateExpression(layer.Pagination); + if (hasIncludes || hasSparseRelationshipSets) + { + throw new UnsupportedRelationshipException(); } - private void ValidateExpression(QueryExpression expression) + ValidateExpression(layer.Filter); + ValidateExpression(layer.Sort); + ValidateExpression(layer.Pagination); + } + + private void ValidateExpression(QueryExpression expression) + { + if (expression != null) { - if (expression != null) - { - Visit(expression, null); - } + Visit(expression, null); } + } - public override QueryExpression VisitResourceFieldChain(ResourceFieldChainExpression expression, object argument) + public override QueryExpression VisitResourceFieldChain(ResourceFieldChainExpression expression, object argument) + { + if (expression != null) { - if (expression != null) + if (expression.Fields.Count > 1 || expression.Fields.First() is RelationshipAttribute) { - if (expression.Fields.Count > 1 || expression.Fields.First() is RelationshipAttribute) - { - throw new UnsupportedRelationshipException(); - } + throw new UnsupportedRelationshipException(); } - - return base.VisitResourceFieldChain(expression, argument); } - public override QueryExpression VisitComparison(ComparisonExpression expression, object argument) - { - if (expression?.Left is ResourceFieldChainExpression && expression.Right is ResourceFieldChainExpression) - { - throw new AttributeComparisonInFilterNotSupportedException(); - } + return base.VisitResourceFieldChain(expression, argument); + } - return base.VisitComparison(expression, argument); + public override QueryExpression VisitComparison(ComparisonExpression expression, object argument) + { + if (expression?.Left is ResourceFieldChainExpression && expression.Right is ResourceFieldChainExpression) + { + throw new AttributeComparisonInFilterNotSupportedException(); } + + return base.VisitComparison(expression, argument); } } diff --git a/src/JsonApiDotNetCore.MongoDb/Repositories/MongoRepository.cs b/src/JsonApiDotNetCore.MongoDb/Repositories/MongoRepository.cs index cd3bf6d..df985d7 100644 --- a/src/JsonApiDotNetCore.MongoDb/Repositories/MongoRepository.cs +++ b/src/JsonApiDotNetCore.MongoDb/Repositories/MongoRepository.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; -using System.Threading; -using System.Threading.Tasks; using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Errors; @@ -18,287 +13,286 @@ using MongoDB.Driver; using MongoDB.Driver.Linq; -namespace JsonApiDotNetCore.MongoDb.Repositories -{ - /// - /// Implements the foundational Repository layer in the JsonApiDotNetCore architecture that uses MongoDB. - /// - [PublicAPI] - public class MongoRepository : IResourceRepository, IRepositorySupportsTransaction - where TResource : class, IIdentifiable - { - private readonly IMongoDataAccess _mongoDataAccess; - private readonly ITargetedFields _targetedFields; - private readonly IResourceContextProvider _resourceContextProvider; - private readonly IResourceFactory _resourceFactory; - private readonly IEnumerable _constraintProviders; +namespace JsonApiDotNetCore.MongoDb.Repositories; - protected virtual IMongoCollection Collection => _mongoDataAccess.MongoDatabase.GetCollection(typeof(TResource).Name); +/// +/// Implements the foundational Repository layer in the JsonApiDotNetCore architecture that uses MongoDB. +/// +[PublicAPI] +public class MongoRepository : IResourceRepository, IRepositorySupportsTransaction + where TResource : class, IIdentifiable +{ + private readonly IMongoDataAccess _mongoDataAccess; + private readonly ITargetedFields _targetedFields; + private readonly IResourceContextProvider _resourceContextProvider; + private readonly IResourceFactory _resourceFactory; + private readonly IEnumerable _constraintProviders; - /// - public virtual string TransactionId => _mongoDataAccess.TransactionId; + protected virtual IMongoCollection Collection => _mongoDataAccess.MongoDatabase.GetCollection(typeof(TResource).Name); - public MongoRepository(IMongoDataAccess mongoDataAccess, ITargetedFields targetedFields, IResourceContextProvider resourceContextProvider, - IResourceFactory resourceFactory, IEnumerable constraintProviders) - { - ArgumentGuard.NotNull(mongoDataAccess, nameof(mongoDataAccess)); - ArgumentGuard.NotNull(targetedFields, nameof(targetedFields)); - ArgumentGuard.NotNull(resourceContextProvider, nameof(resourceContextProvider)); - ArgumentGuard.NotNull(resourceFactory, nameof(resourceFactory)); - ArgumentGuard.NotNull(constraintProviders, nameof(constraintProviders)); - - _mongoDataAccess = mongoDataAccess; - _targetedFields = targetedFields; - _resourceContextProvider = resourceContextProvider; - _resourceFactory = resourceFactory; - _constraintProviders = constraintProviders; - - if (typeof(TId) != typeof(string)) - { - throw new InvalidConfigurationException("MongoDB can only be used for resources with an 'Id' property of type 'string'."); - } - } + /// + public virtual string TransactionId => _mongoDataAccess.TransactionId; - /// - public virtual async Task> GetAsync(QueryLayer layer, CancellationToken cancellationToken) + public MongoRepository(IMongoDataAccess mongoDataAccess, ITargetedFields targetedFields, IResourceContextProvider resourceContextProvider, + IResourceFactory resourceFactory, IEnumerable constraintProviders) + { + ArgumentGuard.NotNull(mongoDataAccess, nameof(mongoDataAccess)); + ArgumentGuard.NotNull(targetedFields, nameof(targetedFields)); + ArgumentGuard.NotNull(resourceContextProvider, nameof(resourceContextProvider)); + ArgumentGuard.NotNull(resourceFactory, nameof(resourceFactory)); + ArgumentGuard.NotNull(constraintProviders, nameof(constraintProviders)); + + _mongoDataAccess = mongoDataAccess; + _targetedFields = targetedFields; + _resourceContextProvider = resourceContextProvider; + _resourceFactory = resourceFactory; + _constraintProviders = constraintProviders; + + if (typeof(TId) != typeof(string)) { - ArgumentGuard.NotNull(layer, nameof(layer)); - - IMongoQueryable query = ApplyQueryLayer(layer); - List resources = await query.ToListAsync(cancellationToken); - return resources.AsReadOnly(); + throw new InvalidConfigurationException("MongoDB can only be used for resources with an 'Id' property of type 'string'."); } + } - /// - public virtual Task CountAsync(FilterExpression topFilter, CancellationToken cancellationToken) - { - ResourceContext resourceContext = _resourceContextProvider.GetResourceContext(); + /// + public virtual async Task> GetAsync(QueryLayer layer, CancellationToken cancellationToken) + { + ArgumentGuard.NotNull(layer, nameof(layer)); - var layer = new QueryLayer(resourceContext) - { - Filter = topFilter - }; + IMongoQueryable query = ApplyQueryLayer(layer); + List resources = await query.ToListAsync(cancellationToken); + return resources.AsReadOnly(); + } - IMongoQueryable query = ApplyQueryLayer(layer); - return query.CountAsync(cancellationToken); - } + /// + public virtual Task CountAsync(FilterExpression topFilter, CancellationToken cancellationToken) + { + ResourceContext resourceContext = _resourceContextProvider.GetResourceContext(); - protected virtual IMongoQueryable ApplyQueryLayer(QueryLayer layer) + var layer = new QueryLayer(resourceContext) { - ArgumentGuard.NotNull(layer, nameof(layer)); - - var queryExpressionValidator = new MongoQueryExpressionValidator(); - queryExpressionValidator.Validate(layer); - - AssertNoRelationshipsInSparseFieldSets(); + Filter = topFilter + }; - IQueryable source = GetAll(); + IMongoQueryable query = ApplyQueryLayer(layer); + return query.CountAsync(cancellationToken); + } - // @formatter:wrap_chained_method_calls chop_always - // @formatter:keep_existing_linebreaks true + protected virtual IMongoQueryable ApplyQueryLayer(QueryLayer layer) + { + ArgumentGuard.NotNull(layer, nameof(layer)); - QueryableHandlerExpression[] queryableHandlers = _constraintProviders - .SelectMany(provider => provider.GetConstraints()) - .Where(expressionInScope => expressionInScope.Scope == null) - .Select(expressionInScope => expressionInScope.Expression) - .OfType() - .ToArray(); + var queryExpressionValidator = new MongoQueryExpressionValidator(); + queryExpressionValidator.Validate(layer); - // @formatter:keep_existing_linebreaks restore - // @formatter:wrap_chained_method_calls restore + AssertNoRelationshipsInSparseFieldSets(); - foreach (QueryableHandlerExpression queryableHandler in queryableHandlers) - { - source = queryableHandler.Apply(source); - } + IQueryable source = GetAll(); - var nameFactory = new LambdaParameterNameFactory(); + // @formatter:wrap_chained_method_calls chop_always + // @formatter:keep_existing_linebreaks true - var builder = new MongoQueryableBuilder(source.Expression, source.ElementType, typeof(Queryable), nameFactory, _resourceFactory, - _resourceContextProvider, new MongoModel(_resourceContextProvider)); + QueryableHandlerExpression[] queryableHandlers = _constraintProviders + .SelectMany(provider => provider.GetConstraints()) + .Where(expressionInScope => expressionInScope.Scope == null) + .Select(expressionInScope => expressionInScope.Expression) + .OfType() + .ToArray(); - Expression expression = builder.ApplyQuery(layer); - return (IMongoQueryable)source.Provider.CreateQuery(expression); - } + // @formatter:keep_existing_linebreaks restore + // @formatter:wrap_chained_method_calls restore - protected virtual IQueryable GetAll() + foreach (QueryableHandlerExpression queryableHandler in queryableHandlers) { - return _mongoDataAccess.ActiveSession != null ? Collection.AsQueryable(_mongoDataAccess.ActiveSession) : Collection.AsQueryable(); + source = queryableHandler.Apply(source); } - private void AssertNoRelationshipsInSparseFieldSets() - { - ResourceContext resourceContext = _resourceContextProvider.GetResourceContext(); + var nameFactory = new LambdaParameterNameFactory(); - // @formatter:wrap_chained_method_calls chop_always - // @formatter:keep_existing_linebreaks true + var builder = new MongoQueryableBuilder(source.Expression, source.ElementType, typeof(Queryable), nameFactory, _resourceFactory, + _resourceContextProvider, new MongoModel(_resourceContextProvider)); - bool hasRelationshipSelectors = _constraintProviders - .SelectMany(provider => provider.GetConstraints()) - .Select(expressionInScope => expressionInScope.Expression) - .OfType() - .Any(fieldTable => - fieldTable.Table.Keys.Any(targetResourceContext => targetResourceContext != resourceContext) || - fieldTable.Table.Values.Any(fieldSet => fieldSet.Fields.Any(field => field is RelationshipAttribute))); + Expression expression = builder.ApplyQuery(layer); + return (IMongoQueryable)source.Provider.CreateQuery(expression); + } - // @formatter:keep_existing_linebreaks restore - // @formatter:wrap_chained_method_calls restore + protected virtual IQueryable GetAll() + { + return _mongoDataAccess.ActiveSession != null ? Collection.AsQueryable(_mongoDataAccess.ActiveSession) : Collection.AsQueryable(); + } - if (hasRelationshipSelectors) - { - throw new UnsupportedRelationshipException(); - } - } + private void AssertNoRelationshipsInSparseFieldSets() + { + ResourceContext resourceContext = _resourceContextProvider.GetResourceContext(); - /// - public virtual Task GetForCreateAsync(TId id, CancellationToken cancellationToken) - { - var resource = _resourceFactory.CreateInstance(); - resource.Id = id; + // @formatter:wrap_chained_method_calls chop_always + // @formatter:keep_existing_linebreaks true - return Task.FromResult(resource); - } + bool hasRelationshipSelectors = _constraintProviders + .SelectMany(provider => provider.GetConstraints()) + .Select(expressionInScope => expressionInScope.Expression) + .OfType() + .Any(fieldTable => + fieldTable.Table.Keys.Any(targetResourceContext => targetResourceContext != resourceContext) || + fieldTable.Table.Values.Any(fieldSet => fieldSet.Fields.Any(field => field is RelationshipAttribute))); - /// - public virtual async Task CreateAsync(TResource resourceFromRequest, TResource resourceForDatabase, CancellationToken cancellationToken) + // @formatter:keep_existing_linebreaks restore + // @formatter:wrap_chained_method_calls restore + + if (hasRelationshipSelectors) { - ArgumentGuard.NotNull(resourceFromRequest, nameof(resourceFromRequest)); - ArgumentGuard.NotNull(resourceForDatabase, nameof(resourceForDatabase)); + throw new UnsupportedRelationshipException(); + } + } - AssertNoRelationshipsAreTargeted(); + /// + public virtual Task GetForCreateAsync(TId id, CancellationToken cancellationToken) + { + var resource = _resourceFactory.CreateInstance(); + resource.Id = id; - foreach (AttrAttribute attribute in _targetedFields.Attributes) - { - attribute.SetValue(resourceForDatabase, attribute.GetValue(resourceFromRequest)); - } + return Task.FromResult(resource); + } - await SaveChangesAsync(async () => - { - await (_mongoDataAccess.ActiveSession != null - ? Collection.InsertOneAsync(_mongoDataAccess.ActiveSession, resourceForDatabase, cancellationToken: cancellationToken) - : Collection.InsertOneAsync(resourceForDatabase, cancellationToken: cancellationToken)); - }, cancellationToken); - } + /// + public virtual async Task CreateAsync(TResource resourceFromRequest, TResource resourceForDatabase, CancellationToken cancellationToken) + { + ArgumentGuard.NotNull(resourceFromRequest, nameof(resourceFromRequest)); + ArgumentGuard.NotNull(resourceForDatabase, nameof(resourceForDatabase)); + + AssertNoRelationshipsAreTargeted(); - private void AssertNoRelationshipsAreTargeted() + foreach (AttrAttribute attribute in _targetedFields.Attributes) { - if (_targetedFields.Relationships.Any()) - { - throw new UnsupportedRelationshipException(); - } + attribute.SetValue(resourceForDatabase, attribute.GetValue(resourceFromRequest)); } - /// - public virtual async Task GetForUpdateAsync(QueryLayer queryLayer, CancellationToken cancellationToken) + await SaveChangesAsync(async () => { - IReadOnlyCollection resources = await GetAsync(queryLayer, cancellationToken); - return resources.FirstOrDefault(); - } + await (_mongoDataAccess.ActiveSession != null + ? Collection.InsertOneAsync(_mongoDataAccess.ActiveSession, resourceForDatabase, cancellationToken: cancellationToken) + : Collection.InsertOneAsync(resourceForDatabase, cancellationToken: cancellationToken)); + }, cancellationToken); + } - /// - public virtual async Task UpdateAsync(TResource resourceFromRequest, TResource resourceFromDatabase, CancellationToken cancellationToken) + private void AssertNoRelationshipsAreTargeted() + { + if (_targetedFields.Relationships.Any()) { - ArgumentGuard.NotNull(resourceFromRequest, nameof(resourceFromRequest)); - ArgumentGuard.NotNull(resourceFromDatabase, nameof(resourceFromDatabase)); + throw new UnsupportedRelationshipException(); + } + } - AssertNoRelationshipsAreTargeted(); + /// + public virtual async Task GetForUpdateAsync(QueryLayer queryLayer, CancellationToken cancellationToken) + { + IReadOnlyCollection resources = await GetAsync(queryLayer, cancellationToken); + return resources.FirstOrDefault(); + } - foreach (AttrAttribute attr in _targetedFields.Attributes) - { - attr.SetValue(resourceFromDatabase, attr.GetValue(resourceFromRequest)); - } + /// + public virtual async Task UpdateAsync(TResource resourceFromRequest, TResource resourceFromDatabase, CancellationToken cancellationToken) + { + ArgumentGuard.NotNull(resourceFromRequest, nameof(resourceFromRequest)); + ArgumentGuard.NotNull(resourceFromDatabase, nameof(resourceFromDatabase)); - FilterDefinition filter = Builders.Filter.Eq(resource => resource.Id, resourceFromDatabase.Id); + AssertNoRelationshipsAreTargeted(); - await SaveChangesAsync(async () => - { - await (_mongoDataAccess.ActiveSession != null - ? Collection.ReplaceOneAsync(_mongoDataAccess.ActiveSession, filter, resourceFromDatabase, cancellationToken: cancellationToken) - : Collection.ReplaceOneAsync(filter, resourceFromDatabase, cancellationToken: cancellationToken)); - }, cancellationToken); + foreach (AttrAttribute attr in _targetedFields.Attributes) + { + attr.SetValue(resourceFromDatabase, attr.GetValue(resourceFromRequest)); } - /// - public virtual async Task DeleteAsync(TId id, CancellationToken cancellationToken) - { - FilterDefinition filter = Builders.Filter.Eq(resource => resource.Id, id); + FilterDefinition filter = Builders.Filter.Eq(resource => resource.Id, resourceFromDatabase.Id); - DeleteResult result = await SaveChangesAsync( - async () => _mongoDataAccess.ActiveSession != null - ? await Collection.DeleteOneAsync(_mongoDataAccess.ActiveSession, filter, cancellationToken: cancellationToken) - : await Collection.DeleteOneAsync(filter, cancellationToken), cancellationToken); + await SaveChangesAsync(async () => + { + await (_mongoDataAccess.ActiveSession != null + ? Collection.ReplaceOneAsync(_mongoDataAccess.ActiveSession, filter, resourceFromDatabase, cancellationToken: cancellationToken) + : Collection.ReplaceOneAsync(filter, resourceFromDatabase, cancellationToken: cancellationToken)); + }, cancellationToken); + } - if (!result.IsAcknowledged) - { - throw new DataStoreUpdateException( - new Exception($"Failed to delete document with id '{id}', because the operation was not acknowledged by MongoDB.")); - } + /// + public virtual async Task DeleteAsync(TId id, CancellationToken cancellationToken) + { + FilterDefinition filter = Builders.Filter.Eq(resource => resource.Id, id); - if (result.DeletedCount == 0) - { - throw new DataStoreUpdateException(new Exception($"Failed to delete document with id '{id}', because it does not exist.")); - } - } + DeleteResult result = await SaveChangesAsync( + async () => _mongoDataAccess.ActiveSession != null + ? await Collection.DeleteOneAsync(_mongoDataAccess.ActiveSession, filter, cancellationToken: cancellationToken) + : await Collection.DeleteOneAsync(filter, cancellationToken), cancellationToken); - /// - public virtual Task SetRelationshipAsync(TResource primaryResource, object secondaryResourceIds, CancellationToken cancellationToken) + if (!result.IsAcknowledged) { - throw new UnsupportedRelationshipException(); + throw new DataStoreUpdateException( + new Exception($"Failed to delete document with id '{id}', because the operation was not acknowledged by MongoDB.")); } - /// - public virtual Task AddToToManyRelationshipAsync(TId primaryId, ISet secondaryResourceIds, CancellationToken cancellationToken) + if (result.DeletedCount == 0) { - throw new UnsupportedRelationshipException(); + throw new DataStoreUpdateException(new Exception($"Failed to delete document with id '{id}', because it does not exist.")); } + } - /// - public virtual Task RemoveFromToManyRelationshipAsync(TResource primaryResource, ISet secondaryResourceIds, - CancellationToken cancellationToken) + /// + public virtual Task SetRelationshipAsync(TResource primaryResource, object secondaryResourceIds, CancellationToken cancellationToken) + { + throw new UnsupportedRelationshipException(); + } + + /// + public virtual Task AddToToManyRelationshipAsync(TId primaryId, ISet secondaryResourceIds, CancellationToken cancellationToken) + { + throw new UnsupportedRelationshipException(); + } + + /// + public virtual Task RemoveFromToManyRelationshipAsync(TResource primaryResource, ISet secondaryResourceIds, + CancellationToken cancellationToken) + { + throw new UnsupportedRelationshipException(); + } + + protected virtual async Task SaveChangesAsync(Func asyncSaveAction, CancellationToken cancellationToken) + { + _ = await SaveChangesAsync(async () => { - throw new UnsupportedRelationshipException(); - } + await asyncSaveAction(); + return null; + }, cancellationToken); + } - protected virtual async Task SaveChangesAsync(Func asyncSaveAction, CancellationToken cancellationToken) + protected virtual async Task SaveChangesAsync(Func> asyncSaveAction, CancellationToken cancellationToken) + { + try { - _ = await SaveChangesAsync(async () => - { - await asyncSaveAction(); - return null; - }, cancellationToken); + return await asyncSaveAction(); } - - protected virtual async Task SaveChangesAsync(Func> asyncSaveAction, CancellationToken cancellationToken) + catch (MongoException exception) { - try - { - return await asyncSaveAction(); - } - catch (MongoException exception) + if (_mongoDataAccess.ActiveSession != null) { - if (_mongoDataAccess.ActiveSession != null) - { - // The ResourceService calling us needs to run additional SQL queries after an aborted transaction, - // to determine error cause. This fails when a failed transaction is still in progress. - await _mongoDataAccess.ActiveSession.AbortTransactionAsync(cancellationToken); - _mongoDataAccess.ActiveSession = null; - } - - throw new DataStoreUpdateException(exception); + // The ResourceService calling us needs to run additional SQL queries after an aborted transaction, + // to determine error cause. This fails when a failed transaction is still in progress. + await _mongoDataAccess.ActiveSession.AbortTransactionAsync(cancellationToken); + _mongoDataAccess.ActiveSession = null; } + + throw new DataStoreUpdateException(exception); } } +} - /// - /// Do not use. This type exists solely to produce a proper error message when trying to use MongoDB with a non-string Id. - /// - public sealed class MongoRepository : MongoRepository, IResourceRepository - where TResource : class, IIdentifiable +/// +/// Do not use. This type exists solely to produce a proper error message when trying to use MongoDB with a non-string Id. +/// +public sealed class MongoRepository : MongoRepository, IResourceRepository + where TResource : class, IIdentifiable +{ + public MongoRepository(IMongoDataAccess mongoDataAccess, ITargetedFields targetedFields, IResourceContextProvider resourceContextProvider, + IResourceFactory resourceFactory, IEnumerable constraintProviders) + : base(mongoDataAccess, targetedFields, resourceContextProvider, resourceFactory, constraintProviders) { - public MongoRepository(IMongoDataAccess mongoDataAccess, ITargetedFields targetedFields, IResourceContextProvider resourceContextProvider, - IResourceFactory resourceFactory, IEnumerable constraintProviders) - : base(mongoDataAccess, targetedFields, resourceContextProvider, resourceFactory, constraintProviders) - { - } } } diff --git a/src/JsonApiDotNetCore.MongoDb/Resources/MongoIdentifiable.cs b/src/JsonApiDotNetCore.MongoDb/Resources/MongoIdentifiable.cs index ded90d5..b269299 100644 --- a/src/JsonApiDotNetCore.MongoDb/Resources/MongoIdentifiable.cs +++ b/src/JsonApiDotNetCore.MongoDb/Resources/MongoIdentifiable.cs @@ -2,28 +2,27 @@ using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; -namespace JsonApiDotNetCore.MongoDb.Resources -{ - /// - /// A convenient basic implementation of for use with MongoDB models. - /// - public abstract class MongoIdentifiable : IIdentifiable - { - /// - [BsonId] - [BsonRepresentation(BsonType.ObjectId)] - public virtual string Id { get; set; } +namespace JsonApiDotNetCore.MongoDb.Resources; - /// - [BsonIgnore] - public string StringId - { - get => Id; - set => Id = value; - } +/// +/// A convenient basic implementation of for use with MongoDB models. +/// +public abstract class MongoIdentifiable : IIdentifiable +{ + /// + [BsonId] + [BsonRepresentation(BsonType.ObjectId)] + public virtual string Id { get; set; } - /// - [BsonIgnore] - public string LocalId { get; set; } + /// + [BsonIgnore] + public string StringId + { + get => Id; + set => Id = value; } + + /// + [BsonIgnore] + public string LocalId { get; set; } } diff --git a/src/JsonApiDotNetCore.MongoDb/Serialization/Building/IgnoreRelationshipsResponseResourceObjectBuilder.cs b/src/JsonApiDotNetCore.MongoDb/Serialization/Building/IgnoreRelationshipsResponseResourceObjectBuilder.cs index 62cbc5c..539eeb4 100644 --- a/src/JsonApiDotNetCore.MongoDb/Serialization/Building/IgnoreRelationshipsResponseResourceObjectBuilder.cs +++ b/src/JsonApiDotNetCore.MongoDb/Serialization/Building/IgnoreRelationshipsResponseResourceObjectBuilder.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.MongoDb.Resources; using JsonApiDotNetCore.Queries; @@ -8,29 +7,27 @@ using JsonApiDotNetCore.Serialization.Building; using JsonApiDotNetCore.Serialization.Objects; -namespace JsonApiDotNetCore.MongoDb.Serialization.Building +namespace JsonApiDotNetCore.MongoDb.Serialization.Building; + +/// +public sealed class IgnoreRelationshipsResponseResourceObjectBuilder : ResponseResourceObjectBuilder { + public IgnoreRelationshipsResponseResourceObjectBuilder(ILinkBuilder linkBuilder, IIncludedResourceObjectBuilder includedBuilder, + IEnumerable constraintProviders, IResourceContextProvider resourceContextProvider, + IResourceDefinitionAccessor resourceDefinitionAccessor, IResourceObjectBuilderSettingsProvider settingsProvider, + IEvaluatedIncludeCache evaluatedIncludeCache) + : base(linkBuilder, includedBuilder, constraintProviders, resourceContextProvider, resourceDefinitionAccessor, settingsProvider, evaluatedIncludeCache) + { + } + /// - public sealed class IgnoreRelationshipsResponseResourceObjectBuilder : ResponseResourceObjectBuilder + protected override RelationshipEntry GetRelationshipData(RelationshipAttribute relationship, IIdentifiable resource) { - public IgnoreRelationshipsResponseResourceObjectBuilder(ILinkBuilder linkBuilder, IIncludedResourceObjectBuilder includedBuilder, - IEnumerable constraintProviders, IResourceContextProvider resourceContextProvider, - IResourceDefinitionAccessor resourceDefinitionAccessor, IResourceObjectBuilderSettingsProvider settingsProvider, - IEvaluatedIncludeCache evaluatedIncludeCache) - : base(linkBuilder, includedBuilder, constraintProviders, resourceContextProvider, resourceDefinitionAccessor, settingsProvider, - evaluatedIncludeCache) + if (resource is MongoIdentifiable) { + return null; } - /// - protected override RelationshipEntry GetRelationshipData(RelationshipAttribute relationship, IIdentifiable resource) - { - if (resource is MongoIdentifiable) - { - return null; - } - - return base.GetRelationshipData(relationship, resource); - } + return base.GetRelationshipData(relationship, resource); } } diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/AtomicOperationsFixture.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/AtomicOperationsFixture.cs index 30b3b11..dd2c0b9 100644 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/AtomicOperationsFixture.cs +++ b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/AtomicOperationsFixture.cs @@ -1,25 +1,23 @@ -using System; using JetBrains.Annotations; using JsonApiDotNetCoreMongoDbExampleTests.TestBuildingBlocks; -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations +namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations; + +[UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] +public sealed class AtomicOperationsFixture : IDisposable { - [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] - public sealed class AtomicOperationsFixture : IDisposable - { - internal IntegrationTestContext TestContext { get; } + internal IntegrationTestContext TestContext { get; } - public AtomicOperationsFixture() + public AtomicOperationsFixture() + { + TestContext = new IntegrationTestContext { - TestContext = new IntegrationTestContext - { - StartMongoDbInSingleNodeReplicaSetMode = true - }; - } + StartMongoDbInSingleNodeReplicaSetMode = true + }; + } - public void Dispose() - { - TestContext.Dispose(); - } + public void Dispose() + { + TestContext.Dispose(); } } diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/AtomicOperationsTestCollection.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/AtomicOperationsTestCollection.cs index 355dab3..bd7121f 100644 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/AtomicOperationsTestCollection.cs +++ b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/AtomicOperationsTestCollection.cs @@ -1,12 +1,11 @@ using Xunit; -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations +namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations; + +[CollectionDefinition("AtomicOperationsFixture")] +public sealed class AtomicOperationsTestCollection : ICollectionFixture { - [CollectionDefinition("AtomicOperationsFixture")] - public sealed class AtomicOperationsTestCollection : ICollectionFixture - { - // Starting MongoDB in Single Node Replica Set mode is required to enable transactions. - // Starting in this mode requires about 10 seconds, which is normally repeated for each test class. - // So to improve test runtime performance, we reuse a single MongoDB instance for all atomic:operations tests. - } + // Starting MongoDB in Single Node Replica Set mode is required to enable transactions. + // Starting in this mode requires about 10 seconds, which is normally repeated for each test class. + // So to improve test runtime performance, we reuse a single MongoDB instance for all atomic:operations tests. } diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceTests.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceTests.cs index 30491ed..1f088c7 100644 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceTests.cs +++ b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceTests.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; using System.Net; -using System.Net.Http; -using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Serialization.Objects; using JsonApiDotNetCoreMongoDbExampleTests.TestBuildingBlocks; @@ -11,219 +6,218 @@ using MongoDB.Driver.Linq; using Xunit; -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Creating +namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Creating; + +[Collection("AtomicOperationsFixture")] +public sealed class AtomicCreateResourceTests { - [Collection("AtomicOperationsFixture")] - public sealed class AtomicCreateResourceTests + private readonly IntegrationTestContext _testContext; + private readonly OperationsFakers _fakers = new(); + + public AtomicCreateResourceTests(AtomicOperationsFixture fixture) { - private readonly IntegrationTestContext _testContext; - private readonly OperationsFakers _fakers = new OperationsFakers(); + _testContext = fixture.TestContext; - public AtomicCreateResourceTests(AtomicOperationsFixture fixture) - { - _testContext = fixture.TestContext; + fixture.TestContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); + } - fixture.TestContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); - } + [Fact] + public async Task Can_create_resource() + { + // Arrange + string newArtistName = _fakers.Performer.Generate().ArtistName; + DateTimeOffset newBornAt = _fakers.Performer.Generate().BornAt; - [Fact] - public async Task Can_create_resource() + await _testContext.RunOnDatabaseAsync(async db => { - // Arrange - string newArtistName = _fakers.Performer.Generate().ArtistName; - DateTimeOffset newBornAt = _fakers.Performer.Generate().BornAt; - - await _testContext.RunOnDatabaseAsync(async db => - { - await db.EnsureEmptyCollectionAsync(); - }); + await db.EnsureEmptyCollectionAsync(); + }); - var requestBody = new + var requestBody = new + { + atomic__operations = new[] { - atomic__operations = new[] + new { - new + op = "add", + data = new { - op = "add", - data = new + type = "performers", + attributes = new { - type = "performers", - attributes = new - { - artistName = newArtistName, - bornAt = newBornAt - } + artistName = newArtistName, + bornAt = newBornAt } } } - }; + } + }; - const string route = "/operations"; + const string route = "/operations"; - // Act - (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = - await _testContext.ExecutePostAtomicAsync(route, requestBody); + // Act + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); - responseDocument.Results.Should().HaveCount(1); - responseDocument.Results[0].SingleData.Should().NotBeNull(); - responseDocument.Results[0].SingleData.Type.Should().Be("performers"); - responseDocument.Results[0].SingleData.Attributes["artistName"].Should().Be(newArtistName); - responseDocument.Results[0].SingleData.Attributes["bornAt"].Should().BeCloseTo(newBornAt); - responseDocument.Results[0].SingleData.Relationships.Should().BeNull(); + responseDocument.Results.Should().HaveCount(1); + responseDocument.Results[0].SingleData.Should().NotBeNull(); + responseDocument.Results[0].SingleData.Type.Should().Be("performers"); + responseDocument.Results[0].SingleData.Attributes["artistName"].Should().Be(newArtistName); + responseDocument.Results[0].SingleData.Attributes["bornAt"].Should().BeCloseTo(newBornAt); + responseDocument.Results[0].SingleData.Relationships.Should().BeNull(); - string newPerformerId = responseDocument.Results[0].SingleData.Id; + string newPerformerId = responseDocument.Results[0].SingleData.Id; - await _testContext.RunOnDatabaseAsync(async db => - { - Performer performerInDatabase = await db.GetCollection().AsQueryable().FirstWithIdAsync(newPerformerId); + await _testContext.RunOnDatabaseAsync(async db => + { + Performer performerInDatabase = await db.GetCollection().AsQueryable().FirstWithIdAsync(newPerformerId); - performerInDatabase.ArtistName.Should().Be(newArtistName); - performerInDatabase.BornAt.Should().BeCloseTo(newBornAt, TimeSpan.FromMilliseconds(20)); - }); - } + performerInDatabase.ArtistName.Should().Be(newArtistName); + performerInDatabase.BornAt.Should().BeCloseTo(newBornAt, TimeSpan.FromMilliseconds(20)); + }); + } - [Fact] - public async Task Can_create_resources() - { - // Arrange - const int elementCount = 5; + [Fact] + public async Task Can_create_resources() + { + // Arrange + const int elementCount = 5; - List newTracks = _fakers.MusicTrack.Generate(elementCount); + List newTracks = _fakers.MusicTrack.Generate(elementCount); - await _testContext.RunOnDatabaseAsync(async db => - { - await db.EnsureEmptyCollectionAsync(); - }); + await _testContext.RunOnDatabaseAsync(async db => + { + await db.EnsureEmptyCollectionAsync(); + }); - var operationElements = new List(elementCount); + var operationElements = new List(elementCount); - for (int index = 0; index < elementCount; index++) + for (int index = 0; index < elementCount; index++) + { + operationElements.Add(new { - operationElements.Add(new + op = "add", + data = new { - op = "add", - data = new + type = "musicTracks", + attributes = new { - type = "musicTracks", - attributes = new - { - title = newTracks[index].Title, - lengthInSeconds = newTracks[index].LengthInSeconds, - genre = newTracks[index].Genre, - releasedAt = newTracks[index].ReleasedAt - } + title = newTracks[index].Title, + lengthInSeconds = newTracks[index].LengthInSeconds, + genre = newTracks[index].Genre, + releasedAt = newTracks[index].ReleasedAt } - }); - } + } + }); + } - var requestBody = new - { - atomic__operations = operationElements - }; + var requestBody = new + { + atomic__operations = operationElements + }; - const string route = "/operations"; + const string route = "/operations"; - // Act - (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = - await _testContext.ExecutePostAtomicAsync(route, requestBody); + // Act + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); - responseDocument.Results.Should().HaveCount(elementCount); + responseDocument.Results.Should().HaveCount(elementCount); - for (int index = 0; index < elementCount; index++) - { - ResourceObject singleData = responseDocument.Results[index].SingleData; - - singleData.Should().NotBeNull(); - singleData.Type.Should().Be("musicTracks"); - singleData.Attributes["title"].Should().Be(newTracks[index].Title); - singleData.Attributes["lengthInSeconds"].As().Should().BeApproximately(newTracks[index].LengthInSeconds); - singleData.Attributes["genre"].Should().Be(newTracks[index].Genre); - singleData.Attributes["releasedAt"].Should().BeCloseTo(newTracks[index].ReleasedAt); - singleData.Relationships.Should().BeNull(); - } + for (int index = 0; index < elementCount; index++) + { + ResourceObject singleData = responseDocument.Results[index].SingleData; + + singleData.Should().NotBeNull(); + singleData.Type.Should().Be("musicTracks"); + singleData.Attributes["title"].Should().Be(newTracks[index].Title); + singleData.Attributes["lengthInSeconds"].As().Should().BeApproximately(newTracks[index].LengthInSeconds); + singleData.Attributes["genre"].Should().Be(newTracks[index].Genre); + singleData.Attributes["releasedAt"].Should().BeCloseTo(newTracks[index].ReleasedAt); + singleData.Relationships.Should().BeNull(); + } - string[] newTrackIds = responseDocument.Results.Select(result => result.SingleData.Id).ToArray(); + string[] newTrackIds = responseDocument.Results.Select(result => result.SingleData.Id).ToArray(); - await _testContext.RunOnDatabaseAsync(async db => - { - List tracksInDatabase = await db.GetCollection().AsQueryable().Where(musicTrack => newTrackIds.Contains(musicTrack.Id)) - .ToListAsync(); + await _testContext.RunOnDatabaseAsync(async db => + { + List tracksInDatabase = await db.GetCollection().AsQueryable().Where(musicTrack => newTrackIds.Contains(musicTrack.Id)) + .ToListAsync(); - tracksInDatabase.Should().HaveCount(elementCount); + tracksInDatabase.Should().HaveCount(elementCount); - for (int index = 0; index < elementCount; index++) - { - MusicTrack trackInDatabase = tracksInDatabase.Single(musicTrack => musicTrack.Id == newTrackIds[index]); + for (int index = 0; index < elementCount; index++) + { + MusicTrack trackInDatabase = tracksInDatabase.Single(musicTrack => musicTrack.Id == newTrackIds[index]); - trackInDatabase.Title.Should().Be(newTracks[index].Title); - trackInDatabase.LengthInSeconds.Should().BeApproximately(newTracks[index].LengthInSeconds); - trackInDatabase.Genre.Should().Be(newTracks[index].Genre); - trackInDatabase.ReleasedAt.Should().BeCloseTo(newTracks[index].ReleasedAt, TimeSpan.FromMilliseconds(20)); - } - }); - } + trackInDatabase.Title.Should().Be(newTracks[index].Title); + trackInDatabase.LengthInSeconds.Should().BeApproximately(newTracks[index].LengthInSeconds); + trackInDatabase.Genre.Should().Be(newTracks[index].Genre); + trackInDatabase.ReleasedAt.Should().BeCloseTo(newTracks[index].ReleasedAt, TimeSpan.FromMilliseconds(20)); + } + }); + } - [Fact] - public async Task Can_create_resource_without_attributes_or_relationships() + [Fact] + public async Task Can_create_resource_without_attributes_or_relationships() + { + // Arrange + await _testContext.RunOnDatabaseAsync(async db => { - // Arrange - await _testContext.RunOnDatabaseAsync(async db => - { - await db.EnsureEmptyCollectionAsync(); - }); + await db.EnsureEmptyCollectionAsync(); + }); - var requestBody = new + var requestBody = new + { + atomic__operations = new[] { - atomic__operations = new[] + new { - new + op = "add", + data = new { - op = "add", - data = new + type = "performers", + attributes = new + { + }, + relationship = new { - type = "performers", - attributes = new - { - }, - relationship = new - { - } } } } - }; + } + }; - const string route = "/operations"; + const string route = "/operations"; - // Act - (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = - await _testContext.ExecutePostAtomicAsync(route, requestBody); + // Act + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); - responseDocument.Results.Should().HaveCount(1); - responseDocument.Results[0].SingleData.Should().NotBeNull(); - responseDocument.Results[0].SingleData.Type.Should().Be("performers"); - responseDocument.Results[0].SingleData.Attributes["artistName"].Should().BeNull(); - responseDocument.Results[0].SingleData.Attributes["bornAt"].Should().BeCloseTo(default(DateTimeOffset)); - responseDocument.Results[0].SingleData.Relationships.Should().BeNull(); + responseDocument.Results.Should().HaveCount(1); + responseDocument.Results[0].SingleData.Should().NotBeNull(); + responseDocument.Results[0].SingleData.Type.Should().Be("performers"); + responseDocument.Results[0].SingleData.Attributes["artistName"].Should().BeNull(); + responseDocument.Results[0].SingleData.Attributes["bornAt"].Should().BeCloseTo(default(DateTimeOffset)); + responseDocument.Results[0].SingleData.Relationships.Should().BeNull(); - string newPerformerId = responseDocument.Results[0].SingleData.Id; + string newPerformerId = responseDocument.Results[0].SingleData.Id; - await _testContext.RunOnDatabaseAsync(async db => - { - Performer performerInDatabase = await db.GetCollection().AsQueryable().FirstWithIdAsync(newPerformerId); + await _testContext.RunOnDatabaseAsync(async db => + { + Performer performerInDatabase = await db.GetCollection().AsQueryable().FirstWithIdAsync(newPerformerId); - performerInDatabase.ArtistName.Should().BeNull(); - performerInDatabase.BornAt.Should().Be(default); - }); - } + performerInDatabase.ArtistName.Should().BeNull(); + performerInDatabase.BornAt.Should().Be(default); + }); } } diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithClientGeneratedIdTests.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithClientGeneratedIdTests.cs index 6ef4930..c854c63 100644 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithClientGeneratedIdTests.cs +++ b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithClientGeneratedIdTests.cs @@ -1,6 +1,4 @@ using System.Net; -using System.Net.Http; -using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Serialization.Objects; @@ -9,181 +7,180 @@ using MongoDB.Driver; using Xunit; -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Creating +namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Creating; + +[Collection("AtomicOperationsFixture")] +public sealed class AtomicCreateResourceWithClientGeneratedIdTests { - [Collection("AtomicOperationsFixture")] - public sealed class AtomicCreateResourceWithClientGeneratedIdTests + private readonly IntegrationTestContext _testContext; + private readonly OperationsFakers _fakers = new(); + + public AtomicCreateResourceWithClientGeneratedIdTests(AtomicOperationsFixture fixture) { - private readonly IntegrationTestContext _testContext; - private readonly OperationsFakers _fakers = new OperationsFakers(); + _testContext = fixture.TestContext; - public AtomicCreateResourceWithClientGeneratedIdTests(AtomicOperationsFixture fixture) - { - _testContext = fixture.TestContext; + fixture.TestContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); - fixture.TestContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); + var options = (JsonApiOptions)fixture.TestContext.Factory.Services.GetRequiredService(); + options.AllowClientGeneratedIds = true; + } - var options = (JsonApiOptions)fixture.TestContext.Factory.Services.GetRequiredService(); - options.AllowClientGeneratedIds = true; - } + [Fact] + public async Task Can_create_resource_with_client_generated_string_ID_having_side_effects() + { + // Arrange + TextLanguage newLanguage = _fakers.TextLanguage.Generate(); + newLanguage.Id = "507f191e810c19729de860ea"; - [Fact] - public async Task Can_create_resource_with_client_generated_string_ID_having_side_effects() + await _testContext.RunOnDatabaseAsync(async db => { - // Arrange - TextLanguage newLanguage = _fakers.TextLanguage.Generate(); - newLanguage.Id = "507f191e810c19729de860ea"; - - await _testContext.RunOnDatabaseAsync(async db => - { - await db.EnsureEmptyCollectionAsync(); - }); + await db.EnsureEmptyCollectionAsync(); + }); - var requestBody = new + var requestBody = new + { + atomic__operations = new[] { - atomic__operations = new[] + new { - new + op = "add", + data = new { - op = "add", - data = new + type = "textLanguages", + id = newLanguage.StringId, + attributes = new { - type = "textLanguages", - id = newLanguage.StringId, - attributes = new - { - isoCode = newLanguage.IsoCode - } + isoCode = newLanguage.IsoCode } } } - }; + } + }; - const string route = "/operations"; + const string route = "/operations"; - // Act - (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = - await _testContext.ExecutePostAtomicAsync(route, requestBody); + // Act + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); - responseDocument.Results.Should().HaveCount(1); - responseDocument.Results[0].SingleData.Should().NotBeNull(); - responseDocument.Results[0].SingleData.Type.Should().Be("textLanguages"); - responseDocument.Results[0].SingleData.Attributes["isoCode"].Should().Be(newLanguage.IsoCode); - responseDocument.Results[0].SingleData.Attributes.Should().NotContainKey("concurrencyToken"); - responseDocument.Results[0].SingleData.Relationships.Should().BeNull(); + responseDocument.Results.Should().HaveCount(1); + responseDocument.Results[0].SingleData.Should().NotBeNull(); + responseDocument.Results[0].SingleData.Type.Should().Be("textLanguages"); + responseDocument.Results[0].SingleData.Attributes["isoCode"].Should().Be(newLanguage.IsoCode); + responseDocument.Results[0].SingleData.Attributes.Should().NotContainKey("concurrencyToken"); + responseDocument.Results[0].SingleData.Relationships.Should().BeNull(); - await _testContext.RunOnDatabaseAsync(async db => - { - TextLanguage languageInDatabase = await db.GetCollection().AsQueryable().FirstWithIdAsync(newLanguage.Id); + await _testContext.RunOnDatabaseAsync(async db => + { + TextLanguage languageInDatabase = await db.GetCollection().AsQueryable().FirstWithIdAsync(newLanguage.Id); - languageInDatabase.IsoCode.Should().Be(newLanguage.IsoCode); - }); - } + languageInDatabase.IsoCode.Should().Be(newLanguage.IsoCode); + }); + } - [Fact] - public async Task Can_create_resource_with_client_generated_string_ID_having_no_side_effects() - { - // Arrange - MusicTrack newTrack = _fakers.MusicTrack.Generate(); - newTrack.Id = "5ffcc0d1d69a27c92b8c62dd"; + [Fact] + public async Task Can_create_resource_with_client_generated_string_ID_having_no_side_effects() + { + // Arrange + MusicTrack newTrack = _fakers.MusicTrack.Generate(); + newTrack.Id = "5ffcc0d1d69a27c92b8c62dd"; - await _testContext.RunOnDatabaseAsync(async db => - { - await db.EnsureEmptyCollectionAsync(); - }); + await _testContext.RunOnDatabaseAsync(async db => + { + await db.EnsureEmptyCollectionAsync(); + }); - var requestBody = new + var requestBody = new + { + atomic__operations = new[] { - atomic__operations = new[] + new { - new + op = "add", + data = new { - op = "add", - data = new + type = "musicTracks", + id = newTrack.StringId, + attributes = new { - type = "musicTracks", - id = newTrack.StringId, - attributes = new - { - title = newTrack.Title, - lengthInSeconds = newTrack.LengthInSeconds - } + title = newTrack.Title, + lengthInSeconds = newTrack.LengthInSeconds } } } - }; + } + }; - const string route = "/operations"; + const string route = "/operations"; - // Act - (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + // Act + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); - responseDocument.Should().BeEmpty(); + responseDocument.Should().BeEmpty(); - await _testContext.RunOnDatabaseAsync(async db => - { - MusicTrack trackInDatabase = await db.GetCollection().AsQueryable().FirstWithIdAsync(newTrack.Id); + await _testContext.RunOnDatabaseAsync(async db => + { + MusicTrack trackInDatabase = await db.GetCollection().AsQueryable().FirstWithIdAsync(newTrack.Id); - trackInDatabase.Title.Should().Be(newTrack.Title); - trackInDatabase.LengthInSeconds.Should().BeApproximately(newTrack.LengthInSeconds); - }); - } + trackInDatabase.Title.Should().Be(newTrack.Title); + trackInDatabase.LengthInSeconds.Should().BeApproximately(newTrack.LengthInSeconds); + }); + } - [Fact] - public async Task Cannot_create_resource_for_existing_client_generated_ID() - { - // Arrange - TextLanguage existingLanguage = _fakers.TextLanguage.Generate(); + [Fact] + public async Task Cannot_create_resource_for_existing_client_generated_ID() + { + // Arrange + TextLanguage existingLanguage = _fakers.TextLanguage.Generate(); - TextLanguage languageToCreate = _fakers.TextLanguage.Generate(); + TextLanguage languageToCreate = _fakers.TextLanguage.Generate(); - await _testContext.RunOnDatabaseAsync(async db => - { - await db.GetCollection().InsertOneAsync(existingLanguage); - languageToCreate.Id = existingLanguage.Id; - }); + await _testContext.RunOnDatabaseAsync(async db => + { + await db.GetCollection().InsertOneAsync(existingLanguage); + languageToCreate.Id = existingLanguage.Id; + }); - var requestBody = new + var requestBody = new + { + atomic__operations = new[] { - atomic__operations = new[] + new { - new + op = "add", + data = new { - op = "add", - data = new + type = "textLanguages", + id = languageToCreate.StringId, + attributes = new { - type = "textLanguages", - id = languageToCreate.StringId, - attributes = new - { - isoCode = languageToCreate.IsoCode - } + isoCode = languageToCreate.IsoCode } } } - }; + } + }; - const string route = "/operations"; + const string route = "/operations"; - // Act - (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + // Act + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.Conflict); + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.Conflict); - responseDocument.Errors.Should().HaveCount(1); + responseDocument.Errors.Should().HaveCount(1); - Error error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.Conflict); - error.Title.Should().Be("Another resource with the specified ID already exists."); - error.Detail.Should().Be($"Another resource of type 'textLanguages' with ID '{languageToCreate.StringId}' already exists."); - error.Source.Pointer.Should().Be("/atomic:operations[0]"); - } + Error error = responseDocument.Errors[0]; + error.StatusCode.Should().Be(HttpStatusCode.Conflict); + error.Title.Should().Be("Another resource with the specified ID already exists."); + error.Detail.Should().Be($"Another resource of type 'textLanguages' with ID '{languageToCreate.StringId}' already exists."); + error.Source.Pointer.Should().Be("/atomic:operations[0]"); } } diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithToManyRelationshipTests.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithToManyRelationshipTests.cs index 177497d..2b156de 100644 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithToManyRelationshipTests.cs +++ b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithToManyRelationshipTests.cs @@ -1,92 +1,88 @@ -using System.Collections.Generic; using System.Net; -using System.Net.Http; -using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Serialization.Objects; using JsonApiDotNetCoreMongoDbExampleTests.TestBuildingBlocks; using Xunit; -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Creating +namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Creating; + +[Collection("AtomicOperationsFixture")] +public sealed class AtomicCreateResourceWithToManyRelationshipTests { - [Collection("AtomicOperationsFixture")] - public sealed class AtomicCreateResourceWithToManyRelationshipTests + private readonly IntegrationTestContext _testContext; + private readonly OperationsFakers _fakers = new(); + + public AtomicCreateResourceWithToManyRelationshipTests(AtomicOperationsFixture fixture) { - private readonly IntegrationTestContext _testContext; - private readonly OperationsFakers _fakers = new OperationsFakers(); + _testContext = fixture.TestContext; - public AtomicCreateResourceWithToManyRelationshipTests(AtomicOperationsFixture fixture) - { - _testContext = fixture.TestContext; + fixture.TestContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); + } - fixture.TestContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); - } + [Fact] + public async Task Cannot_create_HasMany_relationship() + { + // Arrange + List existingPerformers = _fakers.Performer.Generate(2); + string newTitle = _fakers.MusicTrack.Generate().Title; - [Fact] - public async Task Cannot_create_HasMany_relationship() + await _testContext.RunOnDatabaseAsync(async db => { - // Arrange - List existingPerformers = _fakers.Performer.Generate(2); - string newTitle = _fakers.MusicTrack.Generate().Title; + await db.GetCollection().InsertManyAsync(existingPerformers); + }); - await _testContext.RunOnDatabaseAsync(async db => - { - await db.GetCollection().InsertManyAsync(existingPerformers); - }); - - var requestBody = new + var requestBody = new + { + atomic__operations = new[] { - atomic__operations = new[] + new { - new + op = "add", + data = new { - op = "add", - data = new + type = "musicTracks", + attributes = new { - type = "musicTracks", - attributes = new - { - title = newTitle - }, - relationships = new + title = newTitle + }, + relationships = new + { + performers = new { - performers = new + data = new[] { - data = new[] + new + { + type = "performers", + id = existingPerformers[0].StringId + }, + new { - new - { - type = "performers", - id = existingPerformers[0].StringId - }, - new - { - type = "performers", - id = existingPerformers[1].StringId - } + type = "performers", + id = existingPerformers[1].StringId } } } } } } - }; + } + }; - const string route = "/operations"; + const string route = "/operations"; - // Act - (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + // Act + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.Should().HaveCount(1); + responseDocument.Errors.Should().HaveCount(1); - Error error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.BadRequest); - error.Title.Should().Be("Relationships are not supported when using MongoDB."); - error.Detail.Should().BeNull(); - error.Source.Pointer.Should().Be("/atomic:operations[0]"); - } + Error error = responseDocument.Errors[0]; + error.StatusCode.Should().Be(HttpStatusCode.BadRequest); + error.Title.Should().Be("Relationships are not supported when using MongoDB."); + error.Detail.Should().BeNull(); + error.Source.Pointer.Should().Be("/atomic:operations[0]"); } } diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithToOneRelationshipTests.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithToOneRelationshipTests.cs index 1c28d6f..a05f779 100644 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithToOneRelationshipTests.cs +++ b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithToOneRelationshipTests.cs @@ -1,78 +1,75 @@ using System.Net; -using System.Net.Http; -using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Serialization.Objects; using JsonApiDotNetCoreMongoDbExampleTests.TestBuildingBlocks; using Xunit; -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Creating +namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Creating; + +[Collection("AtomicOperationsFixture")] +public sealed class AtomicCreateResourceWithToOneRelationshipTests { - [Collection("AtomicOperationsFixture")] - public sealed class AtomicCreateResourceWithToOneRelationshipTests + private readonly IntegrationTestContext _testContext; + private readonly OperationsFakers _fakers = new(); + + public AtomicCreateResourceWithToOneRelationshipTests(AtomicOperationsFixture fixture) { - private readonly IntegrationTestContext _testContext; - private readonly OperationsFakers _fakers = new OperationsFakers(); + _testContext = fixture.TestContext; - public AtomicCreateResourceWithToOneRelationshipTests(AtomicOperationsFixture fixture) - { - _testContext = fixture.TestContext; + fixture.TestContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); + } - fixture.TestContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); - } + [Fact] + public async Task Cannot_create_relationship() + { + // Arrange + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); - [Fact] - public async Task Cannot_create_relationship() + await _testContext.RunOnDatabaseAsync(async db => { - // Arrange - MusicTrack existingTrack = _fakers.MusicTrack.Generate(); - - await _testContext.RunOnDatabaseAsync(async db => - { - await db.GetCollection().InsertOneAsync(existingTrack); - }); + await db.GetCollection().InsertOneAsync(existingTrack); + }); - var requestBody = new + var requestBody = new + { + atomic__operations = new[] { - atomic__operations = new[] + new { - new + op = "add", + data = new { - op = "add", - data = new + type = "lyrics", + relationships = new { - type = "lyrics", - relationships = new + track = new { - track = new + data = new { - data = new - { - type = "musicTracks", - id = existingTrack.StringId - } + type = "musicTracks", + id = existingTrack.StringId } } } } } - }; + } + }; - const string route = "/operations"; + const string route = "/operations"; - // Act - (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + // Act + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.Should().HaveCount(1); + responseDocument.Errors.Should().HaveCount(1); - Error error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.BadRequest); - error.Title.Should().Be("Relationships are not supported when using MongoDB."); - error.Detail.Should().BeNull(); - error.Source.Pointer.Should().Be("/atomic:operations[0]"); - } + Error error = responseDocument.Errors[0]; + error.StatusCode.Should().Be(HttpStatusCode.BadRequest); + error.Title.Should().Be("Relationships are not supported when using MongoDB."); + error.Detail.Should().BeNull(); + error.Source.Pointer.Should().Be("/atomic:operations[0]"); } } diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Deleting/AtomicDeleteResourceTests.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Deleting/AtomicDeleteResourceTests.cs index 9865896..a20fabd 100644 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Deleting/AtomicDeleteResourceTests.cs +++ b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Deleting/AtomicDeleteResourceTests.cs @@ -1,165 +1,161 @@ -using System.Collections.Generic; using System.Net; -using System.Net.Http; -using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Serialization.Objects; using JsonApiDotNetCoreMongoDbExampleTests.TestBuildingBlocks; using MongoDB.Driver; using Xunit; -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Deleting +namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Deleting; + +[Collection("AtomicOperationsFixture")] +public sealed class AtomicDeleteResourceTests { - [Collection("AtomicOperationsFixture")] - public sealed class AtomicDeleteResourceTests + private readonly IntegrationTestContext _testContext; + private readonly OperationsFakers _fakers = new(); + + public AtomicDeleteResourceTests(AtomicOperationsFixture fixture) { - private readonly IntegrationTestContext _testContext; - private readonly OperationsFakers _fakers = new OperationsFakers(); + _testContext = fixture.TestContext; - public AtomicDeleteResourceTests(AtomicOperationsFixture fixture) - { - _testContext = fixture.TestContext; + fixture.TestContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); + } - fixture.TestContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); - } + [Fact] + public async Task Can_delete_existing_resource() + { + // Arrange + Performer existingPerformer = _fakers.Performer.Generate(); - [Fact] - public async Task Can_delete_existing_resource() + await _testContext.RunOnDatabaseAsync(async db => { - // Arrange - Performer existingPerformer = _fakers.Performer.Generate(); + await db.GetCollection().InsertOneAsync(existingPerformer); + }); - await _testContext.RunOnDatabaseAsync(async db => - { - await db.GetCollection().InsertOneAsync(existingPerformer); - }); - - var requestBody = new + var requestBody = new + { + atomic__operations = new[] { - atomic__operations = new[] + new { - new + op = "remove", + @ref = new { - op = "remove", - @ref = new - { - type = "performers", - id = existingPerformer.StringId - } + type = "performers", + id = existingPerformer.StringId } } - }; + } + }; - const string route = "/operations"; + const string route = "/operations"; - // Act - (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + // Act + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); - responseDocument.Should().BeEmpty(); + responseDocument.Should().BeEmpty(); - await _testContext.RunOnDatabaseAsync(async db => - { - Performer performerInDatabase = await db.GetCollection().AsQueryable().FirstWithIdOrDefaultAsync(existingPerformer.Id); + await _testContext.RunOnDatabaseAsync(async db => + { + Performer performerInDatabase = await db.GetCollection().AsQueryable().FirstWithIdOrDefaultAsync(existingPerformer.Id); - performerInDatabase.Should().BeNull(); - }); - } + performerInDatabase.Should().BeNull(); + }); + } - [Fact] - public async Task Can_delete_existing_resources() - { - // Arrange - const int elementCount = 5; + [Fact] + public async Task Can_delete_existing_resources() + { + // Arrange + const int elementCount = 5; - List existingTracks = _fakers.MusicTrack.Generate(elementCount); + List existingTracks = _fakers.MusicTrack.Generate(elementCount); - await _testContext.RunOnDatabaseAsync(async db => - { - await db.ClearCollectionAsync(); - await db.GetCollection().InsertManyAsync(existingTracks); - }); + await _testContext.RunOnDatabaseAsync(async db => + { + await db.ClearCollectionAsync(); + await db.GetCollection().InsertManyAsync(existingTracks); + }); - var operationElements = new List(elementCount); + var operationElements = new List(elementCount); - for (int index = 0; index < elementCount; index++) + for (int index = 0; index < elementCount; index++) + { + operationElements.Add(new { - operationElements.Add(new + op = "remove", + @ref = new { - op = "remove", - @ref = new - { - type = "musicTracks", - id = existingTracks[index].StringId - } - }); - } + type = "musicTracks", + id = existingTracks[index].StringId + } + }); + } - var requestBody = new - { - atomic__operations = operationElements - }; + var requestBody = new + { + atomic__operations = operationElements + }; - const string route = "/operations"; + const string route = "/operations"; - // Act - (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + // Act + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); - responseDocument.Should().BeEmpty(); + responseDocument.Should().BeEmpty(); - await _testContext.RunOnDatabaseAsync(async db => - { - List tracksInDatabase = await db.GetCollection().AsQueryable().ToListAsync(); + await _testContext.RunOnDatabaseAsync(async db => + { + List tracksInDatabase = await db.GetCollection().AsQueryable().ToListAsync(); - tracksInDatabase.Should().BeEmpty(); - }); - } + tracksInDatabase.Should().BeEmpty(); + }); + } - [Fact] - public async Task Cannot_delete_resource_for_unknown_ID() + [Fact] + public async Task Cannot_delete_resource_for_unknown_ID() + { + // Arrange + await _testContext.RunOnDatabaseAsync(async db => { - // Arrange - await _testContext.RunOnDatabaseAsync(async db => - { - await db.EnsureEmptyCollectionAsync(); - }); + await db.EnsureEmptyCollectionAsync(); + }); - var requestBody = new + var requestBody = new + { + atomic__operations = new[] { - atomic__operations = new[] + new { - new + op = "remove", + @ref = new { - op = "remove", - @ref = new - { - type = "performers", - id = "ffffffffffffffffffffffff" - } + type = "performers", + id = "ffffffffffffffffffffffff" } } - }; + } + }; - const string route = "/operations"; + const string route = "/operations"; - // Act - (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + // Act + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.NotFound); + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.NotFound); - responseDocument.Errors.Should().HaveCount(1); + responseDocument.Errors.Should().HaveCount(1); - Error error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.NotFound); - error.Title.Should().Be("The requested resource does not exist."); - error.Detail.Should().Be("Resource of type 'performers' with ID 'ffffffffffffffffffffffff' does not exist."); - error.Source.Pointer.Should().Be("/atomic:operations[0]"); - } + Error error = responseDocument.Errors[0]; + error.StatusCode.Should().Be(HttpStatusCode.NotFound); + error.Title.Should().Be("The requested resource does not exist."); + error.Detail.Should().Be("Resource of type 'performers' with ID 'ffffffffffffffffffffffff' does not exist."); + error.Source.Pointer.Should().Be("/atomic:operations[0]"); } } diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/LocalIds/AtomicLocalIdTests.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/LocalIds/AtomicLocalIdTests.cs index 884ae8d..2072695 100644 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/LocalIds/AtomicLocalIdTests.cs +++ b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/LocalIds/AtomicLocalIdTests.cs @@ -1,172 +1,169 @@ using System.Net; -using System.Net.Http; -using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Serialization.Objects; using JsonApiDotNetCoreMongoDbExampleTests.TestBuildingBlocks; using MongoDB.Driver; using Xunit; -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.LocalIds +namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.LocalIds; + +[Collection("AtomicOperationsFixture")] +public sealed class AtomicLocalIdTests { - [Collection("AtomicOperationsFixture")] - public sealed class AtomicLocalIdTests + private readonly IntegrationTestContext _testContext; + private readonly OperationsFakers _fakers = new(); + + public AtomicLocalIdTests(AtomicOperationsFixture fixture) { - private readonly IntegrationTestContext _testContext; - private readonly OperationsFakers _fakers = new OperationsFakers(); + _testContext = fixture.TestContext; - public AtomicLocalIdTests(AtomicOperationsFixture fixture) - { - _testContext = fixture.TestContext; + fixture.TestContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); + } - fixture.TestContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); - } + [Fact] + public async Task Can_update_resource_using_local_ID() + { + // Arrange + string newTrackTitle = _fakers.MusicTrack.Generate().Title; + string newTrackGenre = _fakers.MusicTrack.Generate().Genre; - [Fact] - public async Task Can_update_resource_using_local_ID() + await _testContext.RunOnDatabaseAsync(async db => { - // Arrange - string newTrackTitle = _fakers.MusicTrack.Generate().Title; - string newTrackGenre = _fakers.MusicTrack.Generate().Genre; - - await _testContext.RunOnDatabaseAsync(async db => - { - await db.EnsureEmptyCollectionAsync(); - }); + await db.EnsureEmptyCollectionAsync(); + }); - const string trackLocalId = "track-1"; + const string trackLocalId = "track-1"; - var requestBody = new + var requestBody = new + { + atomic__operations = new object[] { - atomic__operations = new object[] + new { - new + op = "add", + data = new { - op = "add", - data = new + type = "musicTracks", + lid = trackLocalId, + attributes = new { - type = "musicTracks", - lid = trackLocalId, - attributes = new - { - title = newTrackTitle - } + title = newTrackTitle } - }, - new + } + }, + new + { + op = "update", + data = new { - op = "update", - data = new + type = "musicTracks", + lid = trackLocalId, + attributes = new { - type = "musicTracks", - lid = trackLocalId, - attributes = new - { - genre = newTrackGenre - } + genre = newTrackGenre } } } - }; + } + }; - const string route = "/operations"; + const string route = "/operations"; - // Act - (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = - await _testContext.ExecutePostAtomicAsync(route, requestBody); + // Act + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); - responseDocument.Results.Should().HaveCount(2); + responseDocument.Results.Should().HaveCount(2); - responseDocument.Results[0].SingleData.Should().NotBeNull(); - responseDocument.Results[0].SingleData.Type.Should().Be("musicTracks"); - responseDocument.Results[0].SingleData.Lid.Should().BeNull(); - responseDocument.Results[0].SingleData.Attributes["title"].Should().Be(newTrackTitle); - responseDocument.Results[0].SingleData.Attributes["genre"].Should().BeNull(); + responseDocument.Results[0].SingleData.Should().NotBeNull(); + responseDocument.Results[0].SingleData.Type.Should().Be("musicTracks"); + responseDocument.Results[0].SingleData.Lid.Should().BeNull(); + responseDocument.Results[0].SingleData.Attributes["title"].Should().Be(newTrackTitle); + responseDocument.Results[0].SingleData.Attributes["genre"].Should().BeNull(); - responseDocument.Results[1].Data.Should().BeNull(); + responseDocument.Results[1].Data.Should().BeNull(); - string newTrackId = responseDocument.Results[0].SingleData.Id; + string newTrackId = responseDocument.Results[0].SingleData.Id; - await _testContext.RunOnDatabaseAsync(async db => - { - MusicTrack trackInDatabase = await db.GetCollection().AsQueryable().FirstWithIdAsync(newTrackId); + await _testContext.RunOnDatabaseAsync(async db => + { + MusicTrack trackInDatabase = await db.GetCollection().AsQueryable().FirstWithIdAsync(newTrackId); - trackInDatabase.Title.Should().Be(newTrackTitle); - trackInDatabase.Genre.Should().Be(newTrackGenre); - }); - } + trackInDatabase.Title.Should().Be(newTrackTitle); + trackInDatabase.Genre.Should().Be(newTrackGenre); + }); + } - [Fact] - public async Task Can_delete_resource_using_local_ID() - { - // Arrange - string newTrackTitle = _fakers.MusicTrack.Generate().Title; + [Fact] + public async Task Can_delete_resource_using_local_ID() + { + // Arrange + string newTrackTitle = _fakers.MusicTrack.Generate().Title; - await _testContext.RunOnDatabaseAsync(async db => - { - await db.EnsureEmptyCollectionAsync(); - }); + await _testContext.RunOnDatabaseAsync(async db => + { + await db.EnsureEmptyCollectionAsync(); + }); - const string trackLocalId = "track-1"; + const string trackLocalId = "track-1"; - var requestBody = new + var requestBody = new + { + atomic__operations = new object[] { - atomic__operations = new object[] + new { - new + op = "add", + data = new { - op = "add", - data = new + type = "musicTracks", + lid = trackLocalId, + attributes = new { - type = "musicTracks", - lid = trackLocalId, - attributes = new - { - title = newTrackTitle - } + title = newTrackTitle } - }, - new + } + }, + new + { + op = "remove", + @ref = new { - op = "remove", - @ref = new - { - type = "musicTracks", - lid = trackLocalId - } + type = "musicTracks", + lid = trackLocalId } } - }; + } + }; - const string route = "/operations"; + const string route = "/operations"; - // Act - (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = - await _testContext.ExecutePostAtomicAsync(route, requestBody); + // Act + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); - responseDocument.Results.Should().HaveCount(2); + responseDocument.Results.Should().HaveCount(2); - responseDocument.Results[0].SingleData.Should().NotBeNull(); - responseDocument.Results[0].SingleData.Type.Should().Be("musicTracks"); - responseDocument.Results[0].SingleData.Lid.Should().BeNull(); - responseDocument.Results[0].SingleData.Attributes["title"].Should().Be(newTrackTitle); + responseDocument.Results[0].SingleData.Should().NotBeNull(); + responseDocument.Results[0].SingleData.Type.Should().Be("musicTracks"); + responseDocument.Results[0].SingleData.Lid.Should().BeNull(); + responseDocument.Results[0].SingleData.Attributes["title"].Should().Be(newTrackTitle); - responseDocument.Results[1].Data.Should().BeNull(); + responseDocument.Results[1].Data.Should().BeNull(); - string newTrackId = responseDocument.Results[0].SingleData.Id; + string newTrackId = responseDocument.Results[0].SingleData.Id; - await _testContext.RunOnDatabaseAsync(async db => - { - MusicTrack trackInDatabase = await db.GetCollection().AsQueryable().FirstWithIdOrDefaultAsync(newTrackId); + await _testContext.RunOnDatabaseAsync(async db => + { + MusicTrack trackInDatabase = await db.GetCollection().AsQueryable().FirstWithIdOrDefaultAsync(newTrackId); - trackInDatabase.Should().BeNull(); - }); - } + trackInDatabase.Should().BeNull(); + }); } } diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Lyric.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Lyric.cs index c9a23f4..bbd7502 100644 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Lyric.cs +++ b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Lyric.cs @@ -1,29 +1,27 @@ -using System; using JetBrains.Annotations; using JsonApiDotNetCore.MongoDb.Resources; using JsonApiDotNetCore.Resources.Annotations; using MongoDB.Bson.Serialization.Attributes; -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations +namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations; + +[UsedImplicitly(ImplicitUseTargetFlags.Members)] +public sealed class Lyric : MongoIdentifiable { - [UsedImplicitly(ImplicitUseTargetFlags.Members)] - public sealed class Lyric : MongoIdentifiable - { - [Attr] - public string Format { get; set; } + [Attr] + public string Format { get; set; } - [Attr] - public string Text { get; set; } + [Attr] + public string Text { get; set; } - [Attr(Capabilities = AttrCapabilities.None)] - public DateTimeOffset CreatedAt { get; set; } + [Attr(Capabilities = AttrCapabilities.None)] + public DateTimeOffset CreatedAt { get; set; } - [HasOne] - [BsonIgnore] - public TextLanguage Language { get; set; } + [HasOne] + [BsonIgnore] + public TextLanguage Language { get; set; } - [HasOne] - [BsonIgnore] - public MusicTrack Track { get; set; } - } + [HasOne] + [BsonIgnore] + public MusicTrack Track { get; set; } } diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Meta/AtomicResourceMetaTests.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Meta/AtomicResourceMetaTests.cs index 48c3a2d..6dc7391 100644 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Meta/AtomicResourceMetaTests.cs +++ b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Meta/AtomicResourceMetaTests.cs @@ -1,132 +1,129 @@ using System.Net; -using System.Net.Http; -using System.Threading.Tasks; using FluentAssertions; using FluentAssertions.Extensions; using JsonApiDotNetCore.Serialization.Objects; using JsonApiDotNetCoreMongoDbExampleTests.TestBuildingBlocks; using Xunit; -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Meta +namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Meta; + +[Collection("AtomicOperationsFixture")] +public sealed class AtomicResourceMetaTests { - [Collection("AtomicOperationsFixture")] - public sealed class AtomicResourceMetaTests + private readonly IntegrationTestContext _testContext; + private readonly OperationsFakers _fakers = new(); + + public AtomicResourceMetaTests(AtomicOperationsFixture fixture) { - private readonly IntegrationTestContext _testContext; - private readonly OperationsFakers _fakers = new OperationsFakers(); + _testContext = fixture.TestContext; - public AtomicResourceMetaTests(AtomicOperationsFixture fixture) - { - _testContext = fixture.TestContext; + fixture.TestContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); + } - fixture.TestContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); - } + [Fact] + public async Task Returns_resource_meta_in_create_resource_with_side_effects() + { + // Arrange + string newTitle1 = _fakers.MusicTrack.Generate().Title; + string newTitle2 = _fakers.MusicTrack.Generate().Title; - [Fact] - public async Task Returns_resource_meta_in_create_resource_with_side_effects() + await _testContext.RunOnDatabaseAsync(async db => { - // Arrange - string newTitle1 = _fakers.MusicTrack.Generate().Title; - string newTitle2 = _fakers.MusicTrack.Generate().Title; - - await _testContext.RunOnDatabaseAsync(async db => - { - await db.EnsureEmptyCollectionAsync(); - }); + await db.EnsureEmptyCollectionAsync(); + }); - var requestBody = new + var requestBody = new + { + atomic__operations = new[] { - atomic__operations = new[] + new { - new + op = "add", + data = new { - op = "add", - data = new + type = "musicTracks", + attributes = new { - type = "musicTracks", - attributes = new - { - title = newTitle1, - releasedAt = 1.January(2018) - } + title = newTitle1, + releasedAt = 1.January(2018) } - }, - new + } + }, + new + { + op = "add", + data = new { - op = "add", - data = new + type = "musicTracks", + attributes = new { - type = "musicTracks", - attributes = new - { - title = newTitle2, - releasedAt = 23.August(1994) - } + title = newTitle2, + releasedAt = 23.August(1994) } } } - }; + } + }; - const string route = "/operations"; + const string route = "/operations"; - // Act - (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = - await _testContext.ExecutePostAtomicAsync(route, requestBody); + // Act + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); - responseDocument.Results.Should().HaveCount(2); + responseDocument.Results.Should().HaveCount(2); - responseDocument.Results[0].SingleData.Meta.Should().HaveCount(1); - responseDocument.Results[0].SingleData.Meta["Copyright"].Should().Be("(C) 2018. All rights reserved."); + responseDocument.Results[0].SingleData.Meta.Should().HaveCount(1); + responseDocument.Results[0].SingleData.Meta["Copyright"].Should().Be("(C) 2018. All rights reserved."); - responseDocument.Results[1].SingleData.Meta.Should().HaveCount(1); - responseDocument.Results[1].SingleData.Meta["Copyright"].Should().Be("(C) 1994. All rights reserved."); - } + responseDocument.Results[1].SingleData.Meta.Should().HaveCount(1); + responseDocument.Results[1].SingleData.Meta["Copyright"].Should().Be("(C) 1994. All rights reserved."); + } - [Fact] - public async Task Returns_resource_meta_in_update_resource_with_side_effects() - { - // Arrange - TextLanguage existingLanguage = _fakers.TextLanguage.Generate(); + [Fact] + public async Task Returns_resource_meta_in_update_resource_with_side_effects() + { + // Arrange + TextLanguage existingLanguage = _fakers.TextLanguage.Generate(); - await _testContext.RunOnDatabaseAsync(async db => - { - await db.GetCollection().InsertOneAsync(existingLanguage); - }); + await _testContext.RunOnDatabaseAsync(async db => + { + await db.GetCollection().InsertOneAsync(existingLanguage); + }); - var requestBody = new + var requestBody = new + { + atomic__operations = new[] { - atomic__operations = new[] + new { - new + op = "update", + data = new { - op = "update", - data = new + type = "textLanguages", + id = existingLanguage.StringId, + attributes = new { - type = "textLanguages", - id = existingLanguage.StringId, - attributes = new - { - } } } } - }; + } + }; - const string route = "/operations"; + const string route = "/operations"; - // Act - (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = - await _testContext.ExecutePostAtomicAsync(route, requestBody); + // Act + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); - responseDocument.Results.Should().HaveCount(1); - responseDocument.Results[0].SingleData.Meta.Should().HaveCount(1); - responseDocument.Results[0].SingleData.Meta["Notice"].Should().Be(TextLanguageMetaDefinition.NoticeText); - } + responseDocument.Results.Should().HaveCount(1); + responseDocument.Results[0].SingleData.Meta.Should().HaveCount(1); + responseDocument.Results[0].SingleData.Meta["Notice"].Should().Be(TextLanguageMetaDefinition.NoticeText); } } diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Meta/MusicTrackMetaDefinition.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Meta/MusicTrackMetaDefinition.cs index 13dfb4d..9b1cc27 100644 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Meta/MusicTrackMetaDefinition.cs +++ b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Meta/MusicTrackMetaDefinition.cs @@ -1,24 +1,22 @@ -using System.Collections.Generic; using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Resources; -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Meta +namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Meta; + +[UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] +public sealed class MusicTrackMetaDefinition : JsonApiResourceDefinition { - [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] - public sealed class MusicTrackMetaDefinition : JsonApiResourceDefinition + public MusicTrackMetaDefinition(IResourceGraph resourceGraph) + : base(resourceGraph) { - public MusicTrackMetaDefinition(IResourceGraph resourceGraph) - : base(resourceGraph) - { - } + } - public override IDictionary GetMeta(MusicTrack resource) + public override IDictionary GetMeta(MusicTrack resource) + { + return new Dictionary { - return new Dictionary - { - ["Copyright"] = $"(C) {resource.ReleasedAt.Year}. All rights reserved." - }; - } + ["Copyright"] = $"(C) {resource.ReleasedAt.Year}. All rights reserved." + }; } } diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Meta/TextLanguageMetaDefinition.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Meta/TextLanguageMetaDefinition.cs index 566ad3f..f21a249 100644 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Meta/TextLanguageMetaDefinition.cs +++ b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Meta/TextLanguageMetaDefinition.cs @@ -1,26 +1,24 @@ -using System.Collections.Generic; using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Resources; -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Meta +namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Meta; + +[UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] +public sealed class TextLanguageMetaDefinition : JsonApiResourceDefinition { - [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] - public sealed class TextLanguageMetaDefinition : JsonApiResourceDefinition - { - internal const string NoticeText = "See https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes for ISO 639-1 language codes."; + internal const string NoticeText = "See https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes for ISO 639-1 language codes."; - public TextLanguageMetaDefinition(IResourceGraph resourceGraph) - : base(resourceGraph) - { - } + public TextLanguageMetaDefinition(IResourceGraph resourceGraph) + : base(resourceGraph) + { + } - public override IDictionary GetMeta(TextLanguage resource) + public override IDictionary GetMeta(TextLanguage resource) + { + return new Dictionary { - return new Dictionary - { - ["Notice"] = NoticeText - }; - } + ["Notice"] = NoticeText + }; } } diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Mixed/MaximumOperationsPerRequestTests.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Mixed/MaximumOperationsPerRequestTests.cs index b0bc94c..20f809b 100644 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Mixed/MaximumOperationsPerRequestTests.cs +++ b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Mixed/MaximumOperationsPerRequestTests.cs @@ -1,70 +1,66 @@ -using System.Collections.Generic; using System.Net; -using System.Net.Http; -using System.Threading.Tasks; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Serialization.Objects; using JsonApiDotNetCoreMongoDbExampleTests.TestBuildingBlocks; using Microsoft.Extensions.DependencyInjection; using Xunit; -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Mixed +namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Mixed; + +[Collection("AtomicOperationsFixture")] +public sealed class MaximumOperationsPerRequestTests { - [Collection("AtomicOperationsFixture")] - public sealed class MaximumOperationsPerRequestTests + private readonly IntegrationTestContext _testContext; + + public MaximumOperationsPerRequestTests(AtomicOperationsFixture fixture) { - private readonly IntegrationTestContext _testContext; + _testContext = fixture.TestContext; - public MaximumOperationsPerRequestTests(AtomicOperationsFixture fixture) - { - _testContext = fixture.TestContext; + fixture.TestContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); + } - fixture.TestContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); - } + [Fact] + public async Task Can_process_high_number_of_operations_when_unconstrained() + { + // Arrange + var options = (JsonApiOptions)_testContext.Factory.Services.GetRequiredService(); + options.MaximumOperationsPerRequest = null; - [Fact] - public async Task Can_process_high_number_of_operations_when_unconstrained() + await _testContext.RunOnDatabaseAsync(async db => { - // Arrange - var options = (JsonApiOptions)_testContext.Factory.Services.GetRequiredService(); - options.MaximumOperationsPerRequest = null; + await db.EnsureEmptyCollectionAsync(); + }); - await _testContext.RunOnDatabaseAsync(async db => - { - await db.EnsureEmptyCollectionAsync(); - }); + const int elementCount = 100; - const int elementCount = 100; + var operationElements = new List(elementCount); - var operationElements = new List(elementCount); - - for (int index = 0; index < elementCount; index++) + for (int index = 0; index < elementCount; index++) + { + operationElements.Add(new { - operationElements.Add(new + op = "add", + data = new { - op = "add", - data = new + type = "performers", + attributes = new { - type = "performers", - attributes = new - { - } } - }); - } + } + }); + } - var requestBody = new - { - atomic__operations = operationElements - }; + var requestBody = new + { + atomic__operations = operationElements + }; - const string route = "/operations"; + const string route = "/operations"; - // Act - (HttpResponseMessage httpResponse, _) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + // Act + (HttpResponseMessage httpResponse, _) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); - } + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); } } diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/MusicTrack.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/MusicTrack.cs index 25a1b7f..ae694e3 100644 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/MusicTrack.cs +++ b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/MusicTrack.cs @@ -1,43 +1,40 @@ -using System; -using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using JetBrains.Annotations; using JsonApiDotNetCore.MongoDb.Resources; using JsonApiDotNetCore.Resources.Annotations; using MongoDB.Bson.Serialization.Attributes; -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations +namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations; + +[UsedImplicitly(ImplicitUseTargetFlags.Members)] +public sealed class MusicTrack : MongoIdentifiable { - [UsedImplicitly(ImplicitUseTargetFlags.Members)] - public sealed class MusicTrack : MongoIdentifiable - { - [RegularExpression(@"(?im)^[{(]?[0-9A-F]{8}[-]?(?:[0-9A-F]{4}[-]?){3}[0-9A-F]{12}[)}]?$")] - public override string Id { get; set; } - - [Attr] - [Required] - public string Title { get; set; } - - [Attr] - [Range(1, 24 * 60)] - public decimal? LengthInSeconds { get; set; } - - [Attr] - public string Genre { get; set; } - - [Attr] - public DateTimeOffset ReleasedAt { get; set; } - - [HasOne] - [BsonIgnore] - public Lyric Lyric { get; set; } - - [HasOne] - [BsonIgnore] - public RecordCompany OwnedBy { get; set; } - - [HasMany] - [BsonIgnore] - public IList Performers { get; set; } - } + [RegularExpression(@"(?im)^[{(]?[0-9A-F]{8}[-]?(?:[0-9A-F]{4}[-]?){3}[0-9A-F]{12}[)}]?$")] + public override string Id { get; set; } + + [Attr] + [Required] + public string Title { get; set; } + + [Attr] + [Range(1, 24 * 60)] + public decimal? LengthInSeconds { get; set; } + + [Attr] + public string Genre { get; set; } + + [Attr] + public DateTimeOffset ReleasedAt { get; set; } + + [HasOne] + [BsonIgnore] + public Lyric Lyric { get; set; } + + [HasOne] + [BsonIgnore] + public RecordCompany OwnedBy { get; set; } + + [HasMany] + [BsonIgnore] + public IList Performers { get; set; } } diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/MusicTracksController.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/MusicTracksController.cs index feb52e8..7a46dc6 100644 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/MusicTracksController.cs +++ b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/MusicTracksController.cs @@ -3,13 +3,12 @@ using JsonApiDotNetCore.Services; using Microsoft.Extensions.Logging; -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations +namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations; + +public sealed class MusicTracksController : JsonApiController { - public sealed class MusicTracksController : JsonApiController + public MusicTracksController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) + : base(options, loggerFactory, resourceService) { - public MusicTracksController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) - : base(options, loggerFactory, resourceService) - { - } } } diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/OperationsFakers.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/OperationsFakers.cs index c750587..1e4cd1e 100644 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/OperationsFakers.cs +++ b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/OperationsFakers.cs @@ -1,65 +1,61 @@ -using System; -using System.Collections.Generic; using System.Globalization; -using System.Linq; using Bogus; using JsonApiDotNetCoreMongoDbExampleTests.TestBuildingBlocks; // @formatter:wrap_chained_method_calls chop_always // @formatter:keep_existing_linebreaks true -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations -{ - internal sealed class OperationsFakers : FakerContainer - { - private static readonly Lazy> LazyLanguageIsoCodes = - new Lazy>(() => CultureInfo - .GetCultures(CultureTypes.NeutralCultures) - .Where(culture => !string.IsNullOrEmpty(culture.Name)) - .Select(culture => culture.Name) - .ToArray()); - - private readonly Lazy> _lazyPlaylistFaker = new Lazy>(() => - new Faker() - .UseSeed(GetFakerSeed()) - .RuleFor(playlist => playlist.Name, faker => faker.Lorem.Sentence())); - - private readonly Lazy> _lazyMusicTrackFaker = new Lazy>(() => - 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())); - - private readonly Lazy> _lazyLyricFaker = new Lazy>(() => - new Faker() - .UseSeed(GetFakerSeed()) - .RuleFor(lyric => lyric.Text, faker => faker.Lorem.Text()) - .RuleFor(lyric => lyric.Format, "LRC")); +namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations; - private readonly Lazy> _lazyTextLanguageFaker = new Lazy>(() => - new Faker() - .UseSeed(GetFakerSeed()) - .RuleFor(textLanguage => textLanguage.IsoCode, faker => faker.PickRandom(LazyLanguageIsoCodes.Value))); - - private readonly Lazy> _lazyPerformerFaker = new Lazy>(() => - new Faker() - .UseSeed(GetFakerSeed()) - .RuleFor(performer => performer.ArtistName, faker => faker.Name.FullName()) - .RuleFor(performer => performer.BornAt, faker => faker.Date.PastOffset())); - - private readonly Lazy> _lazyRecordCompanyFaker = new Lazy>(() => - 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; - public Faker Lyric => _lazyLyricFaker.Value; - public Faker TextLanguage => _lazyTextLanguageFaker.Value; - public Faker Performer => _lazyPerformerFaker.Value; - public Faker RecordCompany => _lazyRecordCompanyFaker.Value; - } +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())); + + 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())); + + 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; + public Faker Lyric => _lazyLyricFaker.Value; + public Faker TextLanguage => _lazyTextLanguageFaker.Value; + public Faker Performer => _lazyPerformerFaker.Value; + public Faker RecordCompany => _lazyRecordCompanyFaker.Value; } diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Performer.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Performer.cs index 5d15d9c..bbdd1ef 100644 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Performer.cs +++ b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Performer.cs @@ -1,17 +1,15 @@ -using System; using JetBrains.Annotations; using JsonApiDotNetCore.MongoDb.Resources; using JsonApiDotNetCore.Resources.Annotations; -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations +namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations; + +[UsedImplicitly(ImplicitUseTargetFlags.Members)] +public sealed class Performer : MongoIdentifiable { - [UsedImplicitly(ImplicitUseTargetFlags.Members)] - public sealed class Performer : MongoIdentifiable - { - [Attr] - public string ArtistName { get; set; } + [Attr] + public string ArtistName { get; set; } - [Attr] - public DateTimeOffset BornAt { get; set; } - } + [Attr] + public DateTimeOffset BornAt { get; set; } } diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Playlist.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Playlist.cs index e76652d..9711bb1 100644 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Playlist.cs +++ b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Playlist.cs @@ -1,28 +1,26 @@ -using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using JetBrains.Annotations; using JsonApiDotNetCore.MongoDb.Resources; using JsonApiDotNetCore.Resources.Annotations; using MongoDB.Bson.Serialization.Attributes; -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations +namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations; + +[UsedImplicitly(ImplicitUseTargetFlags.Members)] +public sealed class Playlist : MongoIdentifiable { - [UsedImplicitly(ImplicitUseTargetFlags.Members)] - public sealed class Playlist : MongoIdentifiable - { - [Attr] - [Required] - public string Name { get; set; } + [Attr] + [Required] + public string Name { get; set; } - [Attr] - [BsonIgnore] - public bool IsArchived => false; + [Attr] + [BsonIgnore] + public bool IsArchived => false; - [HasManyThrough(nameof(PlaylistMusicTracks))] - [BsonIgnore] - public IList Tracks { get; set; } + [HasManyThrough(nameof(PlaylistMusicTracks))] + [BsonIgnore] + public IList Tracks { get; set; } - [BsonIgnore] - public IList PlaylistMusicTracks { get; set; } - } + [BsonIgnore] + public IList PlaylistMusicTracks { get; set; } } diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/PlaylistMusicTrack.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/PlaylistMusicTrack.cs index 8f6ef90..e9bd586 100644 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/PlaylistMusicTrack.cs +++ b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/PlaylistMusicTrack.cs @@ -1,15 +1,13 @@ -using System; using JetBrains.Annotations; -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations +namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations; + +[UsedImplicitly(ImplicitUseTargetFlags.Members)] +public sealed class PlaylistMusicTrack { - [UsedImplicitly(ImplicitUseTargetFlags.Members)] - public sealed class PlaylistMusicTrack - { - public long PlaylistId { get; set; } - public Playlist Playlist { get; set; } + public long PlaylistId { get; set; } + public Playlist Playlist { get; set; } - public Guid MusicTrackId { get; set; } - public MusicTrack MusicTrack { get; set; } - } + public Guid MusicTrackId { get; set; } + public MusicTrack MusicTrack { get; set; } } diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/RecordCompany.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/RecordCompany.cs index 0295252..f31c2df 100644 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/RecordCompany.cs +++ b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/RecordCompany.cs @@ -1,26 +1,24 @@ -using System.Collections.Generic; using JetBrains.Annotations; using JsonApiDotNetCore.MongoDb.Resources; using JsonApiDotNetCore.Resources.Annotations; using MongoDB.Bson.Serialization.Attributes; -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations +namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations; + +[UsedImplicitly(ImplicitUseTargetFlags.Members)] +public sealed class RecordCompany : MongoIdentifiable { - [UsedImplicitly(ImplicitUseTargetFlags.Members)] - public sealed class RecordCompany : MongoIdentifiable - { - [Attr] - public string Name { get; set; } + [Attr] + public string Name { get; set; } - [Attr] - public string CountryOfResidence { get; set; } + [Attr] + public string CountryOfResidence { get; set; } - [HasMany] - [BsonIgnore] - public IList Tracks { get; set; } + [HasMany] + [BsonIgnore] + public IList Tracks { get; set; } - [HasOne] - [BsonIgnore] - public RecordCompany Parent { get; set; } - } + [HasOne] + [BsonIgnore] + public RecordCompany Parent { get; set; } } diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/TextLanguage.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/TextLanguage.cs index 84d1288..61a4c33 100644 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/TextLanguage.cs +++ b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/TextLanguage.cs @@ -1,28 +1,25 @@ -using System; -using System.Collections.Generic; using JetBrains.Annotations; using JsonApiDotNetCore.MongoDb.Resources; using JsonApiDotNetCore.Resources.Annotations; using MongoDB.Bson.Serialization.Attributes; -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations -{ - [UsedImplicitly(ImplicitUseTargetFlags.Members)] - public sealed class TextLanguage : MongoIdentifiable - { - [Attr] - public string IsoCode { get; set; } +namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations; - [Attr(Capabilities = AttrCapabilities.None)] - [BsonIgnore] - public Guid ConcurrencyToken - { - get => Guid.NewGuid(); - set => _ = value; - } +[UsedImplicitly(ImplicitUseTargetFlags.Members)] +public sealed class TextLanguage : MongoIdentifiable +{ + [Attr] + public string IsoCode { get; set; } - [HasMany] - [BsonIgnore] - public ICollection Lyrics { get; set; } + [Attr(Capabilities = AttrCapabilities.None)] + [BsonIgnore] + public Guid ConcurrencyToken + { + get => Guid.NewGuid(); + set => _ = value; } + + [HasMany] + [BsonIgnore] + public ICollection Lyrics { get; set; } } diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Transactions/AtomicRollbackTests.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Transactions/AtomicRollbackTests.cs index 34b8954..a8efe23 100644 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Transactions/AtomicRollbackTests.cs +++ b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Transactions/AtomicRollbackTests.cs @@ -1,220 +1,215 @@ -using System; -using System.Collections.Generic; using System.Net; -using System.Net.Http; -using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Serialization.Objects; using JsonApiDotNetCoreMongoDbExampleTests.TestBuildingBlocks; using MongoDB.Driver; using Xunit; -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Transactions +namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Transactions; + +[Collection("AtomicOperationsFixture")] +public sealed class AtomicRollbackTests { - [Collection("AtomicOperationsFixture")] - public sealed class AtomicRollbackTests + private readonly IntegrationTestContext _testContext; + private readonly OperationsFakers _fakers = new(); + + public AtomicRollbackTests(AtomicOperationsFixture fixture) { - private readonly IntegrationTestContext _testContext; - private readonly OperationsFakers _fakers = new OperationsFakers(); + _testContext = fixture.TestContext; - public AtomicRollbackTests(AtomicOperationsFixture fixture) - { - _testContext = fixture.TestContext; + fixture.TestContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); + } - fixture.TestContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); - } + [Fact] + public async Task Can_rollback_created_resource_on_error() + { + // Arrange + string newArtistName = _fakers.Performer.Generate().ArtistName; + DateTimeOffset newBornAt = _fakers.Performer.Generate().BornAt; - [Fact] - public async Task Can_rollback_created_resource_on_error() + await _testContext.RunOnDatabaseAsync(async db => { - // Arrange - string newArtistName = _fakers.Performer.Generate().ArtistName; - DateTimeOffset newBornAt = _fakers.Performer.Generate().BornAt; + await db.EnsureEmptyCollectionAsync(); + }); - await _testContext.RunOnDatabaseAsync(async db => - { - await db.EnsureEmptyCollectionAsync(); - }); - - var requestBody = new + var requestBody = new + { + atomic__operations = new object[] { - atomic__operations = new object[] + new { - new + op = "add", + id = "507f191e810c19729de860ea", + data = new { - op = "add", - id = "507f191e810c19729de860ea", - data = new + type = "performers", + attributes = new { - type = "performers", - attributes = new - { - artistName = newArtistName, - bornAt = newBornAt - } + artistName = newArtistName, + bornAt = newBornAt } - }, - new + } + }, + new + { + op = "remove", + @ref = new { - op = "remove", - @ref = new - { - type = "performers", - id = "ffffffffffffffffffffffff" - } + type = "performers", + id = "ffffffffffffffffffffffff" } } - }; + } + }; - const string route = "/operations"; + const string route = "/operations"; - // Act - (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + // Act + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.NotFound); + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.NotFound); - responseDocument.Errors.Should().HaveCount(1); + responseDocument.Errors.Should().HaveCount(1); - Error error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.NotFound); - error.Title.Should().Be("The requested resource does not exist."); - error.Detail.Should().Be("Resource of type 'performers' with ID 'ffffffffffffffffffffffff' does not exist."); - error.Source.Pointer.Should().Be("/atomic:operations[1]"); + Error error = responseDocument.Errors[0]; + error.StatusCode.Should().Be(HttpStatusCode.NotFound); + error.Title.Should().Be("The requested resource does not exist."); + error.Detail.Should().Be("Resource of type 'performers' with ID 'ffffffffffffffffffffffff' does not exist."); + error.Source.Pointer.Should().Be("/atomic:operations[1]"); - await _testContext.RunOnDatabaseAsync(async db => - { - List performersInDatabase = await db.GetCollection().AsQueryable().ToListAsync(); + await _testContext.RunOnDatabaseAsync(async db => + { + List performersInDatabase = await db.GetCollection().AsQueryable().ToListAsync(); - performersInDatabase.Should().BeEmpty(); - }); - } + performersInDatabase.Should().BeEmpty(); + }); + } - [Fact] - public async Task Can_rollback_updated_resource_on_error() - { - // Arrange - Performer existingPerformer = _fakers.Performer.Generate(); + [Fact] + public async Task Can_rollback_updated_resource_on_error() + { + // Arrange + Performer existingPerformer = _fakers.Performer.Generate(); - string newArtistName = _fakers.Performer.Generate().ArtistName; + string newArtistName = _fakers.Performer.Generate().ArtistName; - await _testContext.RunOnDatabaseAsync(async db => - { - await db.GetCollection().InsertOneAsync(existingPerformer); - }); + await _testContext.RunOnDatabaseAsync(async db => + { + await db.GetCollection().InsertOneAsync(existingPerformer); + }); - var requestBody = new + var requestBody = new + { + atomic__operations = new object[] { - atomic__operations = new object[] + new { - new + op = "update", + data = new { - op = "update", - data = new + type = "performers", + id = existingPerformer.StringId, + attributes = new { - type = "performers", - id = existingPerformer.StringId, - attributes = new - { - artistName = newArtistName - } + artistName = newArtistName } - }, - new + } + }, + new + { + op = "remove", + @ref = new { - op = "remove", - @ref = new - { - type = "performers", - id = "ffffffffffffffffffffffff" - } + type = "performers", + id = "ffffffffffffffffffffffff" } } - }; + } + }; - const string route = "/operations"; + const string route = "/operations"; - // Act - (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + // Act + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.NotFound); + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.NotFound); - responseDocument.Errors.Should().HaveCount(1); + responseDocument.Errors.Should().HaveCount(1); - Error error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.NotFound); - error.Title.Should().Be("The requested resource does not exist."); - error.Detail.Should().Be("Resource of type 'performers' with ID 'ffffffffffffffffffffffff' does not exist."); - error.Source.Pointer.Should().Be("/atomic:operations[1]"); + Error error = responseDocument.Errors[0]; + error.StatusCode.Should().Be(HttpStatusCode.NotFound); + error.Title.Should().Be("The requested resource does not exist."); + error.Detail.Should().Be("Resource of type 'performers' with ID 'ffffffffffffffffffffffff' does not exist."); + error.Source.Pointer.Should().Be("/atomic:operations[1]"); - await _testContext.RunOnDatabaseAsync(async db => - { - Performer performerInDatabase = await db.GetCollection().AsQueryable().FirstWithIdAsync(existingPerformer.Id); + await _testContext.RunOnDatabaseAsync(async db => + { + Performer performerInDatabase = await db.GetCollection().AsQueryable().FirstWithIdAsync(existingPerformer.Id); - performerInDatabase.ArtistName.Should().Be(existingPerformer.ArtistName); - }); - } + performerInDatabase.ArtistName.Should().Be(existingPerformer.ArtistName); + }); + } - [Fact] - public async Task Can_rollback_deleted_resource_on_error() - { - // Arrange - Performer existingPerformer = _fakers.Performer.Generate(); + [Fact] + public async Task Can_rollback_deleted_resource_on_error() + { + // Arrange + Performer existingPerformer = _fakers.Performer.Generate(); - await _testContext.RunOnDatabaseAsync(async db => - { - await db.ClearCollectionAsync(); - await db.GetCollection().InsertOneAsync(existingPerformer); - }); + await _testContext.RunOnDatabaseAsync(async db => + { + await db.ClearCollectionAsync(); + await db.GetCollection().InsertOneAsync(existingPerformer); + }); - var requestBody = new + var requestBody = new + { + atomic__operations = new object[] { - atomic__operations = new object[] + new { - new + op = "remove", + @ref = new { - op = "remove", - @ref = new - { - type = "performers", - id = existingPerformer.StringId - } - }, - new + type = "performers", + id = existingPerformer.StringId + } + }, + new + { + op = "remove", + @ref = new { - op = "remove", - @ref = new - { - type = "performers", - id = "ffffffffffffffffffffffff" - } + type = "performers", + id = "ffffffffffffffffffffffff" } } - }; + } + }; - const string route = "/operations"; + const string route = "/operations"; - // Act - (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + // Act + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.NotFound); + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.NotFound); - responseDocument.Errors.Should().HaveCount(1); + responseDocument.Errors.Should().HaveCount(1); - Error error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.NotFound); - error.Title.Should().Be("The requested resource does not exist."); - error.Detail.Should().Be("Resource of type 'performers' with ID 'ffffffffffffffffffffffff' does not exist."); - error.Source.Pointer.Should().Be("/atomic:operations[1]"); + Error error = responseDocument.Errors[0]; + error.StatusCode.Should().Be(HttpStatusCode.NotFound); + error.Title.Should().Be("The requested resource does not exist."); + error.Detail.Should().Be("Resource of type 'performers' with ID 'ffffffffffffffffffffffff' does not exist."); + error.Source.Pointer.Should().Be("/atomic:operations[1]"); - await _testContext.RunOnDatabaseAsync(async db => - { - List performersInDatabase = await db.GetCollection().AsQueryable().ToListAsync(); + await _testContext.RunOnDatabaseAsync(async db => + { + List performersInDatabase = await db.GetCollection().AsQueryable().ToListAsync(); - performersInDatabase.Should().HaveCount(1); - }); - } + performersInDatabase.Should().HaveCount(1); + }); } } diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Transactions/AtomicTransactionConsistencyTests.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Transactions/AtomicTransactionConsistencyTests.cs index f0b2c8c..873ae13 100644 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Transactions/AtomicTransactionConsistencyTests.cs +++ b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Transactions/AtomicTransactionConsistencyTests.cs @@ -1,149 +1,146 @@ using System.Net; -using System.Net.Http; -using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Serialization.Objects; using JsonApiDotNetCoreMongoDbExampleTests.TestBuildingBlocks; using Xunit; -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Transactions +namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Transactions; + +public sealed class AtomicTransactionConsistencyTests : IClassFixture> { - public sealed class AtomicTransactionConsistencyTests : IClassFixture> - { - private readonly IntegrationTestContext _testContext; + private readonly IntegrationTestContext _testContext; - public AtomicTransactionConsistencyTests(IntegrationTestContext testContext) - { - _testContext = testContext; + public AtomicTransactionConsistencyTests(IntegrationTestContext testContext) + { + _testContext = testContext; - testContext.StartMongoDbInSingleNodeReplicaSetMode = true; + testContext.StartMongoDbInSingleNodeReplicaSetMode = true; - testContext.ConfigureServicesAfterStartup(services => - { - services.AddControllersFromExampleProject(); + testContext.ConfigureServicesAfterStartup(services => + { + services.AddControllersFromExampleProject(); - services.AddResourceRepository(); - services.AddResourceRepository(); - services.AddResourceRepository(); - }); - } + services.AddResourceRepository(); + services.AddResourceRepository(); + services.AddResourceRepository(); + }); + } - [Fact] - public async Task Cannot_use_non_transactional_repository() + [Fact] + public async Task Cannot_use_non_transactional_repository() + { + // Arrange + var requestBody = new { - // Arrange - var requestBody = new + atomic__operations = new object[] { - atomic__operations = new object[] + new { - new + op = "add", + data = new { - op = "add", - data = new + type = "performers", + attributes = new { - type = "performers", - attributes = new - { - } } } } - }; + } + }; - const string route = "/operations"; + const string route = "/operations"; - // Act - (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + // Act + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); - responseDocument.Errors.Should().HaveCount(1); + responseDocument.Errors.Should().HaveCount(1); - Error error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); - error.Title.Should().Be("Unsupported resource type in atomic:operations request."); - error.Detail.Should().Be("Operations on resources of type 'performers' cannot be used because transaction support is unavailable."); - error.Source.Pointer.Should().Be("/atomic:operations[0]"); - } + Error error = responseDocument.Errors[0]; + error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); + error.Title.Should().Be("Unsupported resource type in atomic:operations request."); + error.Detail.Should().Be("Operations on resources of type 'performers' cannot be used because transaction support is unavailable."); + error.Source.Pointer.Should().Be("/atomic:operations[0]"); + } - [Fact] - public async Task Cannot_use_transactional_repository_without_active_transaction() + [Fact] + public async Task Cannot_use_transactional_repository_without_active_transaction() + { + // Arrange + var requestBody = new { - // Arrange - var requestBody = new + atomic__operations = new object[] { - atomic__operations = new object[] + new { - new + op = "add", + data = new { - op = "add", - data = new + type = "musicTracks", + attributes = new { - type = "musicTracks", - attributes = new - { - } } } } - }; + } + }; - const string route = "/operations"; + const string route = "/operations"; - // Act - (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + // Act + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); - responseDocument.Errors.Should().HaveCount(1); + responseDocument.Errors.Should().HaveCount(1); - Error error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); - error.Title.Should().Be("Unsupported combination of resource types in atomic:operations request."); - error.Detail.Should().Be("All operations need to participate in a single shared transaction, which is not the case for this request."); - error.Source.Pointer.Should().Be("/atomic:operations[0]"); - } + Error error = responseDocument.Errors[0]; + error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); + error.Title.Should().Be("Unsupported combination of resource types in atomic:operations request."); + error.Detail.Should().Be("All operations need to participate in a single shared transaction, which is not the case for this request."); + error.Source.Pointer.Should().Be("/atomic:operations[0]"); + } - [Fact] - public async Task Cannot_use_distributed_transaction() + [Fact] + public async Task Cannot_use_distributed_transaction() + { + // Arrange + var requestBody = new { - // Arrange - var requestBody = new + atomic__operations = new object[] { - atomic__operations = new object[] + new { - new + op = "add", + data = new { - op = "add", - data = new + type = "lyrics", + attributes = new { - type = "lyrics", - attributes = new - { - } } } } - }; + } + }; - const string route = "/operations"; + const string route = "/operations"; - // Act - (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + // Act + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); - responseDocument.Errors.Should().HaveCount(1); + responseDocument.Errors.Should().HaveCount(1); - Error error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); - error.Title.Should().Be("Unsupported combination of resource types in atomic:operations request."); - error.Detail.Should().Be("All operations need to participate in a single shared transaction, which is not the case for this request."); - error.Source.Pointer.Should().Be("/atomic:operations[0]"); - } + Error error = responseDocument.Errors[0]; + error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); + error.Title.Should().Be("Unsupported combination of resource types in atomic:operations request."); + error.Detail.Should().Be("All operations need to participate in a single shared transaction, which is not the case for this request."); + error.Source.Pointer.Should().Be("/atomic:operations[0]"); } } diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Transactions/LyricRepository.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Transactions/LyricRepository.cs index 94c22e0..e31d6d3 100644 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Transactions/LyricRepository.cs +++ b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Transactions/LyricRepository.cs @@ -1,7 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; using JetBrains.Annotations; using JsonApiDotNetCore.AtomicOperations; using JsonApiDotNetCore.Configuration; @@ -12,31 +8,30 @@ #pragma warning disable AV1008 // Class should not be static -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Transactions +namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Transactions; + +internal static class ContainerTypeToHideLyricRepositoryFromAutoDiscovery { - internal static class ContainerTypeToHideLyricRepositoryFromAutoDiscovery + [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] + public sealed class LyricRepository : MongoRepository, IAsyncDisposable { - [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] - public sealed class LyricRepository : MongoRepository, IAsyncDisposable - { - private readonly IOperationsTransaction _transaction; + private readonly IOperationsTransaction _transaction; - public override string TransactionId => _transaction.TransactionId; + public override string TransactionId => _transaction.TransactionId; - public LyricRepository(IMongoDataAccess mongoDataAccess, ITargetedFields targetedFields, IResourceContextProvider resourceContextProvider, - IResourceFactory resourceFactory, IEnumerable constraintProviders) - : base(mongoDataAccess, targetedFields, resourceContextProvider, resourceFactory, constraintProviders) - { - IMongoDataAccess otherDataAccess = new MongoDataAccess(mongoDataAccess.MongoDatabase); + public LyricRepository(IMongoDataAccess mongoDataAccess, ITargetedFields targetedFields, IResourceContextProvider resourceContextProvider, + IResourceFactory resourceFactory, IEnumerable constraintProviders) + : base(mongoDataAccess, targetedFields, resourceContextProvider, resourceFactory, constraintProviders) + { + IMongoDataAccess otherDataAccess = new MongoDataAccess(mongoDataAccess.MongoDatabase); - var factory = new MongoTransactionFactory(otherDataAccess); - _transaction = factory.BeginTransactionAsync(CancellationToken.None).Result; - } + var factory = new MongoTransactionFactory(otherDataAccess); + _transaction = factory.BeginTransactionAsync(CancellationToken.None).Result; + } - public async ValueTask DisposeAsync() - { - await _transaction.DisposeAsync(); - } + public async ValueTask DisposeAsync() + { + await _transaction.DisposeAsync(); } } } diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Transactions/MusicTrackRepository.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Transactions/MusicTrackRepository.cs index 5d8a105..bde2b20 100644 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Transactions/MusicTrackRepository.cs +++ b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Transactions/MusicTrackRepository.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.MongoDb.Repositories; @@ -7,20 +6,19 @@ #pragma warning disable AV1008 // Class should not be static -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Transactions +namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Transactions; + +internal static class ContainerTypeToHideMusicTrackRepositoryFromAutoDiscovery { - internal static class ContainerTypeToHideMusicTrackRepositoryFromAutoDiscovery + [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] + public sealed class MusicTrackRepository : MongoRepository { - [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] - public sealed class MusicTrackRepository : MongoRepository - { - public override string TransactionId => null; + public override string TransactionId => null; - public MusicTrackRepository(IMongoDataAccess mongoDataAccess, ITargetedFields targetedFields, IResourceContextProvider resourceContextProvider, - IResourceFactory resourceFactory, IEnumerable constraintProviders) - : base(mongoDataAccess, targetedFields, resourceContextProvider, resourceFactory, constraintProviders) - { - } + public MusicTrackRepository(IMongoDataAccess mongoDataAccess, ITargetedFields targetedFields, IResourceContextProvider resourceContextProvider, + IResourceFactory resourceFactory, IEnumerable constraintProviders) + : base(mongoDataAccess, targetedFields, resourceContextProvider, resourceFactory, constraintProviders) + { } } } diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Transactions/PerformerRepository.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Transactions/PerformerRepository.cs index 1987b12..484ee0d 100644 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Transactions/PerformerRepository.cs +++ b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Transactions/PerformerRepository.cs @@ -1,7 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; using JetBrains.Annotations; using JsonApiDotNetCore.Queries; using JsonApiDotNetCore.Queries.Expressions; @@ -10,63 +6,61 @@ #pragma warning disable AV1008 // Class should not be static -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Transactions +namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Transactions; + +internal static class ContainerTypeToHidePerformerRepositoryFromAutoDiscovery { - internal static class ContainerTypeToHidePerformerRepositoryFromAutoDiscovery + [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] + public sealed class PerformerRepository : IResourceRepository { - [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] - public sealed class PerformerRepository : IResourceRepository + public Task> GetAsync(QueryLayer layer, CancellationToken cancellationToken) { - public Task> GetAsync(QueryLayer layer, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } + throw new NotImplementedException(); + } - public Task CountAsync(FilterExpression topFilter, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } + public Task CountAsync(FilterExpression topFilter, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } - public Task GetForCreateAsync(string id, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } + public Task GetForCreateAsync(string id, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } - public Task CreateAsync(Performer resourceFromRequest, Performer resourceForDatabase, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } + public Task CreateAsync(Performer resourceFromRequest, Performer resourceForDatabase, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } - public Task GetForUpdateAsync(QueryLayer queryLayer, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } + public Task GetForUpdateAsync(QueryLayer queryLayer, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } - public Task UpdateAsync(Performer resourceFromRequest, Performer resourceFromDatabase, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } + public Task UpdateAsync(Performer resourceFromRequest, Performer resourceFromDatabase, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } - public Task DeleteAsync(string id, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } + public Task DeleteAsync(string id, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } - public Task SetRelationshipAsync(Performer primaryResource, object secondaryResourceIds, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } + public Task SetRelationshipAsync(Performer primaryResource, object secondaryResourceIds, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } - public Task AddToToManyRelationshipAsync(string primaryId, ISet secondaryResourceIds, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } + public Task AddToToManyRelationshipAsync(string primaryId, ISet secondaryResourceIds, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } - public Task RemoveFromToManyRelationshipAsync(Performer primaryResource, ISet secondaryResourceIds, - CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } + public Task RemoveFromToManyRelationshipAsync(Performer primaryResource, ISet secondaryResourceIds, CancellationToken cancellationToken) + { + throw new NotImplementedException(); } } } diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicAddToToManyRelationshipTests.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicAddToToManyRelationshipTests.cs index aa52ed3..45cfc84 100644 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicAddToToManyRelationshipTests.cs +++ b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicAddToToManyRelationshipTests.cs @@ -1,136 +1,133 @@ using System.Net; -using System.Net.Http; -using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Serialization.Objects; using JsonApiDotNetCoreMongoDbExampleTests.TestBuildingBlocks; using Xunit; -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Updating.Relationships +namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Updating.Relationships; + +[Collection("AtomicOperationsFixture")] +public sealed class AtomicAddToToManyRelationshipTests { - [Collection("AtomicOperationsFixture")] - public sealed class AtomicAddToToManyRelationshipTests - { - private readonly IntegrationTestContext _testContext; - private readonly OperationsFakers _fakers = new OperationsFakers(); + private readonly IntegrationTestContext _testContext; + private readonly OperationsFakers _fakers = new(); - public AtomicAddToToManyRelationshipTests(AtomicOperationsFixture fixture) - { - _testContext = fixture.TestContext; + public AtomicAddToToManyRelationshipTests(AtomicOperationsFixture fixture) + { + _testContext = fixture.TestContext; - fixture.TestContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); - } + fixture.TestContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); + } - [Fact] - public async Task Cannot_add_to_HasMany_relationship() - { - // Arrange - MusicTrack existingTrack = _fakers.MusicTrack.Generate(); + [Fact] + public async Task Cannot_add_to_HasMany_relationship() + { + // Arrange + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); - Performer existingPerformer = _fakers.Performer.Generate(); + Performer existingPerformer = _fakers.Performer.Generate(); - await _testContext.RunOnDatabaseAsync(async db => - { - await db.GetCollection().InsertOneAsync(existingTrack); - await db.GetCollection().InsertOneAsync(existingPerformer); - }); + await _testContext.RunOnDatabaseAsync(async db => + { + await db.GetCollection().InsertOneAsync(existingTrack); + await db.GetCollection().InsertOneAsync(existingPerformer); + }); - var requestBody = new + var requestBody = new + { + atomic__operations = new[] { - atomic__operations = new[] + new { - new + op = "add", + @ref = new { - op = "add", - @ref = new - { - type = "musicTracks", - id = existingTrack.StringId, - relationship = "performers" - }, - data = new[] + type = "musicTracks", + id = existingTrack.StringId, + relationship = "performers" + }, + data = new[] + { + new { - new - { - type = "performers", - id = existingPerformer.StringId - } + type = "performers", + id = existingPerformer.StringId } } } - }; + } + }; - const string route = "/operations"; + const string route = "/operations"; - // Act - (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + // Act + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.Should().HaveCount(1); + responseDocument.Errors.Should().HaveCount(1); - Error error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.BadRequest); - error.Title.Should().Be("Relationships are not supported when using MongoDB."); - error.Detail.Should().BeNull(); - error.Source.Pointer.Should().Be("/atomic:operations[0]"); - } + Error error = responseDocument.Errors[0]; + error.StatusCode.Should().Be(HttpStatusCode.BadRequest); + error.Title.Should().Be("Relationships are not supported when using MongoDB."); + error.Detail.Should().BeNull(); + error.Source.Pointer.Should().Be("/atomic:operations[0]"); + } - [Fact] - public async Task Cannot_add_to_HasManyThrough_relationship() - { - // Arrange - Playlist existingPlaylist = _fakers.Playlist.Generate(); + [Fact] + public async Task Cannot_add_to_HasManyThrough_relationship() + { + // Arrange + Playlist existingPlaylist = _fakers.Playlist.Generate(); - MusicTrack existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); - await _testContext.RunOnDatabaseAsync(async db => - { - await db.GetCollection().InsertOneAsync(existingPlaylist); - await db.GetCollection().InsertOneAsync(existingTrack); - }); + await _testContext.RunOnDatabaseAsync(async db => + { + await db.GetCollection().InsertOneAsync(existingPlaylist); + await db.GetCollection().InsertOneAsync(existingTrack); + }); - var requestBody = new + var requestBody = new + { + atomic__operations = new[] { - atomic__operations = new[] + new { - new + op = "add", + @ref = new { - op = "add", - @ref = new - { - type = "playlists", - id = existingPlaylist.StringId, - relationship = "tracks" - }, - data = new[] + type = "playlists", + id = existingPlaylist.StringId, + relationship = "tracks" + }, + data = new[] + { + new { - new - { - type = "musicTracks", - id = existingTrack.StringId - } + type = "musicTracks", + id = existingTrack.StringId } } } - }; + } + }; - const string route = "/operations"; + const string route = "/operations"; - // Act - (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + // Act + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.Should().HaveCount(1); + responseDocument.Errors.Should().HaveCount(1); - Error error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.BadRequest); - error.Title.Should().Be("Relationships are not supported when using MongoDB."); - error.Detail.Should().BeNull(); - error.Source.Pointer.Should().Be("/atomic:operations[0]"); - } + Error error = responseDocument.Errors[0]; + error.StatusCode.Should().Be(HttpStatusCode.BadRequest); + error.Title.Should().Be("Relationships are not supported when using MongoDB."); + error.Detail.Should().BeNull(); + error.Source.Pointer.Should().Be("/atomic:operations[0]"); } } diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicRemoveFromToManyRelationshipTests.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicRemoveFromToManyRelationshipTests.cs index 6bf79ad..3236e19 100644 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicRemoveFromToManyRelationshipTests.cs +++ b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicRemoveFromToManyRelationshipTests.cs @@ -1,138 +1,135 @@ using System.Net; -using System.Net.Http; -using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Serialization.Objects; using JsonApiDotNetCoreMongoDbExampleTests.TestBuildingBlocks; using Xunit; -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Updating.Relationships +namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Updating.Relationships; + +[Collection("AtomicOperationsFixture")] +public sealed class AtomicRemoveFromToManyRelationshipTests { - [Collection("AtomicOperationsFixture")] - public sealed class AtomicRemoveFromToManyRelationshipTests + private readonly IntegrationTestContext _testContext; + private readonly OperationsFakers _fakers = new(); + + public AtomicRemoveFromToManyRelationshipTests(AtomicOperationsFixture fixture) { - private readonly IntegrationTestContext _testContext; - private readonly OperationsFakers _fakers = new OperationsFakers(); + _testContext = fixture.TestContext; - public AtomicRemoveFromToManyRelationshipTests(AtomicOperationsFixture fixture) - { - _testContext = fixture.TestContext; + fixture.TestContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); + } - fixture.TestContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); - } + [Fact] + public async Task Cannot_remove_from_HasMany_relationship() + { + // Arrange + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); + existingTrack.Performers = _fakers.Performer.Generate(1); - [Fact] - public async Task Cannot_remove_from_HasMany_relationship() + await _testContext.RunOnDatabaseAsync(async db => { - // Arrange - MusicTrack existingTrack = _fakers.MusicTrack.Generate(); - existingTrack.Performers = _fakers.Performer.Generate(1); + await db.GetCollection().InsertOneAsync(existingTrack.Performers[0]); + await db.GetCollection().InsertOneAsync(existingTrack); + }); - await _testContext.RunOnDatabaseAsync(async db => - { - await db.GetCollection().InsertOneAsync(existingTrack.Performers[0]); - await db.GetCollection().InsertOneAsync(existingTrack); - }); - - var requestBody = new + var requestBody = new + { + atomic__operations = new[] { - atomic__operations = new[] + new { - new + op = "remove", + @ref = new { - op = "remove", - @ref = new - { - type = "musicTracks", - id = existingTrack.StringId, - relationship = "performers" - }, - data = new[] + type = "musicTracks", + id = existingTrack.StringId, + relationship = "performers" + }, + data = new[] + { + new { - new - { - type = "performers", - id = existingTrack.Performers[0].StringId - } + type = "performers", + id = existingTrack.Performers[0].StringId } } } - }; + } + }; - const string route = "/operations"; + const string route = "/operations"; - // Act - (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + // Act + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.Should().HaveCount(1); + responseDocument.Errors.Should().HaveCount(1); - Error error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.BadRequest); - error.Title.Should().Be("Relationships are not supported when using MongoDB."); - error.Detail.Should().BeNull(); - error.Source.Pointer.Should().Be("/atomic:operations[0]"); - } + Error error = responseDocument.Errors[0]; + error.StatusCode.Should().Be(HttpStatusCode.BadRequest); + error.Title.Should().Be("Relationships are not supported when using MongoDB."); + error.Detail.Should().BeNull(); + error.Source.Pointer.Should().Be("/atomic:operations[0]"); + } - [Fact] - public async Task Cannot_remove_from_HasManyThrough_relationship() + [Fact] + public async Task Cannot_remove_from_HasManyThrough_relationship() + { + // Arrange + var existingPlaylistMusicTrack = new PlaylistMusicTrack { - // Arrange - var existingPlaylistMusicTrack = new PlaylistMusicTrack - { - Playlist = _fakers.Playlist.Generate(), - MusicTrack = _fakers.MusicTrack.Generate() - }; + Playlist = _fakers.Playlist.Generate(), + MusicTrack = _fakers.MusicTrack.Generate() + }; - await _testContext.RunOnDatabaseAsync(async db => - { - await db.GetCollection().InsertOneAsync(existingPlaylistMusicTrack.Playlist); - await db.GetCollection().InsertOneAsync(existingPlaylistMusicTrack.MusicTrack); - await db.GetCollection().InsertOneAsync(existingPlaylistMusicTrack); - }); + await _testContext.RunOnDatabaseAsync(async db => + { + await db.GetCollection().InsertOneAsync(existingPlaylistMusicTrack.Playlist); + await db.GetCollection().InsertOneAsync(existingPlaylistMusicTrack.MusicTrack); + await db.GetCollection().InsertOneAsync(existingPlaylistMusicTrack); + }); - var requestBody = new + var requestBody = new + { + atomic__operations = new[] { - atomic__operations = new[] + new { - new + op = "remove", + @ref = new { - op = "remove", - @ref = new - { - type = "playlists", - id = existingPlaylistMusicTrack.Playlist.StringId, - relationship = "tracks" - }, - data = new[] + type = "playlists", + id = existingPlaylistMusicTrack.Playlist.StringId, + relationship = "tracks" + }, + data = new[] + { + new { - new - { - type = "musicTracks", - id = existingPlaylistMusicTrack.MusicTrack.StringId - } + type = "musicTracks", + id = existingPlaylistMusicTrack.MusicTrack.StringId } } } - }; + } + }; - const string route = "/operations"; + const string route = "/operations"; - // Act - (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + // Act + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.Should().HaveCount(1); + responseDocument.Errors.Should().HaveCount(1); - Error error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.BadRequest); - error.Title.Should().Be("Relationships are not supported when using MongoDB."); - error.Detail.Should().BeNull(); - error.Source.Pointer.Should().Be("/atomic:operations[0]"); - } + Error error = responseDocument.Errors[0]; + error.StatusCode.Should().Be(HttpStatusCode.BadRequest); + error.Title.Should().Be("Relationships are not supported when using MongoDB."); + error.Detail.Should().BeNull(); + error.Source.Pointer.Should().Be("/atomic:operations[0]"); } } diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicReplaceToManyRelationshipTests.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicReplaceToManyRelationshipTests.cs index 69874bc..54126d8 100644 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicReplaceToManyRelationshipTests.cs +++ b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicReplaceToManyRelationshipTests.cs @@ -1,136 +1,133 @@ using System.Net; -using System.Net.Http; -using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Serialization.Objects; using JsonApiDotNetCoreMongoDbExampleTests.TestBuildingBlocks; using Xunit; -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Updating.Relationships +namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Updating.Relationships; + +[Collection("AtomicOperationsFixture")] +public sealed class AtomicReplaceToManyRelationshipTests { - [Collection("AtomicOperationsFixture")] - public sealed class AtomicReplaceToManyRelationshipTests - { - private readonly IntegrationTestContext _testContext; - private readonly OperationsFakers _fakers = new OperationsFakers(); + private readonly IntegrationTestContext _testContext; + private readonly OperationsFakers _fakers = new(); - public AtomicReplaceToManyRelationshipTests(AtomicOperationsFixture fixture) - { - _testContext = fixture.TestContext; + public AtomicReplaceToManyRelationshipTests(AtomicOperationsFixture fixture) + { + _testContext = fixture.TestContext; - fixture.TestContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); - } + fixture.TestContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); + } - [Fact] - public async Task Cannot_replace_HasMany_relationship() - { - // Arrange - MusicTrack existingTrack = _fakers.MusicTrack.Generate(); + [Fact] + public async Task Cannot_replace_HasMany_relationship() + { + // Arrange + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); - Performer existingPerformer = _fakers.Performer.Generate(); + Performer existingPerformer = _fakers.Performer.Generate(); - await _testContext.RunOnDatabaseAsync(async db => - { - await db.GetCollection().InsertOneAsync(existingTrack); - await db.GetCollection().InsertOneAsync(existingPerformer); - }); + await _testContext.RunOnDatabaseAsync(async db => + { + await db.GetCollection().InsertOneAsync(existingTrack); + await db.GetCollection().InsertOneAsync(existingPerformer); + }); - var requestBody = new + var requestBody = new + { + atomic__operations = new[] { - atomic__operations = new[] + new { - new + op = "update", + @ref = new { - op = "update", - @ref = new - { - type = "musicTracks", - id = existingTrack.StringId, - relationship = "performers" - }, - data = new[] + type = "musicTracks", + id = existingTrack.StringId, + relationship = "performers" + }, + data = new[] + { + new { - new - { - type = "performers", - id = existingPerformer.StringId - } + type = "performers", + id = existingPerformer.StringId } } } - }; + } + }; - const string route = "/operations"; + const string route = "/operations"; - // Act - (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + // Act + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.Should().HaveCount(1); + responseDocument.Errors.Should().HaveCount(1); - Error error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.BadRequest); - error.Title.Should().Be("Relationships are not supported when using MongoDB."); - error.Detail.Should().BeNull(); - error.Source.Pointer.Should().Be("/atomic:operations[0]"); - } + Error error = responseDocument.Errors[0]; + error.StatusCode.Should().Be(HttpStatusCode.BadRequest); + error.Title.Should().Be("Relationships are not supported when using MongoDB."); + error.Detail.Should().BeNull(); + error.Source.Pointer.Should().Be("/atomic:operations[0]"); + } - [Fact] - public async Task Cannot_replace_HasManyThrough_relationship() - { - // Arrange - Playlist existingPlaylist = _fakers.Playlist.Generate(); + [Fact] + public async Task Cannot_replace_HasManyThrough_relationship() + { + // Arrange + Playlist existingPlaylist = _fakers.Playlist.Generate(); - MusicTrack existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); - await _testContext.RunOnDatabaseAsync(async db => - { - await db.GetCollection().InsertOneAsync(existingPlaylist); - await db.GetCollection().InsertOneAsync(existingTrack); - }); + await _testContext.RunOnDatabaseAsync(async db => + { + await db.GetCollection().InsertOneAsync(existingPlaylist); + await db.GetCollection().InsertOneAsync(existingTrack); + }); - var requestBody = new + var requestBody = new + { + atomic__operations = new[] { - atomic__operations = new[] + new { - new + op = "update", + @ref = new { - op = "update", - @ref = new - { - type = "playlists", - id = existingPlaylist.StringId, - relationship = "tracks" - }, - data = new[] + type = "playlists", + id = existingPlaylist.StringId, + relationship = "tracks" + }, + data = new[] + { + new { - new - { - type = "musicTracks", - id = existingTrack.StringId - } + type = "musicTracks", + id = existingTrack.StringId } } } - }; + } + }; - const string route = "/operations"; + const string route = "/operations"; - // Act - (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + // Act + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.Should().HaveCount(1); + responseDocument.Errors.Should().HaveCount(1); - Error error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.BadRequest); - error.Title.Should().Be("Relationships are not supported when using MongoDB."); - error.Detail.Should().BeNull(); - error.Source.Pointer.Should().Be("/atomic:operations[0]"); - } + Error error = responseDocument.Errors[0]; + error.StatusCode.Should().Be(HttpStatusCode.BadRequest); + error.Title.Should().Be("Relationships are not supported when using MongoDB."); + error.Detail.Should().BeNull(); + error.Source.Pointer.Should().Be("/atomic:operations[0]"); } } diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicUpdateToOneRelationshipTests.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicUpdateToOneRelationshipTests.cs index 3175e02..670e07f 100644 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicUpdateToOneRelationshipTests.cs +++ b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicUpdateToOneRelationshipTests.cs @@ -1,76 +1,73 @@ using System.Net; -using System.Net.Http; -using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Serialization.Objects; using JsonApiDotNetCoreMongoDbExampleTests.TestBuildingBlocks; using Xunit; -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Updating.Relationships +namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Updating.Relationships; + +[Collection("AtomicOperationsFixture")] +public sealed class AtomicUpdateToOneRelationshipTests { - [Collection("AtomicOperationsFixture")] - public sealed class AtomicUpdateToOneRelationshipTests + private readonly IntegrationTestContext _testContext; + private readonly OperationsFakers _fakers = new(); + + public AtomicUpdateToOneRelationshipTests(AtomicOperationsFixture fixture) { - private readonly IntegrationTestContext _testContext; - private readonly OperationsFakers _fakers = new OperationsFakers(); + _testContext = fixture.TestContext; - public AtomicUpdateToOneRelationshipTests(AtomicOperationsFixture fixture) - { - _testContext = fixture.TestContext; + fixture.TestContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); + } - fixture.TestContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); - } + [Fact] + public async Task Cannot_create_relationship() + { + // Arrange + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); + RecordCompany existingCompany = _fakers.RecordCompany.Generate(); - [Fact] - public async Task Cannot_create_relationship() + await _testContext.RunOnDatabaseAsync(async db => { - // Arrange - MusicTrack existingTrack = _fakers.MusicTrack.Generate(); - RecordCompany existingCompany = _fakers.RecordCompany.Generate(); + await db.GetCollection().InsertOneAsync(existingTrack); + await db.GetCollection().InsertOneAsync(existingCompany); + }); - await _testContext.RunOnDatabaseAsync(async db => - { - await db.GetCollection().InsertOneAsync(existingTrack); - await db.GetCollection().InsertOneAsync(existingCompany); - }); - - var requestBody = new + var requestBody = new + { + atomic__operations = new[] { - atomic__operations = new[] + new { - new + op = "update", + @ref = new + { + type = "musicTracks", + id = existingTrack.StringId, + relationship = "ownedBy" + }, + data = new { - op = "update", - @ref = new - { - type = "musicTracks", - id = existingTrack.StringId, - relationship = "ownedBy" - }, - data = new - { - type = "recordCompanies", - id = existingCompany.StringId - } + type = "recordCompanies", + id = existingCompany.StringId } } - }; + } + }; - const string route = "/operations"; + const string route = "/operations"; - // Act - (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + // Act + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.Should().HaveCount(1); + responseDocument.Errors.Should().HaveCount(1); - Error error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.BadRequest); - error.Title.Should().Be("Relationships are not supported when using MongoDB."); - error.Detail.Should().BeNull(); - error.Source.Pointer.Should().Be("/atomic:operations[0]"); - } + Error error = responseDocument.Errors[0]; + error.StatusCode.Should().Be(HttpStatusCode.BadRequest); + error.Title.Should().Be("Relationships are not supported when using MongoDB."); + error.Detail.Should().BeNull(); + error.Source.Pointer.Should().Be("/atomic:operations[0]"); } } diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicReplaceToManyRelationshipTests.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicReplaceToManyRelationshipTests.cs index 2142aa0..bdd77b1 100644 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicReplaceToManyRelationshipTests.cs +++ b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicReplaceToManyRelationshipTests.cs @@ -1,86 +1,83 @@ using System.Net; -using System.Net.Http; -using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Serialization.Objects; using JsonApiDotNetCoreMongoDbExampleTests.TestBuildingBlocks; using Xunit; -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Updating.Resources +namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Updating.Resources; + +[Collection("AtomicOperationsFixture")] +public sealed class AtomicReplaceToManyRelationshipTests { - [Collection("AtomicOperationsFixture")] - public sealed class AtomicReplaceToManyRelationshipTests - { - private readonly IntegrationTestContext _testContext; - private readonly OperationsFakers _fakers = new OperationsFakers(); + private readonly IntegrationTestContext _testContext; + private readonly OperationsFakers _fakers = new(); - public AtomicReplaceToManyRelationshipTests(AtomicOperationsFixture fixture) - { - _testContext = fixture.TestContext; + public AtomicReplaceToManyRelationshipTests(AtomicOperationsFixture fixture) + { + _testContext = fixture.TestContext; - fixture.TestContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); - } + fixture.TestContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); + } - [Fact] - public async Task Cannot_replace_HasMany_relationship() - { - // Arrange - MusicTrack existingTrack = _fakers.MusicTrack.Generate(); - existingTrack.Performers = _fakers.Performer.Generate(1); + [Fact] + public async Task Cannot_replace_HasMany_relationship() + { + // Arrange + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); + existingTrack.Performers = _fakers.Performer.Generate(1); - Performer existingPerformer = _fakers.Performer.Generate(); + Performer existingPerformer = _fakers.Performer.Generate(); - await _testContext.RunOnDatabaseAsync(async db => - { - await db.GetCollection().InsertManyAsync(existingTrack.Performers[0], existingPerformer); - await db.GetCollection().InsertOneAsync(existingTrack); - }); + await _testContext.RunOnDatabaseAsync(async db => + { + await db.GetCollection().InsertManyAsync(existingTrack.Performers[0], existingPerformer); + await db.GetCollection().InsertOneAsync(existingTrack); + }); - var requestBody = new + var requestBody = new + { + atomic__operations = new[] { - atomic__operations = new[] + new { - new + op = "update", + data = new { - op = "update", - data = new + type = "musicTracks", + id = existingTrack.StringId, + relationships = new { - type = "musicTracks", - id = existingTrack.StringId, - relationships = new + performers = new { - performers = new + data = new[] { - data = new[] + new { - new - { - type = "performers", - id = existingPerformer.StringId - } + type = "performers", + id = existingPerformer.StringId } } } } } } - }; + } + }; - const string route = "/operations"; + const string route = "/operations"; - // Act - (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + // Act + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.Should().HaveCount(1); + responseDocument.Errors.Should().HaveCount(1); - Error error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.BadRequest); - error.Title.Should().Be("Relationships are not supported when using MongoDB."); - error.Detail.Should().BeNull(); - error.Source.Pointer.Should().Be("/atomic:operations[0]"); - } + Error error = responseDocument.Errors[0]; + error.StatusCode.Should().Be(HttpStatusCode.BadRequest); + error.Title.Should().Be("Relationships are not supported when using MongoDB."); + error.Detail.Should().BeNull(); + error.Source.Pointer.Should().Be("/atomic:operations[0]"); } } diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateResourceTests.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateResourceTests.cs index 5c1be0a..4afa0dd 100644 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateResourceTests.cs +++ b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateResourceTests.cs @@ -1,359 +1,353 @@ -using System; -using System.Collections.Generic; -using System.Linq; using System.Net; -using System.Net.Http; -using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Serialization.Objects; using JsonApiDotNetCoreMongoDbExampleTests.TestBuildingBlocks; using MongoDB.Driver; using Xunit; -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Updating.Resources +namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Updating.Resources; + +[Collection("AtomicOperationsFixture")] +public sealed class AtomicUpdateResourceTests { - [Collection("AtomicOperationsFixture")] - public sealed class AtomicUpdateResourceTests - { - private readonly IntegrationTestContext _testContext; - private readonly OperationsFakers _fakers = new OperationsFakers(); + private readonly IntegrationTestContext _testContext; + private readonly OperationsFakers _fakers = new(); - public AtomicUpdateResourceTests(AtomicOperationsFixture fixture) - { - _testContext = fixture.TestContext; + public AtomicUpdateResourceTests(AtomicOperationsFixture fixture) + { + _testContext = fixture.TestContext; - fixture.TestContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); - } + fixture.TestContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); + } - [Fact] - public async Task Can_update_resources() - { - // Arrange - const int elementCount = 5; + [Fact] + public async Task Can_update_resources() + { + // Arrange + const int elementCount = 5; - List existingTracks = _fakers.MusicTrack.Generate(elementCount); - string[] newTrackTitles = _fakers.MusicTrack.Generate(elementCount).Select(musicTrack => musicTrack.Title).ToArray(); + List existingTracks = _fakers.MusicTrack.Generate(elementCount); + string[] newTrackTitles = _fakers.MusicTrack.Generate(elementCount).Select(musicTrack => musicTrack.Title).ToArray(); - await _testContext.RunOnDatabaseAsync(async db => - { - await db.ClearCollectionAsync(); - await db.GetCollection().InsertManyAsync(existingTracks); - }); + await _testContext.RunOnDatabaseAsync(async db => + { + await db.ClearCollectionAsync(); + await db.GetCollection().InsertManyAsync(existingTracks); + }); - var operationElements = new List(elementCount); + var operationElements = new List(elementCount); - for (int index = 0; index < elementCount; index++) + for (int index = 0; index < elementCount; index++) + { + operationElements.Add(new { - operationElements.Add(new + op = "update", + data = new { - op = "update", - data = new + type = "musicTracks", + id = existingTracks[index].StringId, + attributes = new { - type = "musicTracks", - id = existingTracks[index].StringId, - attributes = new - { - title = newTrackTitles[index] - } + title = newTrackTitles[index] } - }); - } + } + }); + } - var requestBody = new - { - atomic__operations = operationElements - }; + var requestBody = new + { + atomic__operations = operationElements + }; - const string route = "/operations"; + const string route = "/operations"; - // Act - (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + // Act + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); - responseDocument.Should().BeEmpty(); + responseDocument.Should().BeEmpty(); - await _testContext.RunOnDatabaseAsync(async db => - { - List tracksInDatabase = await db.GetCollection().AsQueryable().ToListAsync(); + await _testContext.RunOnDatabaseAsync(async db => + { + List tracksInDatabase = await db.GetCollection().AsQueryable().ToListAsync(); - tracksInDatabase.Should().HaveCount(elementCount); + tracksInDatabase.Should().HaveCount(elementCount); - for (int index = 0; index < elementCount; index++) - { - MusicTrack trackInDatabase = tracksInDatabase.Single(musicTrack => musicTrack.Id == existingTracks[index].Id); + for (int index = 0; index < elementCount; index++) + { + MusicTrack trackInDatabase = tracksInDatabase.Single(musicTrack => musicTrack.Id == existingTracks[index].Id); - trackInDatabase.Title.Should().Be(newTrackTitles[index]); - trackInDatabase.Genre.Should().Be(existingTracks[index].Genre); - } - }); - } + trackInDatabase.Title.Should().Be(newTrackTitles[index]); + trackInDatabase.Genre.Should().Be(existingTracks[index].Genre); + } + }); + } - [Fact] - public async Task Can_update_resource_without_attributes_or_relationships() - { - // Arrange - MusicTrack existingTrack = _fakers.MusicTrack.Generate(); + [Fact] + public async Task Can_update_resource_without_attributes_or_relationships() + { + // Arrange + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); - await _testContext.RunOnDatabaseAsync(async db => - { - await db.GetCollection().InsertOneAsync(existingTrack); - }); + await _testContext.RunOnDatabaseAsync(async db => + { + await db.GetCollection().InsertOneAsync(existingTrack); + }); - var requestBody = new + var requestBody = new + { + atomic__operations = new[] { - atomic__operations = new[] + new { - new + op = "update", + data = new { - op = "update", - data = new + type = "musicTracks", + id = existingTrack.StringId, + attributes = new + { + }, + relationships = new { - type = "musicTracks", - id = existingTrack.StringId, - attributes = new - { - }, - relationships = new - { - } } } } - }; + } + }; - const string route = "/operations"; + const string route = "/operations"; - // Act - (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + // Act + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); - responseDocument.Should().BeEmpty(); + responseDocument.Should().BeEmpty(); - await _testContext.RunOnDatabaseAsync(async db => - { - MusicTrack trackInDatabase = await db.GetCollection().AsQueryable().FirstWithIdAsync(existingTrack.Id); + await _testContext.RunOnDatabaseAsync(async db => + { + MusicTrack trackInDatabase = await db.GetCollection().AsQueryable().FirstWithIdAsync(existingTrack.Id); - trackInDatabase.Title.Should().Be(existingTrack.Title); - trackInDatabase.Genre.Should().Be(existingTrack.Genre); - }); - } + trackInDatabase.Title.Should().Be(existingTrack.Title); + trackInDatabase.Genre.Should().Be(existingTrack.Genre); + }); + } - [Fact] - public async Task Can_partially_update_resource_without_side_effects() - { - // Arrange - MusicTrack existingTrack = _fakers.MusicTrack.Generate(); - existingTrack.OwnedBy = _fakers.RecordCompany.Generate(); + [Fact] + public async Task Can_partially_update_resource_without_side_effects() + { + // Arrange + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); + existingTrack.OwnedBy = _fakers.RecordCompany.Generate(); - string newGenre = _fakers.MusicTrack.Generate().Genre; + string newGenre = _fakers.MusicTrack.Generate().Genre; - await _testContext.RunOnDatabaseAsync(async db => - { - await db.GetCollection().InsertOneAsync(existingTrack); - }); + await _testContext.RunOnDatabaseAsync(async db => + { + await db.GetCollection().InsertOneAsync(existingTrack); + }); - var requestBody = new + var requestBody = new + { + atomic__operations = new[] { - atomic__operations = new[] + new { - new + op = "update", + data = new { - op = "update", - data = new + type = "musicTracks", + id = existingTrack.StringId, + attributes = new { - type = "musicTracks", - id = existingTrack.StringId, - attributes = new - { - genre = newGenre - } + genre = newGenre } } } - }; + } + }; - const string route = "/operations"; + const string route = "/operations"; - // Act - (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + // Act + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); - responseDocument.Should().BeEmpty(); + responseDocument.Should().BeEmpty(); - await _testContext.RunOnDatabaseAsync(async db => - { - MusicTrack trackInDatabase = await db.GetCollection().AsQueryable().FirstWithIdAsync(existingTrack.Id); + await _testContext.RunOnDatabaseAsync(async db => + { + MusicTrack trackInDatabase = await db.GetCollection().AsQueryable().FirstWithIdAsync(existingTrack.Id); - trackInDatabase.Title.Should().Be(existingTrack.Title); - trackInDatabase.LengthInSeconds.Should().BeApproximately(existingTrack.LengthInSeconds); - trackInDatabase.Genre.Should().Be(newGenre); - trackInDatabase.ReleasedAt.Should().BeCloseTo(existingTrack.ReleasedAt, TimeSpan.FromMilliseconds(20)); - }); - } + trackInDatabase.Title.Should().Be(existingTrack.Title); + trackInDatabase.LengthInSeconds.Should().BeApproximately(existingTrack.LengthInSeconds); + trackInDatabase.Genre.Should().Be(newGenre); + trackInDatabase.ReleasedAt.Should().BeCloseTo(existingTrack.ReleasedAt, TimeSpan.FromMilliseconds(20)); + }); + } - [Fact] - public async Task Can_completely_update_resource_without_side_effects() - { - // Arrange - MusicTrack existingTrack = _fakers.MusicTrack.Generate(); + [Fact] + public async Task Can_completely_update_resource_without_side_effects() + { + // Arrange + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); - string newTitle = _fakers.MusicTrack.Generate().Title; - decimal? newLengthInSeconds = _fakers.MusicTrack.Generate().LengthInSeconds; - string newGenre = _fakers.MusicTrack.Generate().Genre; - DateTimeOffset newReleasedAt = _fakers.MusicTrack.Generate().ReleasedAt; + string newTitle = _fakers.MusicTrack.Generate().Title; + decimal? newLengthInSeconds = _fakers.MusicTrack.Generate().LengthInSeconds; + string newGenre = _fakers.MusicTrack.Generate().Genre; + DateTimeOffset newReleasedAt = _fakers.MusicTrack.Generate().ReleasedAt; - await _testContext.RunOnDatabaseAsync(async db => - { - await db.GetCollection().InsertOneAsync(existingTrack); - }); + await _testContext.RunOnDatabaseAsync(async db => + { + await db.GetCollection().InsertOneAsync(existingTrack); + }); - var requestBody = new + var requestBody = new + { + atomic__operations = new[] { - atomic__operations = new[] + new { - new + op = "update", + data = new { - op = "update", - data = new + type = "musicTracks", + id = existingTrack.StringId, + attributes = new { - type = "musicTracks", - id = existingTrack.StringId, - attributes = new - { - title = newTitle, - lengthInSeconds = newLengthInSeconds, - genre = newGenre, - releasedAt = newReleasedAt - } + title = newTitle, + lengthInSeconds = newLengthInSeconds, + genre = newGenre, + releasedAt = newReleasedAt } } } - }; + } + }; - const string route = "/operations"; + const string route = "/operations"; - // Act - (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + // Act + (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); - responseDocument.Should().BeEmpty(); + responseDocument.Should().BeEmpty(); - await _testContext.RunOnDatabaseAsync(async db => - { - MusicTrack trackInDatabase = await db.GetCollection().AsQueryable().FirstWithIdAsync(existingTrack.Id); + await _testContext.RunOnDatabaseAsync(async db => + { + MusicTrack trackInDatabase = await db.GetCollection().AsQueryable().FirstWithIdAsync(existingTrack.Id); - trackInDatabase.Title.Should().Be(newTitle); - trackInDatabase.LengthInSeconds.Should().BeApproximately(newLengthInSeconds); - trackInDatabase.Genre.Should().Be(newGenre); - trackInDatabase.ReleasedAt.Should().BeCloseTo(newReleasedAt, TimeSpan.FromMilliseconds(20)); - }); - } + trackInDatabase.Title.Should().Be(newTitle); + trackInDatabase.LengthInSeconds.Should().BeApproximately(newLengthInSeconds); + trackInDatabase.Genre.Should().Be(newGenre); + trackInDatabase.ReleasedAt.Should().BeCloseTo(newReleasedAt, TimeSpan.FromMilliseconds(20)); + }); + } - [Fact] - public async Task Can_update_resource_with_side_effects() - { - // Arrange - TextLanguage existingLanguage = _fakers.TextLanguage.Generate(); - string newIsoCode = _fakers.TextLanguage.Generate().IsoCode; + [Fact] + public async Task Can_update_resource_with_side_effects() + { + // Arrange + TextLanguage existingLanguage = _fakers.TextLanguage.Generate(); + string newIsoCode = _fakers.TextLanguage.Generate().IsoCode; - await _testContext.RunOnDatabaseAsync(async db => - { - await db.GetCollection().InsertOneAsync(existingLanguage); - }); + await _testContext.RunOnDatabaseAsync(async db => + { + await db.GetCollection().InsertOneAsync(existingLanguage); + }); - var requestBody = new + var requestBody = new + { + atomic__operations = new[] { - atomic__operations = new[] + new { - new + op = "update", + data = new { - op = "update", - data = new + type = "textLanguages", + id = existingLanguage.StringId, + attributes = new { - type = "textLanguages", - id = existingLanguage.StringId, - attributes = new - { - isoCode = newIsoCode - } + isoCode = newIsoCode } } } - }; + } + }; - const string route = "/operations"; + const string route = "/operations"; - // Act - (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = - await _testContext.ExecutePostAtomicAsync(route, requestBody); + // Act + (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = + await _testContext.ExecutePostAtomicAsync(route, requestBody); - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); - responseDocument.Results.Should().HaveCount(1); - responseDocument.Results[0].SingleData.Should().NotBeNull(); - responseDocument.Results[0].SingleData.Type.Should().Be("textLanguages"); - responseDocument.Results[0].SingleData.Attributes["isoCode"].Should().Be(newIsoCode); - responseDocument.Results[0].SingleData.Attributes.Should().NotContainKey("concurrencyToken"); - responseDocument.Results[0].SingleData.Relationships.Should().BeNull(); + responseDocument.Results.Should().HaveCount(1); + responseDocument.Results[0].SingleData.Should().NotBeNull(); + responseDocument.Results[0].SingleData.Type.Should().Be("textLanguages"); + responseDocument.Results[0].SingleData.Attributes["isoCode"].Should().Be(newIsoCode); + responseDocument.Results[0].SingleData.Attributes.Should().NotContainKey("concurrencyToken"); + responseDocument.Results[0].SingleData.Relationships.Should().BeNull(); - await _testContext.RunOnDatabaseAsync(async db => - { - TextLanguage languageInDatabase = await db.GetCollection().AsQueryable().FirstWithIdAsync(existingLanguage.Id); + await _testContext.RunOnDatabaseAsync(async db => + { + TextLanguage languageInDatabase = await db.GetCollection().AsQueryable().FirstWithIdAsync(existingLanguage.Id); - languageInDatabase.IsoCode.Should().Be(newIsoCode); - }); - } + languageInDatabase.IsoCode.Should().Be(newIsoCode); + }); + } - [Fact] - public async Task Cannot_update_resource_for_unknown_ID() + [Fact] + public async Task Cannot_update_resource_for_unknown_ID() + { + // Arrange + var requestBody = new { - // Arrange - var requestBody = new + atomic__operations = new[] { - atomic__operations = new[] + new { - new + op = "update", + data = new { - op = "update", - data = new + type = "performers", + id = "ffffffffffffffffffffffff", + attributes = new + { + }, + relationships = new { - type = "performers", - id = "ffffffffffffffffffffffff", - attributes = new - { - }, - relationships = new - { - } } } } - }; + } + }; - const string route = "/operations"; + const string route = "/operations"; - // Act - (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + // Act + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.NotFound); + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.NotFound); - responseDocument.Errors.Should().HaveCount(1); + responseDocument.Errors.Should().HaveCount(1); - Error error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.NotFound); - error.Title.Should().Be("The requested resource does not exist."); - error.Detail.Should().Be("Resource of type 'performers' with ID 'ffffffffffffffffffffffff' does not exist."); - error.Source.Pointer.Should().Be("/atomic:operations[0]"); - } + Error error = responseDocument.Errors[0]; + error.StatusCode.Should().Be(HttpStatusCode.NotFound); + error.Title.Should().Be("The requested resource does not exist."); + error.Detail.Should().Be("Resource of type 'performers' with ID 'ffffffffffffffffffffffff' does not exist."); + error.Source.Pointer.Should().Be("/atomic:operations[0]"); } } diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateToOneRelationshipTests.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateToOneRelationshipTests.cs index 5c55006..ad86c53 100644 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateToOneRelationshipTests.cs +++ b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateToOneRelationshipTests.cs @@ -1,81 +1,78 @@ using System.Net; -using System.Net.Http; -using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Serialization.Objects; using JsonApiDotNetCoreMongoDbExampleTests.TestBuildingBlocks; using Xunit; -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Updating.Resources +namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Updating.Resources; + +[Collection("AtomicOperationsFixture")] +public sealed class AtomicUpdateToOneRelationshipTests { - [Collection("AtomicOperationsFixture")] - public sealed class AtomicUpdateToOneRelationshipTests + private readonly IntegrationTestContext _testContext; + private readonly OperationsFakers _fakers = new(); + + public AtomicUpdateToOneRelationshipTests(AtomicOperationsFixture fixture) { - private readonly IntegrationTestContext _testContext; - private readonly OperationsFakers _fakers = new OperationsFakers(); + _testContext = fixture.TestContext; - public AtomicUpdateToOneRelationshipTests(AtomicOperationsFixture fixture) - { - _testContext = fixture.TestContext; + fixture.TestContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); + } - fixture.TestContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); - } + [Fact] + public async Task Cannot_create_relationship() + { + // Arrange + Lyric existingLyric = _fakers.Lyric.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.Generate(); - [Fact] - public async Task Cannot_create_relationship() + await _testContext.RunOnDatabaseAsync(async db => { - // Arrange - Lyric existingLyric = _fakers.Lyric.Generate(); - MusicTrack existingTrack = _fakers.MusicTrack.Generate(); - - await _testContext.RunOnDatabaseAsync(async db => - { - await db.GetCollection().InsertOneAsync(existingLyric); - await db.GetCollection().InsertOneAsync(existingTrack); - }); + await db.GetCollection().InsertOneAsync(existingLyric); + await db.GetCollection().InsertOneAsync(existingTrack); + }); - var requestBody = new + var requestBody = new + { + atomic__operations = new[] { - atomic__operations = new[] + new { - new + op = "update", + data = new { - op = "update", - data = new + type = "lyrics", + id = existingLyric.StringId, + relationships = new { - type = "lyrics", - id = existingLyric.StringId, - relationships = new + track = new { - track = new + data = new { - data = new - { - type = "musicTracks", - id = existingTrack.StringId - } + type = "musicTracks", + id = existingTrack.StringId } } } } } - }; + } + }; - const string route = "/operations"; + const string route = "/operations"; - // Act - (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); + // Act + (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.Should().HaveCount(1); + responseDocument.Errors.Should().HaveCount(1); - Error error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.BadRequest); - error.Title.Should().Be("Relationships are not supported when using MongoDB."); - error.Detail.Should().BeNull(); - error.Source.Pointer.Should().Be("/atomic:operations[0]"); - } + Error error = responseDocument.Errors[0]; + error.StatusCode.Should().Be(HttpStatusCode.BadRequest); + error.Title.Should().Be("Relationships are not supported when using MongoDB."); + error.Detail.Should().BeNull(); + error.Source.Pointer.Should().Be("/atomic:operations[0]"); } } diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/Meta/ResourceMetaTests.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/Meta/ResourceMetaTests.cs index e6cb1ff..cc50b39 100644 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/Meta/ResourceMetaTests.cs +++ b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/Meta/ResourceMetaTests.cs @@ -1,50 +1,46 @@ -using System.Collections.Generic; using System.Net; -using System.Net.Http; -using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Serialization.Objects; using JsonApiDotNetCoreMongoDbExampleTests.TestBuildingBlocks; using Xunit; -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.Meta +namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.Meta; + +public sealed class ResourceMetaTests : IClassFixture> { - public sealed class ResourceMetaTests : IClassFixture> + private readonly IntegrationTestContext _testContext; + private readonly SupportFakers _fakers = new(); + + public ResourceMetaTests(IntegrationTestContext testContext) { - private readonly IntegrationTestContext _testContext; - private readonly SupportFakers _fakers = new SupportFakers(); + _testContext = testContext; + } - public ResourceMetaTests(IntegrationTestContext testContext) - { - _testContext = testContext; - } + [Fact] + public async Task Returns_resource_meta_from_ResourceDefinition() + { + // Arrange + List tickets = _fakers.SupportTicket.Generate(3); + tickets[0].Description = "Critical: " + tickets[0].Description; + tickets[2].Description = "Critical: " + tickets[2].Description; - [Fact] - public async Task Returns_resource_meta_from_ResourceDefinition() + await _testContext.RunOnDatabaseAsync(async db => { - // Arrange - List tickets = _fakers.SupportTicket.Generate(3); - tickets[0].Description = "Critical: " + tickets[0].Description; - tickets[2].Description = "Critical: " + tickets[2].Description; - - await _testContext.RunOnDatabaseAsync(async db => - { - await db.ClearCollectionAsync(); - await db.GetCollection().InsertManyAsync(tickets); - }); - - const string route = "/supportTickets"; - - // Act - (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); - - responseDocument.ManyData.Should().HaveCount(3); - responseDocument.ManyData[0].Meta.Should().ContainKey("hasHighPriority"); - responseDocument.ManyData[1].Meta.Should().BeNull(); - responseDocument.ManyData[2].Meta.Should().ContainKey("hasHighPriority"); - } + await db.ClearCollectionAsync(); + await db.GetCollection().InsertManyAsync(tickets); + }); + + const string route = "/supportTickets"; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); + + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); + + responseDocument.ManyData.Should().HaveCount(3); + responseDocument.ManyData[0].Meta.Should().ContainKey("hasHighPriority"); + responseDocument.ManyData[1].Meta.Should().BeNull(); + responseDocument.ManyData[2].Meta.Should().ContainKey("hasHighPriority"); } } diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/Meta/SupportFakers.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/Meta/SupportFakers.cs index ca6f6f5..d305f92 100644 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/Meta/SupportFakers.cs +++ b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/Meta/SupportFakers.cs @@ -1,19 +1,17 @@ -using System; using Bogus; using JsonApiDotNetCoreMongoDbExampleTests.TestBuildingBlocks; // @formatter:wrap_chained_method_calls chop_always // @formatter:keep_existing_linebreaks true -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.Meta +namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.Meta; + +internal sealed class SupportFakers : FakerContainer { - internal sealed class SupportFakers : FakerContainer - { - private readonly Lazy> _lazySupportTicketFaker = new Lazy>(() => - 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; - } + public Faker SupportTicket => _lazySupportTicketFaker.Value; } diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/Meta/SupportTicket.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/Meta/SupportTicket.cs index aaee7e8..b44540d 100644 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/Meta/SupportTicket.cs +++ b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/Meta/SupportTicket.cs @@ -2,12 +2,11 @@ using JsonApiDotNetCore.MongoDb.Resources; using JsonApiDotNetCore.Resources.Annotations; -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.Meta +namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.Meta; + +[UsedImplicitly(ImplicitUseTargetFlags.Members)] +public sealed class SupportTicket : MongoIdentifiable { - [UsedImplicitly(ImplicitUseTargetFlags.Members)] - public sealed class SupportTicket : MongoIdentifiable - { - [Attr] - public string Description { get; set; } - } + [Attr] + public string Description { get; set; } } diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/Meta/SupportTicketDefinition.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/Meta/SupportTicketDefinition.cs index 3f18eff..81fbbc2 100644 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/Meta/SupportTicketDefinition.cs +++ b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/Meta/SupportTicketDefinition.cs @@ -1,30 +1,27 @@ -using System; -using System.Collections.Generic; using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Resources; -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.Meta +namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.Meta; + +[UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] +public sealed class SupportTicketDefinition : JsonApiResourceDefinition { - [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] - public sealed class SupportTicketDefinition : JsonApiResourceDefinition + public SupportTicketDefinition(IResourceGraph resourceGraph) + : base(resourceGraph) { - public SupportTicketDefinition(IResourceGraph resourceGraph) - : base(resourceGraph) - { - } + } - public override IDictionary GetMeta(SupportTicket resource) + public override IDictionary GetMeta(SupportTicket resource) + { + if (resource.Description != null && resource.Description.StartsWith("Critical:", StringComparison.Ordinal)) { - if (resource.Description != null && resource.Description.StartsWith("Critical:", StringComparison.Ordinal)) + return new Dictionary { - return new Dictionary - { - ["hasHighPriority"] = true - }; - } - - return base.GetMeta(resource); + ["hasHighPriority"] = true + }; } + + return base.GetMeta(resource); } } diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/Meta/SupportTicketsController.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/Meta/SupportTicketsController.cs index b5674d2..8ca1a4e 100644 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/Meta/SupportTicketsController.cs +++ b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/Meta/SupportTicketsController.cs @@ -3,13 +3,12 @@ using JsonApiDotNetCore.Services; using Microsoft.Extensions.Logging; -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.Meta +namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.Meta; + +public sealed class SupportTicketsController : JsonApiController { - public sealed class SupportTicketsController : JsonApiController + public SupportTicketsController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) + : base(options, loggerFactory, resourceService) { - public SupportTicketsController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) - : base(options, loggerFactory, resourceService) - { - } } } diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/Meta/TopLevelCountTests.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/Meta/TopLevelCountTests.cs index 7aa39df..72c0f35 100644 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/Meta/TopLevelCountTests.cs +++ b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/Meta/TopLevelCountTests.cs @@ -1,6 +1,4 @@ using System.Net; -using System.Net.Http; -using System.Threading.Tasks; using FluentAssertions; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Resources; @@ -9,135 +7,134 @@ using Microsoft.Extensions.DependencyInjection; using Xunit; -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.Meta +namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.Meta; + +public sealed class TopLevelCountTests : IClassFixture> { - public sealed class TopLevelCountTests : IClassFixture> + private readonly IntegrationTestContext _testContext; + private readonly SupportFakers _fakers = new(); + + public TopLevelCountTests(IntegrationTestContext testContext) { - private readonly IntegrationTestContext _testContext; - private readonly SupportFakers _fakers = new SupportFakers(); + _testContext = testContext; - public TopLevelCountTests(IntegrationTestContext testContext) + testContext.ConfigureServicesAfterStartup(services => { - _testContext = testContext; + services.AddScoped(typeof(IResourceChangeTracker<>), typeof(NeverSameResourceChangeTracker<>)); + }); - testContext.ConfigureServicesAfterStartup(services => - { - services.AddScoped(typeof(IResourceChangeTracker<>), typeof(NeverSameResourceChangeTracker<>)); - }); + var options = (JsonApiOptions)testContext.Factory.Services.GetRequiredService(); + options.IncludeTotalResourceCount = true; + } - var options = (JsonApiOptions)testContext.Factory.Services.GetRequiredService(); - options.IncludeTotalResourceCount = true; - } + [Fact] + public async Task Renders_resource_count_for_collection() + { + // Arrange + SupportTicket ticket = _fakers.SupportTicket.Generate(); - [Fact] - public async Task Renders_resource_count_for_collection() + await _testContext.RunOnDatabaseAsync(async db => { - // Arrange - SupportTicket ticket = _fakers.SupportTicket.Generate(); + await db.ClearCollectionAsync(); + await db.GetCollection().InsertOneAsync(ticket); + }); - await _testContext.RunOnDatabaseAsync(async db => - { - await db.ClearCollectionAsync(); - await db.GetCollection().InsertOneAsync(ticket); - }); - - const string route = "/supportTickets"; + const string route = "/supportTickets"; - // Act - (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); - responseDocument.Meta.Should().NotBeNull(); - responseDocument.Meta["totalResources"].Should().Be(1); - } + responseDocument.Meta.Should().NotBeNull(); + responseDocument.Meta["totalResources"].Should().Be(1); + } - [Fact] - public async Task Renders_resource_count_for_empty_collection() + [Fact] + public async Task Renders_resource_count_for_empty_collection() + { + // Arrange + await _testContext.RunOnDatabaseAsync(async db => { - // Arrange - await _testContext.RunOnDatabaseAsync(async db => - { - await db.ClearCollectionAsync(); - }); + await db.ClearCollectionAsync(); + }); - const string route = "/supportTickets"; + const string route = "/supportTickets"; - // Act - (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); - responseDocument.Meta.Should().NotBeNull(); - responseDocument.Meta["totalResources"].Should().Be(0); - } + responseDocument.Meta.Should().NotBeNull(); + responseDocument.Meta["totalResources"].Should().Be(0); + } - [Fact] - public async Task Hides_resource_count_in_create_resource_response() - { - // Arrange - string newDescription = _fakers.SupportTicket.Generate().Description; + [Fact] + public async Task Hides_resource_count_in_create_resource_response() + { + // Arrange + string newDescription = _fakers.SupportTicket.Generate().Description; - var requestBody = new + var requestBody = new + { + data = new { - data = new + type = "supportTickets", + attributes = new { - type = "supportTickets", - attributes = new - { - description = newDescription - } + description = newDescription } - }; + } + }; - const string route = "/supportTickets"; + const string route = "/supportTickets"; - // Act - (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.Created); + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.Created); - responseDocument.Meta.Should().BeNull(); - } + responseDocument.Meta.Should().BeNull(); + } - [Fact] - public async Task Hides_resource_count_in_update_resource_response() - { - // Arrange - SupportTicket existingTicket = _fakers.SupportTicket.Generate(); + [Fact] + public async Task Hides_resource_count_in_update_resource_response() + { + // Arrange + SupportTicket existingTicket = _fakers.SupportTicket.Generate(); - string newDescription = _fakers.SupportTicket.Generate().Description; + string newDescription = _fakers.SupportTicket.Generate().Description; - await _testContext.RunOnDatabaseAsync(async db => - { - await db.GetCollection().InsertOneAsync(existingTicket); - }); + await _testContext.RunOnDatabaseAsync(async db => + { + await db.GetCollection().InsertOneAsync(existingTicket); + }); - var requestBody = new + var requestBody = new + { + data = new { - data = new + type = "supportTickets", + id = existingTicket.StringId, + attributes = new { - type = "supportTickets", - id = existingTicket.StringId, - attributes = new - { - description = newDescription - } + description = newDescription } - }; + } + }; - string route = "/supportTickets/" + existingTicket.StringId; + string route = "/supportTickets/" + existingTicket.StringId; - // Act - (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); - responseDocument.Meta.Should().BeNull(); - } + responseDocument.Meta.Should().BeNull(); } } diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/QueryStrings/AccountPreferences.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/QueryStrings/AccountPreferences.cs index 5f3de6e..9099cdd 100644 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/QueryStrings/AccountPreferences.cs +++ b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/QueryStrings/AccountPreferences.cs @@ -2,12 +2,11 @@ using JsonApiDotNetCore.MongoDb.Resources; using JsonApiDotNetCore.Resources.Annotations; -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.QueryStrings +namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.QueryStrings; + +[UsedImplicitly(ImplicitUseTargetFlags.Members)] +public sealed class AccountPreferences : MongoIdentifiable { - [UsedImplicitly(ImplicitUseTargetFlags.Members)] - public sealed class AccountPreferences : MongoIdentifiable - { - [Attr] - public bool UseDarkTheme { get; set; } - } + [Attr] + public bool UseDarkTheme { get; set; } } diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/QueryStrings/Appointment.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/QueryStrings/Appointment.cs index a9fd483..0b9ccf5 100644 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/QueryStrings/Appointment.cs +++ b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/QueryStrings/Appointment.cs @@ -1,20 +1,18 @@ -using System; using JetBrains.Annotations; using JsonApiDotNetCore.MongoDb.Resources; using JsonApiDotNetCore.Resources.Annotations; -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.QueryStrings +namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.QueryStrings; + +[UsedImplicitly(ImplicitUseTargetFlags.Members)] +public sealed class Appointment : MongoIdentifiable { - [UsedImplicitly(ImplicitUseTargetFlags.Members)] - public sealed class Appointment : MongoIdentifiable - { - [Attr] - public string Title { get; set; } + [Attr] + public string Title { get; set; } - [Attr] - public DateTimeOffset StartTime { get; set; } + [Attr] + public DateTimeOffset StartTime { get; set; } - [Attr] - public DateTimeOffset EndTime { get; set; } - } + [Attr] + public DateTimeOffset EndTime { get; set; } } diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/QueryStrings/Blog.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/QueryStrings/Blog.cs index 7ef49d6..fdefe32 100644 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/QueryStrings/Blog.cs +++ b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/QueryStrings/Blog.cs @@ -1,30 +1,27 @@ -using System; -using System.Collections.Generic; using JetBrains.Annotations; using JsonApiDotNetCore.MongoDb.Resources; using JsonApiDotNetCore.Resources.Annotations; using MongoDB.Bson.Serialization.Attributes; -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.QueryStrings +namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.QueryStrings; + +[UsedImplicitly(ImplicitUseTargetFlags.Members)] +public sealed class Blog : MongoIdentifiable { - [UsedImplicitly(ImplicitUseTargetFlags.Members)] - public sealed class Blog : MongoIdentifiable - { - [Attr] - public string Title { get; set; } + [Attr] + public string Title { get; set; } - [Attr] - public string PlatformName { get; set; } + [Attr] + public string PlatformName { get; set; } - [Attr(Capabilities = AttrCapabilities.All & ~(AttrCapabilities.AllowCreate | AttrCapabilities.AllowChange))] - public bool ShowAdvertisements => PlatformName.EndsWith("(using free account)", StringComparison.Ordinal); + [Attr(Capabilities = AttrCapabilities.All & ~(AttrCapabilities.AllowCreate | AttrCapabilities.AllowChange))] + public bool ShowAdvertisements => PlatformName.EndsWith("(using free account)", StringComparison.Ordinal); - [HasMany] - [BsonIgnore] - public IList Posts { get; set; } + [HasMany] + [BsonIgnore] + public IList Posts { get; set; } - [HasOne] - [BsonIgnore] - public WebAccount Owner { get; set; } - } + [HasOne] + [BsonIgnore] + public WebAccount Owner { get; set; } } diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/QueryStrings/BlogPost.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/QueryStrings/BlogPost.cs index 4678864..f646089 100644 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/QueryStrings/BlogPost.cs +++ b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/QueryStrings/BlogPost.cs @@ -1,41 +1,39 @@ -using System.Collections.Generic; using JetBrains.Annotations; using JsonApiDotNetCore.MongoDb.Resources; using JsonApiDotNetCore.Resources.Annotations; using MongoDB.Bson.Serialization.Attributes; -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.QueryStrings +namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.QueryStrings; + +[UsedImplicitly(ImplicitUseTargetFlags.Members)] +public sealed class BlogPost : MongoIdentifiable { - [UsedImplicitly(ImplicitUseTargetFlags.Members)] - public sealed class BlogPost : MongoIdentifiable - { - [Attr] - public string Caption { get; set; } - - [Attr] - public string Url { get; set; } - - [HasOne] - [BsonIgnore] - public WebAccount Author { get; set; } - - [HasOne] - [BsonIgnore] - public WebAccount Reviewer { get; set; } - - [HasManyThrough(nameof(BlogPostLabels))] - [BsonIgnore] - public ISet