From 245691724f4144b8998d0bc621876eb15a9f2cf1 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Mon, 1 Jul 2024 00:01:17 +0200 Subject: [PATCH 01/14] Increment version to 5.6.1 (used for pre-release builds from ci) --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index a30d43f..0e7e56e 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -27,6 +27,6 @@ false $(MSBuildThisFileDirectory)CodingGuidelines.ruleset $(MSBuildThisFileDirectory)tests.runsettings - 5.6.0 + 5.6.1 From 6e5fe0640c224cfdc49d5128976dec68ad795d23 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 9 Jul 2024 11:07:18 +0000 Subject: [PATCH 02/14] Bump dotnet-reportgenerator-globaltool from 5.3.6 to 5.3.7 (#63) --- .config/dotnet-tools.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 869b61a..56c65d5 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -15,7 +15,7 @@ ] }, "dotnet-reportgenerator-globaltool": { - "version": "5.3.6", + "version": "5.3.7", "commands": [ "reportgenerator" ] From 49ca834a1a1ed89aa2ab61a6465e96aa1846999b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Jul 2024 15:25:46 +0000 Subject: [PATCH 03/14] Bump regitlint from 6.3.12 to 6.3.13 (#64) --- .config/dotnet-tools.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 56c65d5..7bd7eff 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -9,7 +9,7 @@ ] }, "regitlint": { - "version": "6.3.12", + "version": "6.3.13", "commands": [ "regitlint" ] From 5ef48ed0cac85b116867c7a5d1fb7653570c8a53 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 27 Jul 2024 10:34:45 +0000 Subject: [PATCH 04/14] Bump dotnet-reportgenerator-globaltool from 5.3.7 to 5.3.8 (#65) --- .config/dotnet-tools.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 7bd7eff..cc2edb9 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -15,7 +15,7 @@ ] }, "dotnet-reportgenerator-globaltool": { - "version": "5.3.7", + "version": "5.3.8", "commands": [ "reportgenerator" ] From 77db43a28753271250d7c37bb5fcc227a3e7bd4a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 20 Aug 2024 02:04:02 +0000 Subject: [PATCH 05/14] Bump jetbrains.resharper.globaltools from 2024.1.4 to 2024.1.6 (#67) --- .config/dotnet-tools.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index cc2edb9..c6218b7 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "jetbrains.resharper.globaltools": { - "version": "2024.1.4", + "version": "2024.1.6", "commands": [ "jb" ] From 688630645317355c199175340e343c425e5f73ce Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sat, 31 Aug 2024 10:14:55 +0200 Subject: [PATCH 06/14] Reindent dependabot.yml --- .github/dependabot.yml | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 53356ab..8bd1514 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,22 +1,22 @@ version: 2 updates: -- package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "weekly" - pull-request-branch-name: - separator: "-" -- package-ecosystem: nuget - directory: "/" - schedule: - interval: daily - pull-request-branch-name: - separator: "-" - open-pull-requests-limit: 25 - ignore: - # Block updates to all exposed dependencies of the NuGet packages we produce, as updating them would be a breaking change. - - dependency-name: 'JsonApiDotNetCore*' - - dependency-name: 'MongoDB.Driver*' - # Block major updates of packages that require a matching .NET version. - - dependency-name: 'Microsoft.AspNetCore*' - update-types: ["version-update:semver-major"] + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + pull-request-branch-name: + separator: "-" + - package-ecosystem: nuget + directory: "/" + schedule: + interval: daily + pull-request-branch-name: + separator: "-" + open-pull-requests-limit: 25 + ignore: + # Block updates to all exposed dependencies of the NuGet packages we produce, as updating them would be a breaking change. + - dependency-name: "JsonApiDotNetCore*" + - dependency-name: "MongoDB.Driver*" + # Block major updates of packages that require a matching .NET version. + - dependency-name: "Microsoft.AspNetCore*" + update-types: ["version-update:semver-major"] From 2ab9e1766ff704ad383cfd54307b0909ec4d5dac Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sun, 8 Sep 2024 10:16:16 +0200 Subject: [PATCH 07/14] Add link for lack of relationships support --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8e93d08..1a33889 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ with both base classes, but `FreeStringMongoIdentifiable` probably makes the mos ## Limitations -- JSON:API relationships are currently not supported. You can use complex object graphs though, which are stored in a single document. +- JSON:API relationships are [currently not supported](https://github.com/json-api-dotnet/JsonApiDotNetCore.MongoDb/issues/73). You can use complex object graphs though, which are stored in a single document. ## Contributing From 4a40d0a0de272000b09b0d0cc635b49c931a40a1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 8 Sep 2024 11:23:19 +0000 Subject: [PATCH 08/14] Bump dotnet-reportgenerator-globaltool from 5.3.8 to 5.3.9 (#72) --- .config/dotnet-tools.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index c6218b7..1790e5a 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -15,7 +15,7 @@ ] }, "dotnet-reportgenerator-globaltool": { - "version": "5.3.8", + "version": "5.3.9", "commands": [ "reportgenerator" ] From 8b82c26105a12a5e98c5f4759d2e8a37b7705a0f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 12:11:17 +0000 Subject: [PATCH 09/14] Bump dotnet-reportgenerator-globaltool from 5.3.9 to 5.3.10 (#77) --- .config/dotnet-tools.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 1790e5a..fcd0aee 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -15,7 +15,7 @@ ] }, "dotnet-reportgenerator-globaltool": { - "version": "5.3.9", + "version": "5.3.10", "commands": [ "reportgenerator" ] From 4605b257eec405028f17845c87828b7a20074db1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 13 Oct 2024 07:02:55 +0000 Subject: [PATCH 10/14] Bump dotnet-reportgenerator-globaltool from 5.3.10 to 5.3.11 (#79) --- .config/dotnet-tools.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index fcd0aee..2789e0c 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -15,7 +15,7 @@ ] }, "dotnet-reportgenerator-globaltool": { - "version": "5.3.10", + "version": "5.3.11", "commands": [ "reportgenerator" ] From 69f54ba819a791640833221b31d8399c13808b6f Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Wed, 23 Apr 2025 04:03:49 +0200 Subject: [PATCH 11/14] Update to latest version of EphemeralMongo (#94) --- package-versions.props | 8 ++++---- test/TestBuildingBlocks/MongoRunnerProvider.cs | 1 - test/TestBuildingBlocks/TestBuildingBlocks.csproj | 8 ++++---- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/package-versions.props b/package-versions.props index cd63e0e..f0982cc 100644 --- a/package-versions.props +++ b/package-versions.props @@ -2,18 +2,18 @@ 5.6.0 - 2.20.0 + 2.28.0 35.5.* 6.0.* - 1.1.* + 2.0.* 6.12.* 2.3.* 2.0.* - 2.27.* + 2.28.* 8.0.* - 17.10.* + 17.13.* 2.8.* diff --git a/test/TestBuildingBlocks/MongoRunnerProvider.cs b/test/TestBuildingBlocks/MongoRunnerProvider.cs index f2dc54a..96af01f 100644 --- a/test/TestBuildingBlocks/MongoRunnerProvider.cs +++ b/test/TestBuildingBlocks/MongoRunnerProvider.cs @@ -25,7 +25,6 @@ public IMongoRunner Get() { // Single-node replica set mode is required for transaction support in MongoDB. UseSingleNodeReplicaSet = true, - KillMongoProcessesWhenCurrentProcessExits = true, AdditionalArguments = "--quiet" }; diff --git a/test/TestBuildingBlocks/TestBuildingBlocks.csproj b/test/TestBuildingBlocks/TestBuildingBlocks.csproj index 954ef72..d7b0934 100644 --- a/test/TestBuildingBlocks/TestBuildingBlocks.csproj +++ b/test/TestBuildingBlocks/TestBuildingBlocks.csproj @@ -1,4 +1,4 @@ - + net8.0;net6.0 @@ -13,9 +13,9 @@ - - - + + + From 5eca46771b89a58ffacedf65b150a4578236d3e8 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Fri, 25 Apr 2025 01:30:19 +0200 Subject: [PATCH 12/14] Sync up with changes from JsonApiDotNetCore 5.7.0 --- .config/dotnet-tools.json | 13 +- .editorconfig | 166 ++++++++++--- .github/ISSUE_TEMPLATE/question.md | 2 +- .github/workflows/build.yml | 28 ++- .github/workflows/codeql.yml | 4 +- Build.ps1 | 8 +- CodingGuidelines.ruleset | 48 +++- Directory.Build.props | 48 +++- JsonApiDotNetCore.MongoDb.sln.DotSettings | 35 +-- LICENSE | 1 + PackageReadme.md | 2 +- README.md | 232 +++++++++++------- WarningSeverities.DotSettings | 2 +- package-versions.props | 18 +- .../GettingStarted/GettingStarted.csproj | 2 +- src/Examples/GettingStarted/Program.cs | 24 +- .../Properties/launchSettings.json | 2 +- src/Examples/GettingStarted/README.md | 5 +- .../Controllers/OperationsController.cs | 4 +- .../Definitions/TodoItemDefinition.cs | 23 +- .../JsonApiDotNetCoreMongoDbExample.csproj | 2 +- .../Program.cs | 9 +- .../Properties/launchSettings.json | 2 +- .../ArgumentGuard.cs | 17 -- .../AtomicOperations/MongoTransaction.cs | 2 +- .../MongoTransactionFactory.cs | 2 +- .../Configuration/ResourceGraphExtensions.cs | 2 + .../ServiceCollectionExtensions.cs | 4 +- ...ComparisonInFilterNotSupportedException.cs | 9 +- .../UnsupportedRelationshipException.cs | 9 +- .../JsonApiDotNetCore.MongoDb.csproj | 9 +- .../PolyfillCollectionExtensions.cs | 12 + .../Properties/AssemblyInfo.cs | 3 + .../HideRelationshipsSparseFieldSetCache.cs | 12 +- src/JsonApiDotNetCore.MongoDb/ReadOnlySet.cs | 171 +++++++++++++ .../Repositories/MongoDataAccess.cs | 4 +- .../MongoQueryExpressionValidator.cs | 6 +- .../Repositories/MongoRepository.cs | 48 ++-- ...s => AtomicOperationsCollectionFixture.cs} | 2 +- ...rAtomicOperationsTestsThatChangeOptions.cs | 15 +- .../Creating/AtomicCreateResourceTests.cs | 55 ++--- ...reateResourceWithClientGeneratedIdTests.cs | 18 +- ...eateResourceWithToManyRelationshipTests.cs | 8 +- ...reateResourceWithToOneRelationshipTests.cs | 8 +- .../Deleting/AtomicDeleteResourceTests.cs | 8 +- .../LocalIds/AtomicLocalIdTests.cs | 24 +- .../Meta/AtomicResourceMetaTests.cs | 28 +-- .../Mixed/MaximumOperationsPerRequestTests.cs | 3 +- .../AtomicOperations/OperationsController.cs | 4 +- .../AtomicOperations/OperationsDbContext.cs | 3 +- .../AtomicOperations/OperationsFakers.cs | 4 +- .../Transactions/AtomicRollbackTests.cs | 22 +- .../AtomicTransactionConsistencyTests.cs | 19 +- .../Transactions/LyricRepository.cs | 6 +- .../AtomicAddToToManyRelationshipTests.cs | 16 +- ...AtomicRemoveFromToManyRelationshipTests.cs | 16 +- .../AtomicReplaceToManyRelationshipTests.cs | 16 +- .../AtomicUpdateToOneRelationshipTests.cs | 8 +- .../AtomicReplaceToManyRelationshipTests.cs | 8 +- .../Resources/AtomicUpdateResourceTests.cs | 36 +-- .../AtomicUpdateToOneRelationshipTests.cs | 8 +- .../HitCountingResourceDefinition.cs | 13 +- .../IntegrationTests/Meta/MetaDbContext.cs | 3 +- .../Meta/ResourceMetaTests.cs | 12 +- .../Meta/TopLevelCountTests.cs | 15 +- .../Filtering/FilterDataTypeTests.cs | 121 +++++++-- .../QueryStrings/Filtering/FilterDbContext.cs | 3 +- .../Filtering/FilterDepthTests.cs | 10 +- .../Filtering/FilterOperatorTests.cs | 150 +++++++++-- .../QueryStrings/Filtering/FilterTests.cs | 6 +- .../Filtering/FilterableResource.cs | 16 ++ .../QueryStrings/Includes/IncludeTests.cs | 2 +- .../PaginationWithTotalCountTests.cs | 18 +- .../Pagination/RangeValidationTests.cs | 2 +- .../QueryStrings/QueryStringDbContext.cs | 3 +- .../QueryStrings/Sorting/SortTests.cs | 18 +- .../ResultCapturingRepository.cs | 5 +- .../SparseFieldSets/SparseFieldSetTests.cs | 57 +++-- .../QueryStrings/WebAccount.cs | 2 +- .../ReadWrite/Creating/CreateResourceTests.cs | 26 +- ...reateResourceWithClientGeneratedIdTests.cs | 24 +- ...eateResourceWithToManyRelationshipTests.cs | 4 +- ...reateResourceWithToOneRelationshipTests.cs | 8 +- .../ReadWrite/Deleting/DeleteResourceTests.cs | 8 +- .../Fetching/FetchRelationshipTests.cs | 12 +- .../ReadWrite/Fetching/FetchResourceTests.cs | 40 +-- .../ReadWrite/ReadWriteDbContext.cs | 3 +- .../AddToToManyRelationshipTests.cs | 12 +- .../RemoveFromToManyRelationshipTests.cs | 12 +- .../ReplaceToManyRelationshipTests.cs | 12 +- .../UpdateToOneRelationshipTests.cs | 6 +- .../ReplaceToManyRelationshipTests.cs | 12 +- .../Updating/Resources/UpdateResourceTests.cs | 48 ++-- .../Resources/UpdateToOneRelationshipTests.cs | 6 +- .../IntegrationTests/ReadWrite/WorkItem.cs | 2 +- .../Reading/ResourceDefinitionReadTests.cs | 79 +++--- .../Reading/UniverseDbContext.cs | 3 +- .../JsonApiDotNetCoreMongoDbTests.csproj | 2 +- test/TestBuildingBlocks/FakerExtensions.cs | 28 ++- test/TestBuildingBlocks/FluentExtensions.cs | 45 ++++ .../FluentMetaExtensions.cs | 37 +++ test/TestBuildingBlocks/IntegrationTest.cs | 40 ++- .../IntegrationTestContext.cs | 16 +- test/TestBuildingBlocks/MarkedText.cs | 44 ++++ test/TestBuildingBlocks/MongoDbSetShim.cs | 2 +- .../TestBuildingBlocks/MongoRunnerProvider.cs | 15 +- .../NullabilityAssertionExtensions.cs | 43 ---- .../ObjectAssertionsExtensions.cs | 33 --- .../{ => Properties}/AssemblyInfo.cs | 0 test/TestBuildingBlocks/ResourceTypeFinder.cs | 9 +- .../ServiceCollectionExtensions.cs | 3 + .../TestBuildingBlocks.csproj | 4 +- .../TestControllerProvider.cs | 4 +- tests.runsettings | 6 +- 114 files changed, 1556 insertions(+), 873 deletions(-) delete mode 100644 src/JsonApiDotNetCore.MongoDb/ArgumentGuard.cs create mode 100644 src/JsonApiDotNetCore.MongoDb/PolyfillCollectionExtensions.cs create mode 100644 src/JsonApiDotNetCore.MongoDb/Properties/AssemblyInfo.cs rename src/JsonApiDotNetCore.MongoDb/Queries/{Internal => }/HideRelationshipsSparseFieldSetCache.cs (84%) create mode 100644 src/JsonApiDotNetCore.MongoDb/ReadOnlySet.cs rename test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/{AtomicOperationsTestCollection.cs => AtomicOperationsCollectionFixture.cs} (81%) create mode 100644 test/TestBuildingBlocks/FluentExtensions.cs create mode 100644 test/TestBuildingBlocks/FluentMetaExtensions.cs create mode 100644 test/TestBuildingBlocks/MarkedText.cs delete mode 100644 test/TestBuildingBlocks/NullabilityAssertionExtensions.cs delete mode 100644 test/TestBuildingBlocks/ObjectAssertionsExtensions.cs rename test/TestBuildingBlocks/{ => Properties}/AssemblyInfo.cs (100%) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 2789e0c..197960a 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,22 +3,25 @@ "isRoot": true, "tools": { "jetbrains.resharper.globaltools": { - "version": "2024.1.6", + "version": "2024.3.6", "commands": [ "jb" - ] + ], + "rollForward": false }, "regitlint": { "version": "6.3.13", "commands": [ "regitlint" - ] + ], + "rollForward": false }, "dotnet-reportgenerator-globaltool": { - "version": "5.3.11", + "version": "5.4.5", "commands": [ "reportgenerator" - ] + ], + "rollForward": false } } } \ No newline at end of file diff --git a/.editorconfig b/.editorconfig index 5a036d1..2e5c106 100644 --- a/.editorconfig +++ b/.editorconfig @@ -4,60 +4,125 @@ root = true [*] indent_style = space indent_size = 4 +tab-width = 4 charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true -[*.{config,csproj,css,js,json,props,ruleset,xslt,html}] +[*.{config,csproj,css,js,json,props,targets,xml,ruleset,xsd,xslt,html,yml,yaml}] indent_size = 2 +tab-width = 2 +max_line_length = 160 + +[*.{cs,cshtml,ascx,aspx}] -[*.{cs}] #### C#/.NET Coding Conventions #### +# Default severity for IDE* analyzers with category 'Style' +# Note: specific rules below use severity silent, because Resharper code cleanup auto-fixes them. +dotnet_analyzer_diagnostic.category-Style.severity = warning + # 'using' directive preferences dotnet_sort_system_directives_first = true -csharp_using_directive_placement = outside_namespace:suggestion +csharp_using_directive_placement = outside_namespace:silent +# IDE0005: Remove unnecessary import +dotnet_diagnostic.IDE0005.severity = silent # Namespace declarations -csharp_style_namespace_declarations = file_scoped:suggestion +csharp_style_namespace_declarations = file_scoped:silent +# IDE0160: Use block-scoped namespace +dotnet_diagnostic.IDE0160.severity = silent +# IDE0161: Use file-scoped namespace +dotnet_diagnostic.IDE0161.severity = silent # this. preferences -dotnet_style_qualification_for_field = false:suggestion -dotnet_style_qualification_for_property = false:suggestion -dotnet_style_qualification_for_method = false:suggestion -dotnet_style_qualification_for_event = false:suggestion +dotnet_style_qualification_for_field = false:silent +dotnet_style_qualification_for_property = false:silent +dotnet_style_qualification_for_method = false:silent +dotnet_style_qualification_for_event = false:silent +# IDE0003: Remove this or Me qualification +dotnet_diagnostic.IDE0003.severity = silent +# IDE0009: Add this or Me qualification +dotnet_diagnostic.IDE0009.severity = silent # Language keywords vs BCL types preferences -dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion -dotnet_style_predefined_type_for_member_access = true:suggestion +dotnet_style_predefined_type_for_locals_parameters_members = true:silent +dotnet_style_predefined_type_for_member_access = true:silent +# IDE0049: Use language keywords instead of framework type names for type references +dotnet_diagnostic.IDE0049.severity = silent # Modifier preferences -dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion -csharp_preferred_modifier_order = public, private, protected, internal, new, static, abstract, virtual, sealed, readonly, override, extern, unsafe, volatile, async:suggestion -csharp_style_pattern_local_over_anonymous_function = false:silent +dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent +# IDE0040: Add accessibility modifiers +dotnet_diagnostic.IDE0040.severity = silent +csharp_preferred_modifier_order = public, private, protected, internal, new, static, abstract, virtual, sealed, readonly, override, extern, unsafe, volatile, async:silent +# IDE0036: Order modifiers +dotnet_diagnostic.IDE0036.severity = silent # Expression-level preferences dotnet_style_operator_placement_when_wrapping = end_of_line -dotnet_style_prefer_auto_properties = true:suggestion -dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion -dotnet_style_prefer_conditional_expression_over_return = true:suggestion -csharp_style_unused_value_expression_statement_preference = discard_variable:suggestion +dotnet_style_prefer_auto_properties = true:silent +# IDE0032: Use auto property +dotnet_diagnostic.IDE0032.severity = silent +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +# IDE0045: Use conditional expression for assignment +dotnet_diagnostic.IDE0045.severity = silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +# IDE0046: Use conditional expression for return +dotnet_diagnostic.IDE0046.severity = silent +csharp_style_unused_value_expression_statement_preference = discard_variable:silent +# IDE0058: Remove unused expression value +dotnet_diagnostic.IDE0058.severity = silent + +# Collection expression preferences (note: partially turned off in Directory.Build.props) +dotnet_style_prefer_collection_expression = when_types_exactly_match # Parameter preferences -dotnet_code_quality_unused_parameters = non_public:suggestion +dotnet_code_quality_unused_parameters = non_public + +# Local functions vs lambdas +csharp_style_prefer_local_over_anonymous_function = false:silent +# IDE0039: Use local function instead of lambda +dotnet_diagnostic.IDE0039.severity = silent # Expression-bodied members -csharp_style_expression_bodied_accessors = true:suggestion -csharp_style_expression_bodied_constructors = false:suggestion -csharp_style_expression_bodied_indexers = true:suggestion -csharp_style_expression_bodied_lambdas = true:suggestion -csharp_style_expression_bodied_local_functions = false:suggestion -csharp_style_expression_bodied_methods = false:suggestion -csharp_style_expression_bodied_operators = false:suggestion -csharp_style_expression_bodied_properties = true:suggestion +csharp_style_expression_bodied_accessors = true:silent +# IDE0027: Use expression body for accessors +dotnet_diagnostic.IDE0027.severity = silent +csharp_style_expression_bodied_constructors = false:silent +# IDE0021: Use expression body for constructors +dotnet_diagnostic.IDE0021.severity = silent +csharp_style_expression_bodied_indexers = true:silent +# IDE0026: Use expression body for indexers +dotnet_diagnostic.IDE0026.severity = silent +csharp_style_expression_bodied_lambdas = true:silent +# IDE0053: Use expression body for lambdas +dotnet_diagnostic.IDE0053.severity = silent +csharp_style_expression_bodied_local_functions = false:silent +# IDE0061: Use expression body for local functions +dotnet_diagnostic.IDE0061.severity = silent +csharp_style_expression_bodied_methods = false:silent +# IDE0022: Use expression body for methods +dotnet_diagnostic.IDE0022.severity = silent +csharp_style_expression_bodied_operators = false:silent +# IDE0023: Use expression body for conversion operators +dotnet_diagnostic.IDE0023.severity = silent +# IDE0024: Use expression body for operators +dotnet_diagnostic.IDE0024.severity = silent +csharp_style_expression_bodied_properties = true:silent +# IDE0025: Use expression body for properties +dotnet_diagnostic.IDE0025.severity = silent + +# Member preferences (these analyzers are unreliable) +# IDE0051: Remove unused private member +dotnet_diagnostic.IDE0051.severity = silent +# IDE0052: Remove unread private member +dotnet_diagnostic.IDE0052.severity = silent # Code-block preferences -csharp_prefer_braces = true:suggestion +csharp_prefer_braces = true:silent +# IDE0011: Add braces +dotnet_diagnostic.IDE0011.severity = silent # Indentation preferences csharp_indent_case_contents_when_block = false @@ -66,19 +131,44 @@ csharp_indent_case_contents_when_block = false csharp_preserve_single_line_statements = false # 'var' usage preferences -csharp_style_var_for_built_in_types = false:none -csharp_style_var_when_type_is_apparent = true:none -csharp_style_var_elsewhere = false:none +csharp_style_var_for_built_in_types = false:silent +csharp_style_var_when_type_is_apparent = true:silent +csharp_style_var_elsewhere = false:silent +# IDE0007: Use var instead of explicit type +dotnet_diagnostic.IDE0007.severity = silent +# IDE0008: Use explicit type instead of var +dotnet_diagnostic.IDE0008.severity = silent # Parentheses preferences -dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:suggestion -dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:suggestion -dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:suggestion - -# Expression value is never used -dotnet_diagnostic.IDE0058.severity = none - -#### Naming Style #### +dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:silent +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:silent +# IDE0047: Remove unnecessary parentheses +dotnet_diagnostic.IDE0047.severity = silent +# IDE0048: Add parentheses for clarity +dotnet_diagnostic.IDE0048.severity = silent + +# Switch preferences +# IDE0010: Add missing cases to switch statement +dotnet_diagnostic.IDE0010.severity = silent +# IDE0072: Add missing cases to switch expression +dotnet_diagnostic.IDE0072.severity = silent + +# Null check preferences +# IDE0029: Null check can be simplified +dotnet_diagnostic.IDE0029.severity = silent +# IDE0030: Null check can be simplified +dotnet_diagnostic.IDE0030.severity = silent +# IDE0270: Null check can be simplified +dotnet_diagnostic.IDE0270.severity = silent + +# JSON002: Probable JSON string detected +dotnet_diagnostic.JSON002.severity = silent + +# CA1062: Validate arguments of public methods +dotnet_code_quality.CA1062.excluded_symbol_names = Accept|DefaultVisit|Visit*|Apply* + +#### .NET Naming Style #### dotnet_diagnostic.IDE1006.severity = warning diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md index 2520ad8..fcff5ed 100644 --- a/.github/ISSUE_TEMPLATE/question.md +++ b/.github/ISSUE_TEMPLATE/question.md @@ -8,7 +8,7 @@ assignees: '' --- #### SUMMARY diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 75cb869..e6a9569 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,7 +21,7 @@ concurrency: cancel-in-progress: true env: - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true + DOTNET_NOLOGO: true DOTNET_CLI_TELEMETRY_OPTOUT: true jobs: @@ -41,8 +41,8 @@ jobs: uses: actions/setup-dotnet@v4 with: dotnet-version: | - 6.0.x - 8.0.x + 8.0.* + 9.0.* - name: Show installed versions shell: pwsh run: | @@ -66,14 +66,14 @@ jobs: $versionSuffix = $segments.Length -eq 1 ? '' : $segments[1..$($segments.Length - 1)] -join '-' [xml]$xml = Get-Content Directory.Build.props - $configuredVersionPrefix = $xml.Project.PropertyGroup.JsonApiDotNetCoreMongoDbVersionPrefix | Select-Object -First 1 + $configuredVersionPrefix = $xml.Project.PropertyGroup.VersionPrefix | Select-Object -First 1 if ($configuredVersionPrefix -ne $versionPrefix) { Write-Error "Version prefix from git release tag '$versionPrefix' does not match version prefix '$configuredVersionPrefix' stored in Directory.Build.props." # To recover from this: # - Delete the GitHub release # - Run: git push --delete origin the-invalid-tag-name - # - Adjust JsonApiDotNetCoreVersionPrefix in Directory.Build.props, commit and push + # - Adjust VersionPrefix in Directory.Build.props, commit and push # - Recreate the GitHub release } } @@ -97,7 +97,7 @@ jobs: if: matrix.os == 'ubuntu-latest' env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 with: fail_ci_if_error: true verbose: true @@ -128,8 +128,8 @@ jobs: uses: actions/setup-dotnet@v4 with: dotnet-version: | - 6.0.x - 8.0.x + 8.0.* + 9.0.* - name: Git checkout uses: actions/checkout@v4 - name: Restore tools @@ -183,8 +183,8 @@ jobs: uses: actions/setup-dotnet@v4 with: dotnet-version: | - 6.0.x - 8.0.x + 8.0.* + 9.0.* - name: Git checkout uses: actions/checkout@v4 with: @@ -234,6 +234,14 @@ jobs: run: | dotnet nuget add source --username 'json-api-dotnet' --password "$env:GITHUB_TOKEN" --store-password-in-clear-text --name 'github' 'https://nuget.pkg.github.com/json-api-dotnet/index.json' dotnet nuget push "$env:GITHUB_WORKSPACE/packages/*.nupkg" --api-key "$env:GITHUB_TOKEN" --source 'github' + - name: Publish to feedz.io + if: github.event_name == 'push' || github.event_name == 'release' + env: + FEEDZ_IO_API_KEY: ${{ secrets.FEEDZ_IO_API_KEY }} + shell: pwsh + run: | + dotnet nuget add source --name 'feedz-io' 'https://f.feedz.io/json-api-dotnet/jsonapidotnetcore/nuget/index.json' + dotnet nuget push "$env:GITHUB_WORKSPACE/packages/*.nupkg" --api-key "$env:FEEDZ_IO_API_KEY" --source 'feedz-io' - name: Publish to NuGet if: github.event_name == 'release' && startsWith(github.ref, 'refs/tags/v') env: diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index eb03757..d3d4db6 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -27,8 +27,8 @@ jobs: uses: actions/setup-dotnet@v4 with: dotnet-version: | - 6.0.x - 8.0.x + 8.0.* + 9.0.* - name: Git checkout uses: actions/checkout@v4 - name: Initialize CodeQL diff --git a/Build.ps1 b/Build.ps1 index 3abc926..6c6ff9c 100644 --- a/Build.ps1 +++ b/Build.ps1 @@ -1,5 +1,3 @@ -$versionSuffix="pre" - function VerifySuccessExitCode { if ($LastExitCode -ne 0) { throw "Command failed with exit code $LastExitCode." @@ -16,14 +14,14 @@ Remove-Item -Recurse -Force * -Include coverage.cobertura.xml dotnet tool restore VerifySuccessExitCode -dotnet build --configuration Release /p:VersionSuffix=$versionSuffix +dotnet build --configuration Release VerifySuccessExitCode -dotnet test --no-build --configuration Release --collect:"XPlat Code Coverage" +dotnet test --no-build --configuration Release --verbosity quiet --collect:"XPlat Code Coverage" VerifySuccessExitCode dotnet reportgenerator -reports:**\coverage.cobertura.xml -targetdir:artifacts\coverage -filefilters:-*.g.cs VerifySuccessExitCode -dotnet pack --no-build --configuration Release --output artifacts/packages /p:VersionSuffix=$versionSuffix +dotnet pack --no-build --configuration Release --output artifacts/packages VerifySuccessExitCode diff --git a/CodingGuidelines.ruleset b/CodingGuidelines.ruleset index e647ad9..b29d742 100644 --- a/CodingGuidelines.ruleset +++ b/CodingGuidelines.ruleset @@ -1,32 +1,54 @@  - + + + - - - - - - + + - - - + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Directory.Build.props b/Directory.Build.props index 0e7e56e..86f7636 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,4 +1,31 @@ + + enable + latest + enable + false + false + true + Recommended + $(MSBuildThisFileDirectory)CodingGuidelines.ruleset + $(MSBuildThisFileDirectory)tests.runsettings + 5.7.1 + pre + direct + + + + + IDE0028;IDE0300;IDE0301;IDE0302;IDE0303;IDE0304;IDE0305;IDE0306 + $(NoWarn);$(UseCollectionExpressionRules) + + $(NoWarn);AV2210 @@ -13,20 +40,17 @@ true + + $(NoWarn);CA1707;CA1062 + + + + $(NoWarn);CA1062 + + - + - - - enable - latest - enable - false - false - $(MSBuildThisFileDirectory)CodingGuidelines.ruleset - $(MSBuildThisFileDirectory)tests.runsettings - 5.6.1 - diff --git a/JsonApiDotNetCore.MongoDb.sln.DotSettings b/JsonApiDotNetCore.MongoDb.sln.DotSettings index 2cd13da..cf02da1 100644 --- a/JsonApiDotNetCore.MongoDb.sln.DotSettings +++ b/JsonApiDotNetCore.MongoDb.sln.DotSettings @@ -1,19 +1,9 @@  - // Use the following placeholders: -// $EXPR$ -- source expression -// $NAME$ -- source name (string literal or 'nameof' expression) -// $MESSAGE$ -- string literal in the form of "$NAME$ != null" -JsonApiDotNetCore.MongoDb.ArgumentGuard.NotNull($EXPR$); - 199 - 5000 - 99 - 100 - 200 - 1000 - 500 + 5000 + 2000 3000 - 50 False + FD312677-2A62-4B8F-A965-879B059F1755/f:ReadOnlySet.cs SOLUTION True True @@ -81,6 +71,7 @@ JsonApiDotNetCore.MongoDb.ArgumentGuard.NotNull($EXPR$); SUGGESTION SUGGESTION WARNING + DO_NOT_SHOW WARNING WARNING WARNING @@ -99,6 +90,7 @@ JsonApiDotNetCore.MongoDb.ArgumentGuard.NotNull($EXPR$); WARNING True SUGGESTION + False <?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" ArrangeNullCheckingPattern="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><CSReformatInactiveBranches>True</CSReformatInactiveBranches></Profile> JADNC Full Cleanup Required @@ -137,6 +129,7 @@ JsonApiDotNetCore.MongoDb.ArgumentGuard.NotNull($EXPR$); NEVER False NEVER + False False False NEVER @@ -595,11 +588,12 @@ JsonApiDotNetCore.MongoDb.ArgumentGuard.NotNull($EXPR$); True True True + True True True True True - Replace argument null check using throw expression with Guard clause + Replace argument null check using throw expression with ArgumentNullException.ThrowIfNull True True False @@ -618,13 +612,12 @@ JsonApiDotNetCore.MongoDb.ArgumentGuard.NotNull($EXPR$); True CSHARP False - Replace argument null check with Guard clause - JsonApiDotNetCore.MongoDb.ArgumentGuard.NotNull($argument$); + System.ArgumentNullException.ThrowIfNull($argument$); $left$ = $right$; $left$ = $right$ ?? throw new ArgumentNullException(nameof($argument$)); WARNING True - Replace argument == null check with Guard clause + Replace argument == null check with ArgumentNullException.ThrowIfNull True True False @@ -633,8 +626,7 @@ $left$ = $right$; True CSHARP False - Replace argument null check with Guard clause - JsonApiDotNetCore.MongoDb.ArgumentGuard.NotNull($argument$); + System.ArgumentNullException.ThrowIfNull($argument$); if ($argument$ == null) throw new ArgumentNullException(nameof($argument$)); WARNING True @@ -646,12 +638,11 @@ $left$ = $right$; True CSHARP False - Replace collection null/empty check with extension method $collection$.IsNullOrEmpty() $collection$ == null || !$collection$.Any() WARNING True - Replace argument is null check with Guard clause + Replace argument is null check with ArgumentNullException.ThrowIfNull True True False @@ -660,7 +651,7 @@ $left$ = $right$; True CSHARP False - JsonApiDotNetCore.MongoDb.ArgumentGuard.NotNull($argument$); + System.ArgumentNullException.ThrowIfNull($argument$); if ($argument$ is null) throw new ArgumentNullException(nameof($argument$)); WARNING True diff --git a/LICENSE b/LICENSE index 7bfce65..54cf85b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,5 @@ Copyright (c) 2020 Alvaro Nicoli +Copyright (c) 2021 Bart Koelman MIT License diff --git a/PackageReadme.md b/PackageReadme.md index 6c9ce84..a2889e6 100644 --- a/PackageReadme.md +++ b/PackageReadme.md @@ -1 +1 @@ -Persistence layer implementation for use of [MongoDB](https://www.mongodb.com/) in APIs using [JsonApiDotNetCore](https://www.jsonapi.net/). +[MongoDB](https://www.mongodb.com/) persistence for [JsonApiDotNetCore](https://www.jsonapi.net/), which is a framework for building JSON:API compliant REST APIs using ASP.NET Core. diff --git a/README.md b/README.md index 1a33889..7a0e52d 100644 --- a/README.md +++ b/README.md @@ -1,118 +1,181 @@ # MongoDB support for JsonApiDotNetCore -Plug-n-play implementation of `IResourceRepository` allowing you to use [MongoDB](https://www.mongodb.com/) with your [JsonApiDotNetCore](https://github.com/json-api-dotnet/JsonApiDotNetCore) APIs. - [![Build](https://github.com/json-api-dotnet/JsonApiDotNetCore.MongoDb/actions/workflows/build.yml/badge.svg?branch=master)](https://github.com/json-api-dotnet/JsonApiDotNetCore.MongoDb/actions/workflows/build.yml?query=branch%3Amaster) [![Coverage](https://codecov.io/gh/json-api-dotnet/JsonApiDotNetCore.MongoDb/branch/master/graph/badge.svg?token=QPVf8rii7l)](https://codecov.io/gh/json-api-dotnet/JsonApiDotNetCore.MongoDb) [![NuGet](https://img.shields.io/nuget/v/JsonApiDotNetCore.MongoDb.svg)](https://www.nuget.org/packages/JsonApiDotNetCore.MongoDb/) +[![GitHub License](https://img.shields.io/github/license/json-api-dotnet/JsonApiDotNetCore.MongoDb)](LICENSE) -## Installation and Usage - -```bash -dotnet add package JsonApiDotNetCore.MongoDb -``` - -### Models - -```c# -#nullable enable - -[Resource] -public class Book : HexStringMongoIdentifiable -{ - [Attr] - public string Name { get; set; } = null!; -} -``` - -### Middleware - -```c# -// Program.cs +Plug-n-play implementation of `IResourceRepository`, allowing you to use [MongoDB](https://www.mongodb.com/) with your [JsonApiDotNetCore](https://github.com/json-api-dotnet/JsonApiDotNetCore) API projects. -#nullable enable +## Getting started -WebApplicationBuilder builder = WebApplication.CreateBuilder(args); +The following steps describe how to create a JSON:API project with MongoDB. -// Add services to the container. - -builder.Services.AddSingleton(_ => -{ - var client = new MongoClient("mongodb://localhost:27017"); - return client.GetDatabase("ExampleDbName"); -}); - -builder.Services.AddJsonApi(resources: resourceGraphBuilder => -{ - resourceGraphBuilder.Add(); -}); - -builder.Services.AddJsonApiMongoDb(); +1. Install the JsonApiDotNetCore.MongoDb package: + ```bash + dotnet add package JsonApiDotNetCore.MongoDb + ``` -builder.Services.AddResourceRepository>(); +1. Declare your entities, annotated with JsonApiDotNetCore attributes: + ```c# + #nullable enable -// Configure the HTTP request pipeline. + [Resource] + public class Person : HexStringMongoIdentifiable + { + [Attr] public string? FirstName { get; set; } + [Attr] public string LastName { get; set; } = null!; + } + ``` -app.UseRouting(); -app.UseJsonApi(); -app.MapControllers(); +1. Configure MongoDB and JsonApiDotNetCore in `Program.cs`, seeding the database with sample data: + ```c# + var builder = WebApplication.CreateBuilder(args); + builder.Services.AddSingleton(_ => new MongoClient("mongodb://localhost:27017").GetDatabase("ExampleDbName")); + builder.Services.AddJsonApi(options => + { + options.UseRelativeLinks = true; + options.IncludeTotalResourceCount = true; + }, resources: resourceGraphBuilder => resourceGraphBuilder.Add()); + builder.Services.AddJsonApiMongoDb(); + builder.Services.AddResourceRepository>(); + + var app = builder.Build(); + app.UseRouting(); + app.UseJsonApi(); + app.MapControllers(); + + var database = app.Services.GetRequiredService(); + await CreateSampleDataAsync(database); + + app.Run(); + + static async Task CreateSampleDataAsync(IMongoDatabase database) + { + await database.DropCollectionAsync(nameof(Person)); + await database.GetCollection(nameof(Person)).InsertManyAsync(new[] + { + new Person + { + FirstName = "John", + LastName = "Doe", + }, + new Person + { + FirstName = "Jane", + LastName = "Doe", + }, + new Person + { + FirstName = "John", + LastName = "Smith", + } + }); + } + ``` -app.Run(); -``` + > [!TIP] + > If your API project uses MongoDB only (so not in combination with EF Core), then instead of + > registering all MongoDB resources and repositories individually, you can use: + > + > ```c# + > builder.Services.AddJsonApi(facade => facade.AddCurrentAssembly()); + > builder.Services.AddJsonApiMongoDb(); + > + > builder.Services.AddScoped(typeof(IResourceReadRepository<,>), typeof(MongoRepository<,>)); + > builder.Services.AddScoped(typeof(IResourceWriteRepository<,>), typeof(MongoRepository<,>)); + > builder.Services.AddScoped(typeof(IResourceRepository<,>), typeof(MongoRepository<,>)); + > ``` + +1. Start your API + ```bash + dotnet run + ``` -Note: If your API project uses MongoDB only (so not in combination with EF Core), then instead of -registering all MongoDB resources and repositories individually, you can use: +1. Send a GET request to retrieve data: + ```bash + GET http://localhost:5000/people?filter=equals(lastName,'Doe')&fields[people]=firstName HTTP/1.1 + ``` -```c# -builder.Services.AddJsonApi(facade => facade.AddCurrentAssembly()); -builder.Services.AddJsonApiMongoDb(); +
+ Expand to view the JSON response + + ```json + { + "links": { + "self": "/people?filter=equals(lastName,%27Doe%27)&fields[people]=firstName", + "first": "/people?filter=equals(lastName,%27Doe%27)&fields%5Bpeople%5D=firstName", + "last": "/people?filter=equals(lastName,%27Doe%27)&fields%5Bpeople%5D=firstName" + }, + "data": [ + { + "type": "people", + "id": "680cae2e1759666c5c1e988c", + "attributes": { + "firstName": "John" + }, + "links": { + "self": "/people/680cae2e1759666c5c1e988c" + } + }, + { + "type": "people", + "id": "680cae2e1759666c5c1e988d", + "attributes": { + "firstName": "Jane" + }, + "links": { + "self": "/people/680cae2e1759666c5c1e988d" + } + } + ], + "meta": { + "total": 2 + } + } + ``` -builder.Services.AddScoped(typeof(IResourceReadRepository<,>), typeof(MongoRepository<,>)); -builder.Services.AddScoped(typeof(IResourceWriteRepository<,>), typeof(MongoRepository<,>)); -builder.Services.AddScoped(typeof(IResourceRepository<,>), typeof(MongoRepository<,>)); -``` +
## Using client-generated IDs -Resources that inherit from `HexStringMongoIdentifiable` use auto-generated (performant) 12-byte hexadecimal + +Resources that inherit from `HexStringMongoIdentifiable` use auto-generated (high-performance) 12-byte hexadecimal [Object IDs](https://docs.mongodb.com/manual/reference/bson-types/#objectid). -You can assign an ID manually, but it must match the 12-byte hexadecimal pattern. +You can assign an ID explicitly, but it must match the 12-byte hexadecimal pattern. -To assign free-format string IDs manually, make your resources inherit from `FreeStringMongoIdentifiable` instead. +To use free-format string IDs, make your resources inherit from `FreeStringMongoIdentifiable` instead. When creating a resource without assigning an ID, a 12-byte hexadecimal ID will be auto-generated. -Set `options.AllowClientGeneratedIds` to `true` in Program.cs to allow API clients to assign IDs. This can be combined +Set `options.ClientIdGeneration` to `Allowed` or `Required` from `Program.cs` to enable API clients to assign IDs. This can be combined with both base classes, but `FreeStringMongoIdentifiable` probably makes the most sense. ## Limitations -- JSON:API relationships are [currently not supported](https://github.com/json-api-dotnet/JsonApiDotNetCore.MongoDb/issues/73). You can use complex object graphs though, which are stored in a single document. - -## Contributing - -Have a question, found a bug or want to submit code changes? See our [contributing guidelines](https://github.com/json-api-dotnet/JsonApiDotNetCore/blob/master/.github/CONTRIBUTING.md). +- JSON:API relationships are [currently not supported](https://github.com/json-api-dotnet/JsonApiDotNetCore.MongoDb/issues/73). You *can* use complex object graphs though, which are stored in a single document. ## Trying out the latest build -After each commit to the master branch, a new pre-release NuGet package is automatically published to [GitHub Packages](https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-nuget-registry). +After each commit to the master branch, a new pre-release NuGet package is automatically published to [feedz.io](https://feedz.io/docs/package-types/nuget). To try it out, follow the steps below: -1. [Create a Personal Access Token (classic)](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-personal-access-token-classic) with at least `read:packages` scope. -1. Add our package source to your local user-specific `nuget.config` file by running: - ```bash - dotnet nuget add source https://nuget.pkg.github.com/json-api-dotnet/index.json --name github-json-api --username YOUR-GITHUB-USERNAME --password YOUR-PAT-CLASSIC +1. Create a `nuget.config` file in the same directory as your .sln file, with the following contents: + ```xml + + + + + + + ``` - In the command above: - - Replace YOUR-GITHUB-USERNAME with the username you use to login your GitHub account. - - Replace YOUR-PAT-CLASSIC with the token your created above. - - :warning: If the above command doesn't give you access in the next step, remove the package source by running: - ```bash - dotnet nuget remove source github-json-api - ``` - and retry with the `--store-password-in-clear-text` switch added. -1. Restart your IDE, open your project, and browse the list of packages from the github-json-api feed (make sure pre-release packages are included). -## Development +1. In your IDE, browse the list of packages from the `json-api-dotnet` feed. Make sure pre-release packages are included in the list. + +## Contributing + +Have a question, found a bug or want to submit code changes? See our [contributing guidelines](https://github.com/json-api-dotnet/JsonApiDotNetCore/blob/master/.github/CONTRIBUTING.md). + +## Build from source To build the code from this repository locally, run: @@ -120,13 +183,14 @@ To build the code from this repository locally, run: dotnet build ``` -You don't need to have a running instance of MongoDB on your machine to run tests. Just type the following command in your terminal: +You can run tests without MongoDB on your machine. The following command runs all tests: ```bash dotnet test ``` -If you want to run the examples and explore them on your own **you are** going to need that running instance of MongoDB. If you have docker installed you can launch it like this: +A running instance of MongoDB is required to run the examples. +If you have docker installed, you can launch MongoDB in a container with the following command: ```bash pwsh run-docker-mongodb.ps1 diff --git a/WarningSeverities.DotSettings b/WarningSeverities.DotSettings index 060df31..5b64971 100644 --- a/WarningSeverities.DotSettings +++ b/WarningSeverities.DotSettings @@ -1,4 +1,5 @@  + WARNING WARNING WARNING WARNING @@ -197,7 +198,6 @@ WARNING WARNING WARNING - WARNING WARNING WARNING WARNING diff --git a/package-versions.props b/package-versions.props index f0982cc..14ed6fe 100644 --- a/package-versions.props +++ b/package-versions.props @@ -5,25 +5,25 @@ 2.28.0 - 35.5.* + 35.6.* 6.0.* 2.0.* - 6.12.* - 2.3.* + 7.2.* + 2.4.* 2.0.* 2.28.* - 8.0.* 17.13.* - 2.8.* + 2.9.* + 2.8.* - + - 8.0.* + 9.0.* - + - 6.0.* + 8.0.*
diff --git a/src/Examples/GettingStarted/GettingStarted.csproj b/src/Examples/GettingStarted/GettingStarted.csproj index 0f6b40f..408a434 100644 --- a/src/Examples/GettingStarted/GettingStarted.csproj +++ b/src/Examples/GettingStarted/GettingStarted.csproj @@ -1,6 +1,6 @@ - net8.0;net6.0 + net9.0;net8.0 diff --git a/src/Examples/GettingStarted/Program.cs b/src/Examples/GettingStarted/Program.cs index 7deafcc..3a20376 100644 --- a/src/Examples/GettingStarted/Program.cs +++ b/src/Examples/GettingStarted/Program.cs @@ -15,7 +15,19 @@ return client.GetDatabase(builder.Configuration.GetSection("DatabaseSettings:Database").Value); }); -builder.Services.AddJsonApi(ConfigureJsonApiOptions, resources: resourceGraphBuilder => resourceGraphBuilder.Add()); +builder.Services.AddJsonApi(options => +{ + options.Namespace = "api"; + options.UseRelativeLinks = true; + options.IncludeTotalResourceCount = true; + +#if DEBUG + options.IncludeExceptionStackTraceInErrors = true; + options.IncludeRequestBodyInErrors = true; + options.SerializerOptions.WriteIndented = true; +#endif +}, resources: resourceGraphBuilder => resourceGraphBuilder.Add()); + builder.Services.AddJsonApiMongoDb(); builder.Services.AddResourceRepository>(); @@ -31,15 +43,7 @@ var database = app.Services.GetRequiredService(); await CreateSampleDataAsync(database); -app.Run(); - -static void ConfigureJsonApiOptions(JsonApiOptions options) -{ - options.Namespace = "api"; - options.UseRelativeLinks = true; - options.IncludeTotalResourceCount = true; - options.SerializerOptions.WriteIndented = true; -} +await app.RunAsync(); static async Task CreateSampleDataAsync(IMongoDatabase database) { diff --git a/src/Examples/GettingStarted/Properties/launchSettings.json b/src/Examples/GettingStarted/Properties/launchSettings.json index b82968b..55041b5 100644 --- a/src/Examples/GettingStarted/Properties/launchSettings.json +++ b/src/Examples/GettingStarted/Properties/launchSettings.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/launchsettings.json", + "$schema": "https://json.schemastore.org/launchsettings.json", "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, diff --git a/src/Examples/GettingStarted/README.md b/src/Examples/GettingStarted/README.md index aa8afe0..b076df4 100644 --- a/src/Examples/GettingStarted/README.md +++ b/src/Examples/GettingStarted/README.md @@ -7,8 +7,7 @@ You can verify the project is running by checking this endpoint: `localhost:24141/api/people` -For further documentation and implementation of a JsonApiDotNetCore Application see the documentation or GitHub page: +For further documentation and implementation of a JsonApiDotNetCore application, see the documentation or GitHub page: Repository: https://github.com/json-api-dotnet/JsonApiDotNetCore - -Documentation: http://www.jsonapi.net +Documentation: https://www.jsonapi.net diff --git a/src/Examples/JsonApiDotNetCoreMongoDbExample/Controllers/OperationsController.cs b/src/Examples/JsonApiDotNetCoreMongoDbExample/Controllers/OperationsController.cs index b748e26..a2a1ca7 100644 --- a/src/Examples/JsonApiDotNetCoreMongoDbExample/Controllers/OperationsController.cs +++ b/src/Examples/JsonApiDotNetCoreMongoDbExample/Controllers/OperationsController.cs @@ -8,5 +8,5 @@ namespace JsonApiDotNetCoreMongoDbExample.Controllers; public sealed class OperationsController( IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory, IOperationsProcessor processor, IJsonApiRequest request, - ITargetedFields targetedFields, IAtomicOperationFilter operationFilter) : JsonApiOperationsController(options, resourceGraph, loggerFactory, processor, - request, targetedFields, operationFilter); + ITargetedFields targetedFields, IAtomicOperationFilter operationFilter) + : JsonApiOperationsController(options, resourceGraph, loggerFactory, processor, request, targetedFields, operationFilter); diff --git a/src/Examples/JsonApiDotNetCoreMongoDbExample/Definitions/TodoItemDefinition.cs b/src/Examples/JsonApiDotNetCoreMongoDbExample/Definitions/TodoItemDefinition.cs index 7910a8e..96c21ee 100644 --- a/src/Examples/JsonApiDotNetCoreMongoDbExample/Definitions/TodoItemDefinition.cs +++ b/src/Examples/JsonApiDotNetCoreMongoDbExample/Definitions/TodoItemDefinition.cs @@ -5,27 +5,14 @@ using JsonApiDotNetCore.Queries.Expressions; using JsonApiDotNetCore.Resources; using JsonApiDotNetCoreMongoDbExample.Models; -#if NET6_0 -using Microsoft.AspNetCore.Authentication; -#endif namespace JsonApiDotNetCoreMongoDbExample.Definitions; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] -public sealed class TodoItemDefinition( - IResourceGraph resourceGraph, -#if NET6_0 - ISystemClock systemClock -#else - TimeProvider timeProvider -#endif -) : JsonApiResourceDefinition(resourceGraph) +public sealed class TodoItemDefinition(IResourceGraph resourceGraph, TimeProvider timeProvider) + : JsonApiResourceDefinition(resourceGraph) { -#if NET6_0 - private readonly Func _getUtcNow = () => systemClock.UtcNow; -#else - private readonly Func _getUtcNow = timeProvider.GetUtcNow; -#endif + private readonly TimeProvider _timeProvider = timeProvider; public override SortExpression OnApplySort(SortExpression? existingSort) { @@ -44,11 +31,11 @@ public override Task OnWritingAsync(TodoItem resource, WriteOperationKind writeO { if (writeOperation == WriteOperationKind.CreateResource) { - resource.CreatedAt = _getUtcNow(); + resource.CreatedAt = _timeProvider.GetUtcNow(); } else if (writeOperation == WriteOperationKind.UpdateResource) { - resource.LastModifiedAt = _getUtcNow(); + resource.LastModifiedAt = _timeProvider.GetUtcNow(); } return Task.CompletedTask; diff --git a/src/Examples/JsonApiDotNetCoreMongoDbExample/JsonApiDotNetCoreMongoDbExample.csproj b/src/Examples/JsonApiDotNetCoreMongoDbExample/JsonApiDotNetCoreMongoDbExample.csproj index 0f6b40f..408a434 100644 --- a/src/Examples/JsonApiDotNetCoreMongoDbExample/JsonApiDotNetCoreMongoDbExample.csproj +++ b/src/Examples/JsonApiDotNetCoreMongoDbExample/JsonApiDotNetCoreMongoDbExample.csproj @@ -1,6 +1,6 @@ - net8.0;net6.0 + net9.0;net8.0 diff --git a/src/Examples/JsonApiDotNetCoreMongoDbExample/Program.cs b/src/Examples/JsonApiDotNetCoreMongoDbExample/Program.cs index 95ec278..ff216b8 100644 --- a/src/Examples/JsonApiDotNetCoreMongoDbExample/Program.cs +++ b/src/Examples/JsonApiDotNetCoreMongoDbExample/Program.cs @@ -6,9 +6,6 @@ using JsonApiDotNetCore.Repositories; using Microsoft.Extensions.DependencyInjection.Extensions; using MongoDB.Driver; -#if NET6_0 -using Microsoft.AspNetCore.Authentication; -#endif [assembly: ExcludeFromCodeCoverage] @@ -16,11 +13,7 @@ // Add services to the container. -#if NET6_0 -builder.Services.TryAddSingleton(); -#else builder.Services.TryAddSingleton(TimeProvider.System); -#endif builder.Services.TryAddSingleton(_ => { @@ -43,7 +36,7 @@ app.UseJsonApi(); app.MapControllers(); -app.Run(); +await app.RunAsync(); static void ConfigureJsonApiOptions(JsonApiOptions options) { diff --git a/src/Examples/JsonApiDotNetCoreMongoDbExample/Properties/launchSettings.json b/src/Examples/JsonApiDotNetCoreMongoDbExample/Properties/launchSettings.json index c14bdd1..efcce2e 100644 --- a/src/Examples/JsonApiDotNetCoreMongoDbExample/Properties/launchSettings.json +++ b/src/Examples/JsonApiDotNetCoreMongoDbExample/Properties/launchSettings.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/launchsettings.json", + "$schema": "https://json.schemastore.org/launchsettings.json", "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, diff --git a/src/JsonApiDotNetCore.MongoDb/ArgumentGuard.cs b/src/JsonApiDotNetCore.MongoDb/ArgumentGuard.cs deleted file mode 100644 index 5063b63..0000000 --- a/src/JsonApiDotNetCore.MongoDb/ArgumentGuard.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Runtime.CompilerServices; -using JetBrains.Annotations; -using SysNotNull = System.Diagnostics.CodeAnalysis.NotNullAttribute; - -#pragma warning disable AV1008 // Class should not be static - -namespace JsonApiDotNetCore.MongoDb; - -internal static class ArgumentGuard -{ - [AssertionMethod] - public static void NotNull([NoEnumeration] [SysNotNull] T? value, [CallerArgumentExpression("value")] string? parameterName = null) - where T : class - { - ArgumentNullException.ThrowIfNull(value, parameterName); - } -} diff --git a/src/JsonApiDotNetCore.MongoDb/AtomicOperations/MongoTransaction.cs b/src/JsonApiDotNetCore.MongoDb/AtomicOperations/MongoTransaction.cs index 3514e87..0e19327 100644 --- a/src/JsonApiDotNetCore.MongoDb/AtomicOperations/MongoTransaction.cs +++ b/src/JsonApiDotNetCore.MongoDb/AtomicOperations/MongoTransaction.cs @@ -16,7 +16,7 @@ public sealed class MongoTransaction : IOperationsTransaction public MongoTransaction(IMongoDataAccess mongoDataAccess, bool ownsTransaction) { - ArgumentGuard.NotNull(mongoDataAccess); + ArgumentNullException.ThrowIfNull(mongoDataAccess); _mongoDataAccess = mongoDataAccess; _ownsTransaction = ownsTransaction; diff --git a/src/JsonApiDotNetCore.MongoDb/AtomicOperations/MongoTransactionFactory.cs b/src/JsonApiDotNetCore.MongoDb/AtomicOperations/MongoTransactionFactory.cs index b51e1a1..d016781 100644 --- a/src/JsonApiDotNetCore.MongoDb/AtomicOperations/MongoTransactionFactory.cs +++ b/src/JsonApiDotNetCore.MongoDb/AtomicOperations/MongoTransactionFactory.cs @@ -12,7 +12,7 @@ public sealed class MongoTransactionFactory : IOperationsTransactionFactory public MongoTransactionFactory(IMongoDataAccess mongoDataAccess) { - ArgumentGuard.NotNull(mongoDataAccess); + ArgumentNullException.ThrowIfNull(mongoDataAccess); _mongoDataAccess = mongoDataAccess; } diff --git a/src/JsonApiDotNetCore.MongoDb/Configuration/ResourceGraphExtensions.cs b/src/JsonApiDotNetCore.MongoDb/Configuration/ResourceGraphExtensions.cs index af84a69..5ad31b2 100644 --- a/src/JsonApiDotNetCore.MongoDb/Configuration/ResourceGraphExtensions.cs +++ b/src/JsonApiDotNetCore.MongoDb/Configuration/ResourceGraphExtensions.cs @@ -11,6 +11,8 @@ internal static class ResourceGraphExtensions { public static IReadOnlyModel ToEntityModel(this IResourceGraph resourceGraph) { + ArgumentNullException.ThrowIfNull(resourceGraph); + var modelBuilder = new ModelBuilder(); foreach (ResourceType resourceType in resourceGraph.GetResourceTypes()) diff --git a/src/JsonApiDotNetCore.MongoDb/Configuration/ServiceCollectionExtensions.cs b/src/JsonApiDotNetCore.MongoDb/Configuration/ServiceCollectionExtensions.cs index 48278f0..a49d77e 100644 --- a/src/JsonApiDotNetCore.MongoDb/Configuration/ServiceCollectionExtensions.cs +++ b/src/JsonApiDotNetCore.MongoDb/Configuration/ServiceCollectionExtensions.cs @@ -2,7 +2,7 @@ using JsonApiDotNetCore.AtomicOperations; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.MongoDb.AtomicOperations; -using JsonApiDotNetCore.MongoDb.Queries.Internal; +using JsonApiDotNetCore.MongoDb.Queries; using JsonApiDotNetCore.MongoDb.Repositories; using JsonApiDotNetCore.Queries; using Microsoft.Extensions.DependencyInjection; @@ -18,6 +18,8 @@ public static class ServiceCollectionExtensions [PublicAPI] public static IServiceCollection AddJsonApiMongoDb(this IServiceCollection services) { + ArgumentNullException.ThrowIfNull(services); + services.TryAddSingleton(serviceProvider => { var resourceGraph = serviceProvider.GetRequiredService(); diff --git a/src/JsonApiDotNetCore.MongoDb/Errors/AttributeComparisonInFilterNotSupportedException.cs b/src/JsonApiDotNetCore.MongoDb/Errors/AttributeComparisonInFilterNotSupportedException.cs index 55c865a..51c7131 100644 --- a/src/JsonApiDotNetCore.MongoDb/Errors/AttributeComparisonInFilterNotSupportedException.cs +++ b/src/JsonApiDotNetCore.MongoDb/Errors/AttributeComparisonInFilterNotSupportedException.cs @@ -10,7 +10,8 @@ namespace JsonApiDotNetCore.MongoDb.Errors; /// https://jira.mongodb.org/browse/CSHARP-1592. /// [PublicAPI] -public sealed class AttributeComparisonInFilterNotSupportedException() : JsonApiException(new ErrorObject(HttpStatusCode.BadRequest) -{ - Title = "Comparing attributes against each other is not supported when using MongoDB." -}); +public sealed class AttributeComparisonInFilterNotSupportedException() + : JsonApiException(new ErrorObject(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 01852e5..b1401c2 100644 --- a/src/JsonApiDotNetCore.MongoDb/Errors/UnsupportedRelationshipException.cs +++ b/src/JsonApiDotNetCore.MongoDb/Errors/UnsupportedRelationshipException.cs @@ -9,7 +9,8 @@ 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(new ErrorObject(HttpStatusCode.BadRequest) -{ - Title = "Relationships are not supported when using MongoDB." -}); +public sealed class UnsupportedRelationshipException() + : JsonApiException(new ErrorObject(HttpStatusCode.BadRequest) + { + Title = "Relationships are not supported when using MongoDB." + }); diff --git a/src/JsonApiDotNetCore.MongoDb/JsonApiDotNetCore.MongoDb.csproj b/src/JsonApiDotNetCore.MongoDb/JsonApiDotNetCore.MongoDb.csproj index 354b32a..13aa46a 100644 --- a/src/JsonApiDotNetCore.MongoDb/JsonApiDotNetCore.MongoDb.csproj +++ b/src/JsonApiDotNetCore.MongoDb/JsonApiDotNetCore.MongoDb.csproj @@ -1,6 +1,6 @@ - + - net8.0;net6.0 + net8.0 true true @@ -8,9 +8,8 @@ - $(JsonApiDotNetCoreMongoDbVersionPrefix) jsonapi;json:api;dotnet;asp.net;rest;web-api;MongoDB - Persistence layer implementation for use of MongoDB in APIs using JsonApiDotNetCore. + MongoDB persistence for JsonApiDotNetCore, which is a framework for building JSON:API compliant REST APIs using ASP.NET Core. json-api-dotnet https://www.jsonapi.net/ MIT @@ -19,7 +18,6 @@ package-icon.png PackageReadme.md true - true embedded @@ -34,7 +32,6 @@ - diff --git a/src/JsonApiDotNetCore.MongoDb/PolyfillCollectionExtensions.cs b/src/JsonApiDotNetCore.MongoDb/PolyfillCollectionExtensions.cs new file mode 100644 index 0000000..06adff6 --- /dev/null +++ b/src/JsonApiDotNetCore.MongoDb/PolyfillCollectionExtensions.cs @@ -0,0 +1,12 @@ +using System.Collections.ObjectModel; + +namespace JsonApiDotNetCore.MongoDb; + +// These methods provide polyfills for lower .NET versions. +internal static class PolyfillCollectionExtensions +{ + public static IReadOnlySet AsReadOnly(this HashSet source) + { + return new ReadOnlySet(source); + } +} diff --git a/src/JsonApiDotNetCore.MongoDb/Properties/AssemblyInfo.cs b/src/JsonApiDotNetCore.MongoDb/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..58b7ea5 --- /dev/null +++ b/src/JsonApiDotNetCore.MongoDb/Properties/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("TestBuildingBlocks")] diff --git a/src/JsonApiDotNetCore.MongoDb/Queries/Internal/HideRelationshipsSparseFieldSetCache.cs b/src/JsonApiDotNetCore.MongoDb/Queries/HideRelationshipsSparseFieldSetCache.cs similarity index 84% rename from src/JsonApiDotNetCore.MongoDb/Queries/Internal/HideRelationshipsSparseFieldSetCache.cs rename to src/JsonApiDotNetCore.MongoDb/Queries/HideRelationshipsSparseFieldSetCache.cs index aeebe64..ac5f45c 100644 --- a/src/JsonApiDotNetCore.MongoDb/Queries/Internal/HideRelationshipsSparseFieldSetCache.cs +++ b/src/JsonApiDotNetCore.MongoDb/Queries/HideRelationshipsSparseFieldSetCache.cs @@ -5,7 +5,7 @@ using JsonApiDotNetCore.Resources; using JsonApiDotNetCore.Resources.Annotations; -namespace JsonApiDotNetCore.MongoDb.Queries.Internal; +namespace JsonApiDotNetCore.MongoDb.Queries; /// public sealed class HideRelationshipsSparseFieldSetCache : ISparseFieldSetCache @@ -15,8 +15,8 @@ public sealed class HideRelationshipsSparseFieldSetCache : ISparseFieldSetCache public HideRelationshipsSparseFieldSetCache(IEnumerable constraintProviders, IResourceDefinitionAccessor resourceDefinitionAccessor) { - ArgumentGuard.NotNull(constraintProviders); - ArgumentGuard.NotNull(resourceDefinitionAccessor); + ArgumentNullException.ThrowIfNull(constraintProviders); + ArgumentNullException.ThrowIfNull(resourceDefinitionAccessor); _innerCache = new SparseFieldSetCache(constraintProviders, resourceDefinitionAccessor); } @@ -24,18 +24,24 @@ public HideRelationshipsSparseFieldSetCache(IEnumerable public IImmutableSet GetSparseFieldSetForQuery(ResourceType resourceType) { + ArgumentNullException.ThrowIfNull(resourceType); + return _innerCache.GetSparseFieldSetForQuery(resourceType); } /// public IImmutableSet GetIdAttributeSetForRelationshipQuery(ResourceType resourceType) { + ArgumentNullException.ThrowIfNull(resourceType); + return _innerCache.GetIdAttributeSetForRelationshipQuery(resourceType); } /// public IImmutableSet GetSparseFieldSetForSerializer(ResourceType resourceType) { + ArgumentNullException.ThrowIfNull(resourceType); + IImmutableSet fieldSet = _innerCache.GetSparseFieldSetForSerializer(resourceType); return resourceType.ClrType.IsAssignableTo(typeof(IMongoIdentifiable)) ? RemoveRelationships(fieldSet) : fieldSet; diff --git a/src/JsonApiDotNetCore.MongoDb/ReadOnlySet.cs b/src/JsonApiDotNetCore.MongoDb/ReadOnlySet.cs new file mode 100644 index 0000000..9917af9 --- /dev/null +++ b/src/JsonApiDotNetCore.MongoDb/ReadOnlySet.cs @@ -0,0 +1,171 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#if NET8_0 +#pragma warning disable + +// ReadOnlySet was introduced in .NET 9. +// This file was copied from https://github.com/dotnet/runtime/blob/release/9.0/src/libraries/System.Collections/src/System/Collections/Generic/ReadOnlySet.cs +// and made internal to enable usage on lower .NET versions. + +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; + +namespace System.Collections.ObjectModel; + +/// Represents a read-only, generic set of values. +/// The type of values in the set. +[DebuggerDisplay("Count = {Count}")] +[ExcludeFromCodeCoverage] +internal class ReadOnlySet : IReadOnlySet, ISet, ICollection +{ + /// The wrapped set. + private readonly ISet _set; + + /// Initializes a new instance of the class that is a wrapper around the specified set. + /// The set to wrap. + public ReadOnlySet(ISet set) + { + ArgumentNullException.ThrowIfNull(set); + _set = set; + } + + /// Gets an empty . + public static ReadOnlySet Empty { get; } = new ReadOnlySet(new HashSet()); + + /// Gets the set that is wrapped by this object. + protected ISet Set => _set; + + /// + public int Count => _set.Count; + + /// + public IEnumerator GetEnumerator() => + _set.Count == 0 ? ((IEnumerable)Array.Empty()).GetEnumerator() : + _set.GetEnumerator(); + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// + public bool Contains(T item) => _set.Contains(item); + + /// + public bool IsProperSubsetOf(IEnumerable other) => _set.IsProperSubsetOf(other); + + /// + public bool IsProperSupersetOf(IEnumerable other) => _set.IsProperSupersetOf(other); + + /// + public bool IsSubsetOf(IEnumerable other) => _set.IsSubsetOf(other); + + /// + public bool IsSupersetOf(IEnumerable other) => _set.IsSupersetOf(other); + + /// + public bool Overlaps(IEnumerable other) => _set.Overlaps(other); + + /// + public bool SetEquals(IEnumerable other) => _set.SetEquals(other); + + /// + void ICollection.CopyTo(T[] array, int arrayIndex) => _set.CopyTo(array, arrayIndex); + + /// + void ICollection.CopyTo(Array array, int index) => CollectionHelpers.CopyTo(_set, array, index); + + /// + bool ICollection.IsReadOnly => true; + + /// + bool ICollection.IsSynchronized => false; + + /// + object ICollection.SyncRoot => _set is ICollection c ? c.SyncRoot : this; + + /// + bool ISet.Add(T item) => throw new NotSupportedException(); + + /// + void ISet.ExceptWith(IEnumerable other) => throw new NotSupportedException(); + + /// + void ISet.IntersectWith(IEnumerable other) => throw new NotSupportedException(); + + /// + void ISet.SymmetricExceptWith(IEnumerable other) => throw new NotSupportedException(); + + /// + void ISet.UnionWith(IEnumerable other) => throw new NotSupportedException(); + + /// + void ICollection.Add(T item) => throw new NotSupportedException(); + + /// + void ICollection.Clear() => throw new NotSupportedException(); + + /// + bool ICollection.Remove(T item) => throw new NotSupportedException(); + + private static class CollectionHelpers + { + private static void ValidateCopyToArguments(int sourceCount, Array array, int index) + { + ArgumentNullException.ThrowIfNull(array); + + if (array.Rank != 1) + { + throw new ArgumentException("Only single dimensional arrays are supported for the requested action.", nameof(array)); + } + + if (array.GetLowerBound(0) != 0) + { + throw new ArgumentException("The lower bound of target array must be zero.", nameof(array)); + } + + ArgumentOutOfRangeException.ThrowIfNegative(index); + ArgumentOutOfRangeException.ThrowIfGreaterThan(index, array.Length); + + if (array.Length - index < sourceCount) + { + throw new ArgumentException("Destination array is not long enough to copy all the items in the collection. Check array index and length."); + } + } + + internal static void CopyTo(ICollection collection, Array array, int index) + { + ValidateCopyToArguments(collection.Count, array, index); + + if (collection is ICollection nonGenericCollection) + { + // Easy out if the ICollection implements the non-generic ICollection + nonGenericCollection.CopyTo(array, index); + } + else if (array is T[] items) + { + collection.CopyTo(items, index); + } + else + { + // We can't cast array of value type to object[], so we don't support widening of primitive types here. + if (array is not object?[] objects) + { + throw new ArgumentException("Target array type is not compatible with the type of items in the collection.", nameof(array)); + } + + try + { + foreach (T item in collection) + { + objects[index++] = item; + } + } + catch (ArrayTypeMismatchException) + { + throw new ArgumentException("Target array type is not compatible with the type of items in the collection.", nameof(array)); + } + } + } + } +} +#endif diff --git a/src/JsonApiDotNetCore.MongoDb/Repositories/MongoDataAccess.cs b/src/JsonApiDotNetCore.MongoDb/Repositories/MongoDataAccess.cs index 018b0a4..a92db8c 100644 --- a/src/JsonApiDotNetCore.MongoDb/Repositories/MongoDataAccess.cs +++ b/src/JsonApiDotNetCore.MongoDb/Repositories/MongoDataAccess.cs @@ -20,8 +20,8 @@ public sealed class MongoDataAccess : IMongoDataAccess public MongoDataAccess(IReadOnlyModel entityModel, IMongoDatabase mongoDatabase) { - ArgumentGuard.NotNull(entityModel); - ArgumentGuard.NotNull(mongoDatabase); + ArgumentNullException.ThrowIfNull(entityModel); + ArgumentNullException.ThrowIfNull(mongoDatabase); EntityModel = entityModel; MongoDatabase = mongoDatabase; diff --git a/src/JsonApiDotNetCore.MongoDb/Repositories/MongoQueryExpressionValidator.cs b/src/JsonApiDotNetCore.MongoDb/Repositories/MongoQueryExpressionValidator.cs index fe8ce6c..c1deeed 100644 --- a/src/JsonApiDotNetCore.MongoDb/Repositories/MongoQueryExpressionValidator.cs +++ b/src/JsonApiDotNetCore.MongoDb/Repositories/MongoQueryExpressionValidator.cs @@ -10,9 +10,9 @@ internal sealed class MongoQueryExpressionValidator : QueryExpressionRewriter 0; if (hasIncludes || HasSparseRelationshipSets(layer.Selection)) { @@ -52,7 +52,7 @@ private void ValidateExpression(QueryExpression? expression) public override QueryExpression VisitResourceFieldChain(ResourceFieldChainExpression expression, object? argument) { - if (expression.Fields.Count > 1 || expression.Fields.First() is RelationshipAttribute) + if (expression.Fields.Count > 1 || expression.Fields[0] is RelationshipAttribute) { throw new UnsupportedRelationshipException(); } diff --git a/src/JsonApiDotNetCore.MongoDb/Repositories/MongoRepository.cs b/src/JsonApiDotNetCore.MongoDb/Repositories/MongoRepository.cs index 579531c..0221b00 100644 --- a/src/JsonApiDotNetCore.MongoDb/Repositories/MongoRepository.cs +++ b/src/JsonApiDotNetCore.MongoDb/Repositories/MongoRepository.cs @@ -1,3 +1,4 @@ +using System.Data; using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; using JetBrains.Annotations; @@ -34,7 +35,7 @@ public class MongoRepository : IResourceRepository _constraintProviders; + private readonly IQueryConstraintProvider[] _constraintProviders; private readonly IResourceDefinitionAccessor _resourceDefinitionAccessor; private readonly IQueryableBuilder _queryableBuilder; @@ -46,19 +47,19 @@ public class MongoRepository : IResourceRepository constraintProviders, IResourceDefinitionAccessor resourceDefinitionAccessor, IQueryableBuilder queryableBuilder) { - ArgumentGuard.NotNull(mongoDataAccess); - ArgumentGuard.NotNull(targetedFields); - ArgumentGuard.NotNull(resourceGraph); - ArgumentGuard.NotNull(resourceFactory); - ArgumentGuard.NotNull(constraintProviders); - ArgumentGuard.NotNull(resourceDefinitionAccessor); - ArgumentGuard.NotNull(queryableBuilder); + ArgumentNullException.ThrowIfNull(mongoDataAccess); + ArgumentNullException.ThrowIfNull(targetedFields); + ArgumentNullException.ThrowIfNull(resourceGraph); + ArgumentNullException.ThrowIfNull(resourceFactory); + ArgumentNullException.ThrowIfNull(constraintProviders); + ArgumentNullException.ThrowIfNull(resourceDefinitionAccessor); + ArgumentNullException.ThrowIfNull(queryableBuilder); _mongoDataAccess = mongoDataAccess; _targetedFields = targetedFields; _resourceGraph = resourceGraph; _resourceFactory = resourceFactory; - _constraintProviders = constraintProviders; + _constraintProviders = constraintProviders as IQueryConstraintProvider[] ?? constraintProviders.ToArray(); _resourceDefinitionAccessor = resourceDefinitionAccessor; _queryableBuilder = queryableBuilder; @@ -71,10 +72,11 @@ public MongoRepository(IMongoDataAccess mongoDataAccess, ITargetedFields targete /// public virtual async Task> GetAsync(QueryLayer queryLayer, CancellationToken cancellationToken) { - ArgumentGuard.NotNull(queryLayer); + ArgumentNullException.ThrowIfNull(queryLayer); IMongoQueryable query = ApplyQueryLayer(queryLayer); - return await query.ToListAsync(cancellationToken); + List? resources = await query.ToListAsync(cancellationToken); + return resources.AsReadOnly(); } /// @@ -95,7 +97,7 @@ public virtual Task CountAsync(FilterExpression? topFilter, CancellationTok protected virtual IMongoQueryable ApplyQueryLayer(QueryLayer queryLayer) #pragma warning restore AV1130 // Return type in method signature should be an interface to an unchangeable collection { - ArgumentGuard.NotNull(queryLayer); + ArgumentNullException.ThrowIfNull(queryLayer); var queryExpressionValidator = new MongoQueryExpressionValidator(); queryExpressionValidator.Validate(queryLayer); @@ -159,6 +161,8 @@ private void AssertNoRelationshipsInSparseFieldSets() /// public virtual Task GetForCreateAsync(Type resourceClrType, [DisallowNull] TId id, CancellationToken cancellationToken) { + ArgumentNullException.ThrowIfNull(resourceClrType); + var resource = (TResource)_resourceFactory.CreateInstance(resourceClrType); resource.Id = id; @@ -168,8 +172,8 @@ public virtual Task GetForCreateAsync(Type resourceClrType, [Disallow /// public virtual async Task CreateAsync(TResource resourceFromRequest, TResource resourceForDatabase, CancellationToken cancellationToken) { - ArgumentGuard.NotNull(resourceFromRequest); - ArgumentGuard.NotNull(resourceForDatabase); + ArgumentNullException.ThrowIfNull(resourceFromRequest); + ArgumentNullException.ThrowIfNull(resourceForDatabase); AssertNoRelationshipsAreTargeted(); @@ -190,7 +194,7 @@ await SaveChangesAsync( private void AssertNoRelationshipsAreTargeted() { - if (_targetedFields.Relationships.Any()) + if (_targetedFields.Relationships.Count > 0) { throw new UnsupportedRelationshipException(); } @@ -199,7 +203,7 @@ private void AssertNoRelationshipsAreTargeted() /// public virtual async Task GetForUpdateAsync(QueryLayer queryLayer, CancellationToken cancellationToken) { - ArgumentGuard.NotNull(queryLayer); + ArgumentNullException.ThrowIfNull(queryLayer); IReadOnlyCollection resources = await GetAsync(queryLayer, cancellationToken); return resources.FirstOrDefault(); @@ -208,8 +212,8 @@ private void AssertNoRelationshipsAreTargeted() /// public virtual async Task UpdateAsync(TResource resourceFromRequest, TResource resourceFromDatabase, CancellationToken cancellationToken) { - ArgumentGuard.NotNull(resourceFromRequest); - ArgumentGuard.NotNull(resourceFromDatabase); + ArgumentNullException.ThrowIfNull(resourceFromRequest); + ArgumentNullException.ThrowIfNull(resourceFromDatabase); AssertNoRelationshipsAreTargeted(); @@ -248,12 +252,12 @@ public virtual async Task DeleteAsync(TResource? resourceFromDatabase, [Disallow if (!result.IsAcknowledged) { throw new DataStoreUpdateException( - new Exception($"Failed to delete document with id '{id}', because the operation was not acknowledged by MongoDB.")); + new DataException($"Failed to delete document with id '{id}', because the operation was not acknowledged by MongoDB.")); } if (result.DeletedCount == 0) { - throw new DataStoreUpdateException(new Exception($"Failed to delete document with id '{id}', because it does not exist.")); + throw new DataStoreUpdateException(new DataException($"Failed to delete document with id '{id}', because it does not exist.")); } await _resourceDefinitionAccessor.OnWriteSucceededAsync(placeholderResource, WriteOperationKind.DeleteResource, cancellationToken); @@ -280,6 +284,8 @@ public virtual Task RemoveFromToManyRelationshipAsync(TResource leftResource, IS protected virtual async Task SaveChangesAsync(Func asyncSaveAction, CancellationToken cancellationToken) { + ArgumentNullException.ThrowIfNull(asyncSaveAction); + _ = await SaveChangesAsync(async () => { await asyncSaveAction(); @@ -289,6 +295,8 @@ protected virtual async Task SaveChangesAsync(Func asyncSaveAction, Cancel protected virtual async Task SaveChangesAsync(Func> asyncSaveAction, CancellationToken cancellationToken) { + ArgumentNullException.ThrowIfNull(asyncSaveAction); + try { return await asyncSaveAction(); diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/AtomicOperationsTestCollection.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/AtomicOperationsCollectionFixture.cs similarity index 81% rename from test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/AtomicOperationsTestCollection.cs rename to test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/AtomicOperationsCollectionFixture.cs index acd9a96..dd1be80 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/AtomicOperationsTestCollection.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/AtomicOperationsCollectionFixture.cs @@ -3,7 +3,7 @@ namespace JsonApiDotNetCoreMongoDbTests.IntegrationTests.AtomicOperations; [CollectionDefinition("AtomicOperationsFixture")] -public sealed class AtomicOperationsTestCollection : ICollectionFixture +public sealed class AtomicOperationsCollectionFixture : 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. diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/BaseForAtomicOperationsTestsThatChangeOptions.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/BaseForAtomicOperationsTestsThatChangeOptions.cs index 6f60018..e9ffc53 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/BaseForAtomicOperationsTestsThatChangeOptions.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/BaseForAtomicOperationsTestsThatChangeOptions.cs @@ -16,12 +16,23 @@ protected BaseForAtomicOperationsTestsThatChangeOptions(AtomicOperationsFixture public void Dispose() { - _optionsScope.Dispose(); + Dispose(true); + GC.SuppressFinalize(this); + } + +#pragma warning disable CA1063 // Implement IDisposable Correctly + private void Dispose(bool disposing) +#pragma warning restore CA1063 // Implement IDisposable Correctly + { + if (disposing) + { + _optionsScope.Dispose(); + } } private sealed class JsonApiOptionsScope : IDisposable { - private static readonly List PropertyCache = typeof(JsonApiOptions).GetProperties().Where(IsAccessibleProperty).ToList(); + private static readonly PropertyInfo[] PropertyCache = typeof(JsonApiOptions).GetProperties().Where(IsAccessibleProperty).ToArray(); private readonly JsonApiOptions _options; private readonly JsonApiOptions _backupValues; diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceTests.cs index 0172c7a..8d65046 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceTests.cs @@ -17,8 +17,8 @@ public sealed class AtomicCreateResourceTests(AtomicOperationsFixture fixture) public async Task Can_create_resource() { // Arrange - string newArtistName = _fakers.Performer.Generate().ArtistName!; - DateTimeOffset newBornAt = _fakers.Performer.Generate().BornAt; + string newArtistName = _fakers.Performer.GenerateOne().ArtistName!; + DateTimeOffset newBornAt = _fakers.Performer.GenerateOne().BornAt; var requestBody = new { @@ -48,17 +48,17 @@ public async Task Can_create_resource() // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Results.ShouldHaveCount(1); + responseDocument.Results.Should().HaveCount(1); - responseDocument.Results[0].Data.SingleValue.ShouldNotBeNull().With(resource => + responseDocument.Results[0].Data.SingleValue.RefShould().NotBeNull().And.Subject.With(resource => { resource.Type.Should().Be("performers"); - resource.Attributes.ShouldContainKey("artistName").With(value => value.Should().Be(newArtistName)); - resource.Attributes.ShouldContainKey("bornAt").With(value => value.Should().Be(newBornAt)); + resource.Attributes.Should().ContainKey("artistName").WhoseValue.Should().Be(newArtistName); + resource.Attributes.Should().ContainKey("bornAt").WhoseValue.Should().Be(newBornAt); resource.Relationships.Should().BeNull(); }); - string newPerformerId = responseDocument.Results[0].Data.SingleValue!.Id.ShouldNotBeNull(); + string newPerformerId = responseDocument.Results[0].Data.SingleValue!.Id.Should().NotBeNull().And.Subject; await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -75,7 +75,7 @@ public async Task Can_create_resources() // Arrange const int elementCount = 5; - List newTracks = _fakers.MusicTrack.Generate(elementCount); + List newTracks = _fakers.MusicTrack.GenerateList(elementCount); var operationElements = new List(elementCount); @@ -111,33 +111,30 @@ public async Task Can_create_resources() // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Results.ShouldHaveCount(elementCount); + responseDocument.Results.Should().HaveCount(elementCount); for (int index = 0; index < elementCount; index++) { - responseDocument.Results[index].Data.SingleValue.ShouldNotBeNull().With(resource => + responseDocument.Results[index].Data.SingleValue.RefShould().NotBeNull().And.Subject.With(resource => { - resource.ShouldNotBeNull(); + resource.Should().NotBeNull(); resource.Type.Should().Be("musicTracks"); - resource.Attributes.ShouldContainKey("title").With(value => value.Should().Be(newTracks[index].Title)); - - resource.Attributes.ShouldContainKey("lengthInSeconds") - .With(value => value.As().Should().BeApproximately(newTracks[index].LengthInSeconds)); - - resource.Attributes.ShouldContainKey("genre").With(value => value.Should().Be(newTracks[index].Genre)); - resource.Attributes.ShouldContainKey("releasedAt").With(value => value.Should().Be(newTracks[index].ReleasedAt)); + resource.Attributes.Should().ContainKey("title").WhoseValue.Should().Be(newTracks[index].Title); + resource.Attributes.Should().ContainKey("lengthInSeconds").WhoseValue.As().Should().BeApproximately(newTracks[index].LengthInSeconds); + resource.Attributes.Should().ContainKey("genre").WhoseValue.Should().Be(newTracks[index].Genre); + resource.Attributes.Should().ContainKey("releasedAt").WhoseValue.Should().Be(newTracks[index].ReleasedAt); resource.Relationships.Should().BeNull(); }); } - string[] newTrackIds = responseDocument.Results.Select(result => result.Data.SingleValue!.Id.ShouldNotBeNull()).ToArray(); + string[] newTrackIds = responseDocument.Results.Select(result => result.Data.SingleValue!.Id.Should().NotBeNull().And.Subject).ToArray(); await _testContext.RunOnDatabaseAsync(async dbContext => { List tracksInDatabase = await dbContext.MusicTracks.ToListWhereAsync(musicTrack => newTrackIds.Contains(musicTrack.Id)); - tracksInDatabase.ShouldHaveCount(elementCount); + tracksInDatabase.Should().HaveCount(elementCount); for (int index = 0; index < elementCount; index++) { @@ -184,17 +181,17 @@ public async Task Can_create_resource_without_attributes_or_relationships() // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Results.ShouldHaveCount(1); + responseDocument.Results.Should().HaveCount(1); - responseDocument.Results[0].Data.SingleValue.ShouldNotBeNull().With(resource => + responseDocument.Results[0].Data.SingleValue.RefShould().NotBeNull().And.Subject.With(resource => { resource.Type.Should().Be("performers"); - resource.Attributes.ShouldContainKey("artistName").With(value => value.Should().BeNull()); - resource.Attributes.ShouldContainKey("bornAt").With(value => value.Should().Be(default(DateTimeOffset))); + resource.Attributes.Should().ContainKey("artistName").WhoseValue.Should().BeNull(); + resource.Attributes.Should().ContainKey("bornAt").WhoseValue.Should().Be(default(DateTimeOffset)); resource.Relationships.Should().BeNull(); }); - string newPerformerId = responseDocument.Results[0].Data.SingleValue!.Id.ShouldNotBeNull(); + string newPerformerId = responseDocument.Results[0].Data.SingleValue!.Id.Should().NotBeNull().And.Subject; await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -209,7 +206,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Cannot_create_resource_with_client_generated_ID() { // Arrange - MusicTrack newTrack = _fakers.MusicTrack.Generate(); + MusicTrack newTrack = _fakers.MusicTrack.GenerateOne(); newTrack.Id = ObjectId.GenerateNewId().ToString(); var requestBody = new @@ -240,14 +237,14 @@ public async Task Cannot_create_resource_with_client_generated_ID() // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.Forbidden); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.Forbidden); error.Title.Should().Be("Failed to deserialize request body: The use of client-generated IDs is disabled."); error.Detail.Should().BeNull(); - error.Source.ShouldNotBeNull(); + error.Source.Should().NotBeNull(); error.Source.Pointer.Should().Be("/atomic:operations[0]/data/id"); - error.Meta.ShouldContainKey("requestBody").With(value => value.ShouldNotBeNull().ToString().ShouldNotBeEmpty()); + error.Meta.Should().HaveRequestBody(); } } diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithClientGeneratedIdTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithClientGeneratedIdTests.cs index 7fdf996..6014612 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithClientGeneratedIdTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithClientGeneratedIdTests.cs @@ -27,7 +27,7 @@ public AtomicCreateResourceWithClientGeneratedIdTests(AtomicOperationsFixture fi public async Task Can_create_resource_with_client_generated_string_ID_having_side_effects() { // Arrange - TextLanguage newLanguage = _fakers.TextLanguage.Generate(); + TextLanguage newLanguage = _fakers.TextLanguage.GenerateOne(); newLanguage.Id = "free-format-client-generated-id"; var requestBody = new @@ -60,12 +60,12 @@ public async Task Can_create_resource_with_client_generated_string_ID_having_sid string isoCode = $"{newLanguage.IsoCode}{ImplicitlyChangingTextLanguageDefinition.Suffix}"; - responseDocument.Results.ShouldHaveCount(1); + responseDocument.Results.Should().HaveCount(1); - responseDocument.Results[0].Data.SingleValue.ShouldNotBeNull().With(resource => + responseDocument.Results[0].Data.SingleValue.RefShould().NotBeNull().And.Subject.With(resource => { resource.Type.Should().Be("textLanguages"); - resource.Attributes.ShouldContainKey("isoCode").With(value => value.Should().Be(isoCode)); + resource.Attributes.Should().ContainKey("isoCode").WhoseValue.Should().Be(isoCode); resource.Attributes.Should().NotContainKey("isRightToLeft"); resource.Relationships.Should().BeNull(); }); @@ -82,7 +82,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_create_resource_with_client_generated_string_ID_having_no_side_effects() { // Arrange - Playlist newPlaylist = _fakers.Playlist.Generate(); + Playlist newPlaylist = _fakers.Playlist.GenerateOne(); newPlaylist.Id = "free-format-client-generated-id"; var requestBody = new @@ -127,10 +127,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Cannot_create_resource_for_existing_client_generated_ID() { // Arrange - TextLanguage existingLanguage = _fakers.TextLanguage.Generate(); + TextLanguage existingLanguage = _fakers.TextLanguage.GenerateOne(); existingLanguage.Id = "existing-free-format-client-generated-id"; - string newIsoCode = _fakers.TextLanguage.Generate().IsoCode!; + string newIsoCode = _fakers.TextLanguage.GenerateOne().IsoCode!; await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -166,13 +166,13 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.Conflict); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject 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 '{existingLanguage.StringId}' already exists."); - error.Source.ShouldNotBeNull(); + error.Source.Should().NotBeNull(); error.Source.Pointer.Should().Be("/atomic:operations[0]"); error.Meta.Should().NotContainKey("requestBody"); } diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithToManyRelationshipTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithToManyRelationshipTests.cs index 70b5468..4f5f561 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithToManyRelationshipTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithToManyRelationshipTests.cs @@ -16,9 +16,9 @@ public sealed class AtomicCreateResourceWithToManyRelationshipTests(AtomicOperat public async Task Cannot_create_ToMany_relationship() { // Arrange - Performer existingPerformer = _fakers.Performer.Generate(); + Performer existingPerformer = _fakers.Performer.GenerateOne(); - string newTitle = _fakers.MusicTrack.Generate().Title; + string newTitle = _fakers.MusicTrack.GenerateOne().Title; await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -67,13 +67,13 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject 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.ShouldNotBeNull(); + error.Source.Should().NotBeNull(); error.Source.Pointer.Should().Be("/atomic:operations[0]"); } } diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithToOneRelationshipTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithToOneRelationshipTests.cs index 51fd385..e449068 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithToOneRelationshipTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithToOneRelationshipTests.cs @@ -16,9 +16,9 @@ public sealed class AtomicCreateResourceWithToOneRelationshipTests(AtomicOperati public async Task Cannot_create_ToOne_relationship() { // Arrange - MusicTrack existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.GenerateOne(); - string newLyricText = _fakers.Lyric.Generate().Text; + string newLyricText = _fakers.Lyric.GenerateOne().Text; await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -64,13 +64,13 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject 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.ShouldNotBeNull(); + error.Source.Should().NotBeNull(); error.Source.Pointer.Should().Be("/atomic:operations[0]"); } } diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Deleting/AtomicDeleteResourceTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Deleting/AtomicDeleteResourceTests.cs index cd7accf..1ea1ac4 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Deleting/AtomicDeleteResourceTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Deleting/AtomicDeleteResourceTests.cs @@ -16,7 +16,7 @@ public sealed class AtomicDeleteResourceTests(AtomicOperationsFixture fixture) public async Task Can_delete_existing_resource() { // Arrange - Performer existingPerformer = _fakers.Performer.Generate(); + Performer existingPerformer = _fakers.Performer.GenerateOne(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -64,7 +64,7 @@ public async Task Can_delete_existing_resources() // Arrange const int elementCount = 5; - List existingTracks = _fakers.MusicTrack.Generate(elementCount); + List existingTracks = _fakers.MusicTrack.GenerateList(elementCount); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -141,13 +141,13 @@ public async Task Cannot_delete_resource_for_unknown_ID() // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.NotFound); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject 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 '{performerId}' does not exist."); - error.Source.ShouldNotBeNull(); + error.Source.Should().NotBeNull(); error.Source.Pointer.Should().Be("/atomic:operations[0]"); error.Meta.Should().NotContainKey("requestBody"); } diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/LocalIds/AtomicLocalIdTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/LocalIds/AtomicLocalIdTests.cs index 54680ba..61459ed 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/LocalIds/AtomicLocalIdTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/LocalIds/AtomicLocalIdTests.cs @@ -16,8 +16,8 @@ public sealed class AtomicLocalIdTests(AtomicOperationsFixture fixture) public async Task Can_update_resource_using_local_ID() { // Arrange - string newTrackTitle = _fakers.MusicTrack.Generate().Title; - string newTrackGenre = _fakers.MusicTrack.Generate().Genre!; + string newTrackTitle = _fakers.MusicTrack.GenerateOne().Title; + string newTrackGenre = _fakers.MusicTrack.GenerateOne().Genre!; const string trackLocalId = "track-1"; @@ -62,19 +62,19 @@ public async Task Can_update_resource_using_local_ID() // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Results.ShouldHaveCount(2); + responseDocument.Results.Should().HaveCount(2); - responseDocument.Results[0].Data.SingleValue.ShouldNotBeNull().With(resource => + responseDocument.Results[0].Data.SingleValue.RefShould().NotBeNull().And.Subject.With(resource => { resource.Type.Should().Be("musicTracks"); resource.Lid.Should().BeNull(); - resource.Attributes.ShouldContainKey("title").With(value => value.Should().Be(newTrackTitle)); - resource.Attributes.ShouldContainKey("genre").With(value => value.Should().BeNull()); + resource.Attributes.Should().ContainKey("title").WhoseValue.Should().Be(newTrackTitle); + resource.Attributes.Should().ContainKey("genre").WhoseValue.Should().BeNull(); }); responseDocument.Results[1].Data.Value.Should().BeNull(); - string newTrackId = responseDocument.Results[0].Data.SingleValue!.Id.ShouldNotBeNull(); + string newTrackId = responseDocument.Results[0].Data.SingleValue!.Id.Should().NotBeNull().And.Subject; await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -89,7 +89,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_delete_resource_using_local_ID() { // Arrange - string newTrackTitle = _fakers.MusicTrack.Generate().Title; + string newTrackTitle = _fakers.MusicTrack.GenerateOne().Title; const string trackLocalId = "track-1"; @@ -130,18 +130,18 @@ public async Task Can_delete_resource_using_local_ID() // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Results.ShouldHaveCount(2); + responseDocument.Results.Should().HaveCount(2); - responseDocument.Results[0].Data.SingleValue.ShouldNotBeNull().With(resource => + responseDocument.Results[0].Data.SingleValue.RefShould().NotBeNull().And.Subject.With(resource => { resource.Type.Should().Be("musicTracks"); resource.Lid.Should().BeNull(); - resource.Attributes.ShouldContainKey("title").With(value => value.Should().Be(newTrackTitle)); + resource.Attributes.Should().ContainKey("title").WhoseValue.Should().Be(newTrackTitle); }); responseDocument.Results[1].Data.Value.Should().BeNull(); - string newTrackId = responseDocument.Results[0].Data.SingleValue!.Id.ShouldNotBeNull(); + string newTrackId = responseDocument.Results[0].Data.SingleValue!.Id.Should().NotBeNull().And.Subject; await _testContext.RunOnDatabaseAsync(async dbContext => { diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Meta/AtomicResourceMetaTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Meta/AtomicResourceMetaTests.cs index a701256..988b697 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Meta/AtomicResourceMetaTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Meta/AtomicResourceMetaTests.cs @@ -29,8 +29,8 @@ public async Task Returns_resource_meta_in_create_resource_with_side_effects() // Arrange var hitCounter = _testContext.Factory.Services.GetRequiredService(); - string newTitle1 = _fakers.MusicTrack.Generate().Title; - string newTitle2 = _fakers.MusicTrack.Generate().Title; + string newTitle1 = _fakers.MusicTrack.GenerateOne().Title; + string newTitle2 = _fakers.MusicTrack.GenerateOne().Title; var requestBody = new { @@ -73,24 +73,24 @@ public async Task Returns_resource_meta_in_create_resource_with_side_effects() // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Results.ShouldHaveCount(2); + responseDocument.Results.Should().HaveCount(2); - responseDocument.Results[0].Data.SingleValue.ShouldNotBeNull().With(resource => + responseDocument.Results[0].Data.SingleValue.RefShould().NotBeNull().And.Subject.With(resource => { - resource.Meta.ShouldHaveCount(1); + resource.Meta.Should().HaveCount(1); - resource.Meta.ShouldContainKey("copyright").With(value => + resource.Meta.Should().ContainKey("copyright").WhoseValue.With(value => { JsonElement element = value.Should().BeOfType().Subject; element.GetString().Should().Be("(C) 2018. All rights reserved."); }); }); - responseDocument.Results[1].Data.SingleValue.ShouldNotBeNull().With(resource => + responseDocument.Results[1].Data.SingleValue.RefShould().NotBeNull().And.Subject.With(resource => { - resource.Meta.ShouldHaveCount(1); + resource.Meta.Should().HaveCount(1); - resource.Meta.ShouldContainKey("copyright").With(value => + resource.Meta.Should().ContainKey("copyright").WhoseValue.With(value => { JsonElement element = value.Should().BeOfType().Subject; element.GetString().Should().Be("(C) 1994. All rights reserved."); @@ -110,7 +110,7 @@ public async Task Returns_resource_meta_in_update_resource_with_side_effects() // Arrange var hitCounter = _testContext.Factory.Services.GetRequiredService(); - TextLanguage existingLanguage = _fakers.TextLanguage.Generate(); + TextLanguage existingLanguage = _fakers.TextLanguage.GenerateOne(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -145,13 +145,13 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Results.ShouldHaveCount(1); + responseDocument.Results.Should().HaveCount(1); - responseDocument.Results[0].Data.SingleValue.ShouldNotBeNull().With(resource => + responseDocument.Results[0].Data.SingleValue.RefShould().NotBeNull().And.Subject.With(resource => { - resource.Meta.ShouldHaveCount(1); + resource.Meta.Should().HaveCount(1); - resource.Meta.ShouldContainKey("notice").With(value => + resource.Meta.Should().ContainKey("notice").WhoseValue.With(value => { JsonElement element = value.Should().BeOfType().Subject; element.GetString().Should().Be(TextLanguageMetaDefinition.NoticeText); diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Mixed/MaximumOperationsPerRequestTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Mixed/MaximumOperationsPerRequestTests.cs index ba4bf6a..d405a69 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Mixed/MaximumOperationsPerRequestTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Mixed/MaximumOperationsPerRequestTests.cs @@ -8,7 +8,8 @@ namespace JsonApiDotNetCoreMongoDbTests.IntegrationTests.AtomicOperations.Mixed; [Collection("AtomicOperationsFixture")] -public sealed class MaximumOperationsPerRequestTests(AtomicOperationsFixture fixture) : BaseForAtomicOperationsTestsThatChangeOptions(fixture) +public sealed class MaximumOperationsPerRequestTests(AtomicOperationsFixture fixture) + : BaseForAtomicOperationsTestsThatChangeOptions(fixture) { private readonly IntegrationTestContext _testContext = fixture.TestContext; diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/OperationsController.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/OperationsController.cs index d8ec189..1ba04d2 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/OperationsController.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/OperationsController.cs @@ -9,5 +9,5 @@ namespace JsonApiDotNetCoreMongoDbTests.IntegrationTests.AtomicOperations; public sealed class OperationsController( IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory, IOperationsProcessor processor, IJsonApiRequest request, - ITargetedFields targetedFields, IAtomicOperationFilter operationFilter) : JsonApiOperationsController(options, resourceGraph, loggerFactory, processor, - request, targetedFields, operationFilter); + ITargetedFields targetedFields, IAtomicOperationFilter operationFilter) + : JsonApiOperationsController(options, resourceGraph, loggerFactory, processor, request, targetedFields, operationFilter); diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/OperationsDbContext.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/OperationsDbContext.cs index aa75333..5d443a6 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/OperationsDbContext.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/OperationsDbContext.cs @@ -5,7 +5,8 @@ namespace JsonApiDotNetCoreMongoDbTests.IntegrationTests.AtomicOperations; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class OperationsDbContext(IMongoDatabase database) : MongoDbContextShim(database) +public sealed class OperationsDbContext(IMongoDatabase database) + : MongoDbContextShim(database) { public MongoDbSetShim Playlists => Set(); public MongoDbSetShim MusicTracks => Set(); diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/OperationsFakers.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/OperationsFakers.cs index 51f3ba8..6a10744 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/OperationsFakers.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/OperationsFakers.cs @@ -9,7 +9,7 @@ namespace JsonApiDotNetCoreMongoDbTests.IntegrationTests.AtomicOperations; internal sealed class OperationsFakers { - private static readonly Lazy> LazyLanguageIsoCodes = new(() => CultureInfo + private static readonly Lazy LazyLanguageIsoCodes = new(() => CultureInfo .GetCultures(CultureTypes.NeutralCultures) .Where(culture => !string.IsNullOrEmpty(culture.Name)) .Select(culture => culture.Name) @@ -33,7 +33,7 @@ internal sealed class OperationsFakers private readonly Lazy> _lazyTextLanguageFaker = new(() => new Faker() .MakeDeterministic() - .RuleFor(textLanguage => textLanguage.IsoCode, faker => faker.PickRandom(LazyLanguageIsoCodes.Value))); + .RuleFor(textLanguage => textLanguage.IsoCode, faker => faker.PickRandom(LazyLanguageIsoCodes.Value))); private readonly Lazy> _lazyPerformerFaker = new(() => new Faker() .MakeDeterministic() diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Transactions/AtomicRollbackTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Transactions/AtomicRollbackTests.cs index 0a2cc3a..08bd99a 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Transactions/AtomicRollbackTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Transactions/AtomicRollbackTests.cs @@ -23,8 +23,8 @@ public AtomicRollbackTests(AtomicOperationsFixture fixture) public async Task Can_rollback_created_resource_on_error() { // Arrange - string newArtistName = _fakers.Performer.Generate().ArtistName!; - DateTimeOffset newBornAt = _fakers.Performer.Generate().BornAt; + string newArtistName = _fakers.Performer.GenerateOne().ArtistName!; + DateTimeOffset newBornAt = _fakers.Performer.GenerateOne().BornAt; await _testContext.RunOnDatabaseAsync(async dbContext => await dbContext.ClearTableAsync()); @@ -67,13 +67,13 @@ public async Task Can_rollback_created_resource_on_error() // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.NotFound); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject 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 '{unknownPerformerId}' does not exist."); - error.Source.ShouldNotBeNull(); + error.Source.Should().NotBeNull(); error.Source.Pointer.Should().Be("/atomic:operations[1]"); await _testContext.RunOnDatabaseAsync(async dbContext => @@ -87,9 +87,9 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_rollback_updated_resource_on_error() { // Arrange - Performer existingPerformer = _fakers.Performer.Generate(); + Performer existingPerformer = _fakers.Performer.GenerateOne(); - string newArtistName = _fakers.Performer.Generate().ArtistName!; + string newArtistName = _fakers.Performer.GenerateOne().ArtistName!; await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -136,13 +136,13 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.NotFound); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject 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 '{unknownPerformerId}' does not exist."); - error.Source.ShouldNotBeNull(); + error.Source.Should().NotBeNull(); error.Source.Pointer.Should().Be("/atomic:operations[1]"); await _testContext.RunOnDatabaseAsync(async dbContext => @@ -157,7 +157,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_rollback_deleted_resource_on_error() { // Arrange - Performer existingPerformer = _fakers.Performer.Generate(); + Performer existingPerformer = _fakers.Performer.GenerateOne(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -201,13 +201,13 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.NotFound); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject 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 '{unknownPerformerId}' does not exist."); - error.Source.ShouldNotBeNull(); + error.Source.Should().NotBeNull(); error.Source.Pointer.Should().Be("/atomic:operations[1]"); await _testContext.RunOnDatabaseAsync(async dbContext => diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Transactions/AtomicTransactionConsistencyTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Transactions/AtomicTransactionConsistencyTests.cs index 5475fcb..b19501b 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Transactions/AtomicTransactionConsistencyTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Transactions/AtomicTransactionConsistencyTests.cs @@ -2,7 +2,6 @@ using FluentAssertions; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Serialization.Objects; -using Microsoft.Extensions.DependencyInjection.Extensions; using TestBuildingBlocks; using Xunit; @@ -23,8 +22,6 @@ public AtomicTransactionConsistencyTests(IntegrationTestContext { - services.TryAddSingleton(); - services.AddResourceRepository(); services.AddResourceRepository(); services.AddResourceRepository(); @@ -61,13 +58,13 @@ public async Task Cannot_use_non_transactional_repository() // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.UnprocessableEntity); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject 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.ShouldNotBeNull(); + error.Source.Should().NotBeNull(); error.Source.Pointer.Should().Be("/atomic:operations[0]"); } @@ -75,7 +72,7 @@ public async Task Cannot_use_non_transactional_repository() public async Task Cannot_use_transactional_repository_without_active_transaction() { // Arrange - string newTrackTitle = _fakers.MusicTrack.Generate().Title; + string newTrackTitle = _fakers.MusicTrack.GenerateOne().Title; var requestBody = new { @@ -104,13 +101,13 @@ public async Task Cannot_use_transactional_repository_without_active_transaction // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.UnprocessableEntity); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject 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.ShouldNotBeNull(); + error.Source.Should().NotBeNull(); error.Source.Pointer.Should().Be("/atomic:operations[0]"); } @@ -118,7 +115,7 @@ public async Task Cannot_use_transactional_repository_without_active_transaction public async Task Cannot_use_distributed_transaction() { // Arrange - string newLyricText = _fakers.Lyric.Generate().Text; + string newLyricText = _fakers.Lyric.GenerateOne().Text; var requestBody = new { @@ -147,13 +144,13 @@ public async Task Cannot_use_distributed_transaction() // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.UnprocessableEntity); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject 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.ShouldNotBeNull(); + error.Source.Should().NotBeNull(); error.Source.Pointer.Should().Be("/atomic:operations[0]"); } } diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Transactions/LyricRepository.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Transactions/LyricRepository.cs index d8ad7e0..90d71d0 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Transactions/LyricRepository.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Transactions/LyricRepository.cs @@ -12,6 +12,7 @@ namespace JsonApiDotNetCoreMongoDbTests.IntegrationTests.AtomicOperations.Transa [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] public sealed class LyricRepository : MongoRepository, IAsyncDisposable { + private readonly MongoDataAccess _otherDataAccess; private readonly IOperationsTransaction _transaction; public override string TransactionId => _transaction.TransactionId; @@ -20,14 +21,15 @@ public LyricRepository(IMongoDataAccess mongoDataAccess, ITargetedFields targete IEnumerable constraintProviders, IResourceDefinitionAccessor resourceDefinitionAccessor, IQueryableBuilder queryableBuilder) : base(mongoDataAccess, targetedFields, resourceGraph, resourceFactory, constraintProviders, resourceDefinitionAccessor, queryableBuilder) { - IMongoDataAccess otherDataAccess = new MongoDataAccess(mongoDataAccess.EntityModel, mongoDataAccess.MongoDatabase); + _otherDataAccess = new MongoDataAccess(mongoDataAccess.EntityModel, mongoDataAccess.MongoDatabase); - var factory = new MongoTransactionFactory(otherDataAccess); + var factory = new MongoTransactionFactory(_otherDataAccess); _transaction = factory.BeginTransactionAsync(CancellationToken.None).Result; } public async ValueTask DisposeAsync() { await _transaction.DisposeAsync(); + await _otherDataAccess.DisposeAsync(); } } diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicAddToToManyRelationshipTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicAddToToManyRelationshipTests.cs index 50382cb..e461a70 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicAddToToManyRelationshipTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicAddToToManyRelationshipTests.cs @@ -16,8 +16,8 @@ public sealed class AtomicAddToToManyRelationshipTests(AtomicOperationsFixture f public async Task Cannot_add_to_OneToMany_relationship() { // Arrange - MusicTrack existingTrack = _fakers.MusicTrack.Generate(); - Performer existingPerformer = _fakers.Performer.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.GenerateOne(); + Performer existingPerformer = _fakers.Performer.GenerateOne(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -59,13 +59,13 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject 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.ShouldNotBeNull(); + error.Source.Should().NotBeNull(); error.Source.Pointer.Should().Be("/atomic:operations[0]"); } @@ -73,8 +73,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Cannot_add_to_ManyToMany_relationship() { // Arrange - Playlist existingPlaylist = _fakers.Playlist.Generate(); - MusicTrack existingTrack = _fakers.MusicTrack.Generate(); + Playlist existingPlaylist = _fakers.Playlist.GenerateOne(); + MusicTrack existingTrack = _fakers.MusicTrack.GenerateOne(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -116,13 +116,13 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject 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.ShouldNotBeNull(); + error.Source.Should().NotBeNull(); error.Source.Pointer.Should().Be("/atomic:operations[0]"); } } diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicRemoveFromToManyRelationshipTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicRemoveFromToManyRelationshipTests.cs index 8ecb491..e68e8fd 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicRemoveFromToManyRelationshipTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicRemoveFromToManyRelationshipTests.cs @@ -16,8 +16,8 @@ public sealed class AtomicRemoveFromToManyRelationshipTests(AtomicOperationsFixt public async Task Cannot_remove_from_OneToMany_relationship() { // Arrange - MusicTrack existingTrack = _fakers.MusicTrack.Generate(); - existingTrack.Performers = _fakers.Performer.Generate(1); + MusicTrack existingTrack = _fakers.MusicTrack.GenerateOne(); + existingTrack.Performers = _fakers.Performer.GenerateList(1); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -59,13 +59,13 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject 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.ShouldNotBeNull(); + error.Source.Should().NotBeNull(); error.Source.Pointer.Should().Be("/atomic:operations[0]"); } @@ -73,8 +73,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Cannot_remove_from_ManyToMany_relationship() { // Arrange - Playlist existingPlaylist = _fakers.Playlist.Generate(); - existingPlaylist.Tracks = _fakers.MusicTrack.Generate(1); + Playlist existingPlaylist = _fakers.Playlist.GenerateOne(); + existingPlaylist.Tracks = _fakers.MusicTrack.GenerateList(1); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -116,13 +116,13 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject 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.ShouldNotBeNull(); + error.Source.Should().NotBeNull(); error.Source.Pointer.Should().Be("/atomic:operations[0]"); } } diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicReplaceToManyRelationshipTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicReplaceToManyRelationshipTests.cs index 09f2979..e71ef7b 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicReplaceToManyRelationshipTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicReplaceToManyRelationshipTests.cs @@ -16,8 +16,8 @@ public sealed class AtomicReplaceToManyRelationshipTests(AtomicOperationsFixture public async Task Cannot_replace_OneToMany_relationship() { // Arrange - MusicTrack existingTrack = _fakers.MusicTrack.Generate(); - Performer existingPerformer = _fakers.Performer.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.GenerateOne(); + Performer existingPerformer = _fakers.Performer.GenerateOne(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -59,13 +59,13 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject 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.ShouldNotBeNull(); + error.Source.Should().NotBeNull(); error.Source.Pointer.Should().Be("/atomic:operations[0]"); } @@ -73,8 +73,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Cannot_replace_ManyToMany_relationship() { // Arrange - Playlist existingPlaylist = _fakers.Playlist.Generate(); - MusicTrack existingTrack = _fakers.MusicTrack.Generate(); + Playlist existingPlaylist = _fakers.Playlist.GenerateOne(); + MusicTrack existingTrack = _fakers.MusicTrack.GenerateOne(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -116,13 +116,13 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject 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.ShouldNotBeNull(); + error.Source.Should().NotBeNull(); error.Source.Pointer.Should().Be("/atomic:operations[0]"); } } diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicUpdateToOneRelationshipTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicUpdateToOneRelationshipTests.cs index 2541703..e51e98d 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicUpdateToOneRelationshipTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicUpdateToOneRelationshipTests.cs @@ -16,8 +16,8 @@ public sealed class AtomicUpdateToOneRelationshipTests(AtomicOperationsFixture f public async Task Cannot_create_ManyToOne_relationship() { // Arrange - MusicTrack existingTrack = _fakers.MusicTrack.Generate(); - RecordCompany existingCompany = _fakers.RecordCompany.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.GenerateOne(); + RecordCompany existingCompany = _fakers.RecordCompany.GenerateOne(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -56,13 +56,13 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject 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.ShouldNotBeNull(); + error.Source.Should().NotBeNull(); error.Source.Pointer.Should().Be("/atomic:operations[0]"); } } diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicReplaceToManyRelationshipTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicReplaceToManyRelationshipTests.cs index 329d74d..8ebe878 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicReplaceToManyRelationshipTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicReplaceToManyRelationshipTests.cs @@ -16,8 +16,8 @@ public sealed class AtomicReplaceToManyRelationshipTests(AtomicOperationsFixture public async Task Cannot_replace_ToMany_relationship() { // Arrange - MusicTrack existingTrack = _fakers.MusicTrack.Generate(); - Performer existingPerformer = _fakers.Performer.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.GenerateOne(); + Performer existingPerformer = _fakers.Performer.GenerateOne(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -64,13 +64,13 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject 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.ShouldNotBeNull(); + error.Source.Should().NotBeNull(); error.Source.Pointer.Should().Be("/atomic:operations[0]"); } } diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateResourceTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateResourceTests.cs index 9b88500..d438d34 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateResourceTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateResourceTests.cs @@ -18,8 +18,8 @@ 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.GenerateList(elementCount); + string[] newTrackTitles = _fakers.MusicTrack.GenerateList(elementCount).Select(musicTrack => musicTrack.Title).ToArray(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -66,7 +66,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => { List tracksInDatabase = await dbContext.MusicTracks.ToListAsync(); - tracksInDatabase.ShouldHaveCount(elementCount); + tracksInDatabase.Should().HaveCount(elementCount); for (int index = 0; index < elementCount; index++) { @@ -82,7 +82,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_update_resource_without_attributes_or_relationships() { // Arrange - MusicTrack existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.GenerateOne(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -135,9 +135,9 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_partially_update_resource_without_side_effects() { // Arrange - MusicTrack existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.GenerateOne(); - string newGenre = _fakers.MusicTrack.Generate().Genre!; + string newGenre = _fakers.MusicTrack.GenerateOne().Genre!; await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -190,12 +190,12 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_completely_update_resource_without_side_effects() { // Arrange - MusicTrack existingTrack = _fakers.MusicTrack.Generate(); + MusicTrack existingTrack = _fakers.MusicTrack.GenerateOne(); - 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.GenerateOne().Title; + decimal? newLengthInSeconds = _fakers.MusicTrack.GenerateOne().LengthInSeconds; + string newGenre = _fakers.MusicTrack.GenerateOne().Genre!; + DateTimeOffset newReleasedAt = _fakers.MusicTrack.GenerateOne().ReleasedAt; await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -251,8 +251,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_update_resource_with_side_effects() { // Arrange - TextLanguage existingLanguage = _fakers.TextLanguage.Generate(); - string newIsoCode = _fakers.TextLanguage.Generate().IsoCode!; + TextLanguage existingLanguage = _fakers.TextLanguage.GenerateOne(); + string newIsoCode = _fakers.TextLanguage.GenerateOne().IsoCode!; await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -288,14 +288,14 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Results.ShouldHaveCount(1); + responseDocument.Results.Should().HaveCount(1); string isoCode = $"{newIsoCode}{ImplicitlyChangingTextLanguageDefinition.Suffix}"; - responseDocument.Results[0].Data.SingleValue.ShouldNotBeNull().With(resource => + responseDocument.Results[0].Data.SingleValue.RefShould().NotBeNull().And.Subject.With(resource => { resource.Type.Should().Be("textLanguages"); - resource.Attributes.ShouldContainKey("isoCode").With(value => value.Should().Be(isoCode)); + resource.Attributes.Should().ContainKey("isoCode").WhoseValue.Should().Be(isoCode); resource.Attributes.Should().NotContainKey("isRightToLeft"); resource.Relationships.Should().BeNull(); }); @@ -343,13 +343,13 @@ public async Task Cannot_update_resource_for_unknown_ID() // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.NotFound); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject 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 '{performerId}' does not exist."); - error.Source.ShouldNotBeNull(); + error.Source.Should().NotBeNull(); error.Source.Pointer.Should().Be("/atomic:operations[0]"); error.Meta.Should().NotContainKey("requestBody"); } diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateToOneRelationshipTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateToOneRelationshipTests.cs index 75734f4..21967d5 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateToOneRelationshipTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateToOneRelationshipTests.cs @@ -16,8 +16,8 @@ public sealed class AtomicUpdateToOneRelationshipTests(AtomicOperationsFixture f public async Task Cannot_create_ToOne_relationship() { // Arrange - Lyric existingLyric = _fakers.Lyric.Generate(); - MusicTrack existingTrack = _fakers.MusicTrack.Generate(); + Lyric existingLyric = _fakers.Lyric.GenerateOne(); + MusicTrack existingTrack = _fakers.MusicTrack.GenerateOne(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -61,13 +61,13 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject 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.ShouldNotBeNull(); + error.Source.Should().NotBeNull(); error.Source.Pointer.Should().Be("/atomic:operations[0]"); } } diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/HitCountingResourceDefinition.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/HitCountingResourceDefinition.cs index 3ee880d..d059689 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/HitCountingResourceDefinition.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/HitCountingResourceDefinition.cs @@ -11,14 +11,21 @@ namespace JsonApiDotNetCoreMongoDbTests.IntegrationTests; /// Tracks invocations on callback methods. This is used solely in our tests, so we can assert which /// calls were made, and in which order. /// -public abstract class HitCountingResourceDefinition(IResourceGraph resourceGraph, ResourceDefinitionHitCounter hitCounter) - : JsonApiResourceDefinition(resourceGraph) +public abstract class HitCountingResourceDefinition : JsonApiResourceDefinition where TResource : class, IIdentifiable { - private readonly ResourceDefinitionHitCounter _hitCounter = hitCounter; + private readonly ResourceDefinitionHitCounter _hitCounter; protected virtual ResourceDefinitionExtensibilityPoints ExtensibilityPointsToTrack => ResourceDefinitionExtensibilityPoints.All; + protected HitCountingResourceDefinition(IResourceGraph resourceGraph, ResourceDefinitionHitCounter hitCounter) + : base(resourceGraph) + { + ArgumentNullException.ThrowIfNull(hitCounter); + + _hitCounter = hitCounter; + } + public override IImmutableSet OnApplyIncludes(IImmutableSet existingIncludes) { if (ExtensibilityPointsToTrack.HasFlag(ResourceDefinitionExtensibilityPoints.OnApplyIncludes)) diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/Meta/MetaDbContext.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/Meta/MetaDbContext.cs index 8aa82f8..ec003be 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/Meta/MetaDbContext.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/Meta/MetaDbContext.cs @@ -5,7 +5,8 @@ namespace JsonApiDotNetCoreMongoDbTests.IntegrationTests.Meta; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class MetaDbContext(IMongoDatabase database) : MongoDbContextShim(database) +public sealed class MetaDbContext(IMongoDatabase database) + : MongoDbContextShim(database) { public MongoDbSetShim SupportTickets => Set(); } diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/Meta/ResourceMetaTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/Meta/ResourceMetaTests.cs index d92419c..f8eedf2 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/Meta/ResourceMetaTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/Meta/ResourceMetaTests.cs @@ -3,7 +3,6 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Serialization.Objects; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; using TestBuildingBlocks; using Xunit; @@ -24,8 +23,9 @@ public ResourceMetaTests(IntegrationTestContext testContext.ConfigureServices(services => { - services.TryAddSingleton(); services.AddResourceDefinition(); + + services.AddSingleton(); }); var hitCounter = _testContext.Factory.Services.GetRequiredService(); @@ -38,7 +38,7 @@ public async Task Returns_resource_meta_from_ResourceDefinition() // Arrange var hitCounter = _testContext.Factory.Services.GetRequiredService(); - List tickets = _fakers.SupportTicket.Generate(3); + List tickets = _fakers.SupportTicket.GenerateList(3); tickets[0].Description = $"Critical: {tickets[0].Description}"; tickets[2].Description = $"Critical: {tickets[2].Description}"; @@ -57,10 +57,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Data.ManyValue.ShouldHaveCount(3); - responseDocument.Data.ManyValue[0].Meta.ShouldContainKey("hasHighPriority"); + responseDocument.Data.ManyValue.Should().HaveCount(3); + responseDocument.Data.ManyValue[0].Meta.Should().ContainKey("hasHighPriority"); responseDocument.Data.ManyValue[1].Meta.Should().BeNull(); - responseDocument.Data.ManyValue[2].Meta.ShouldContainKey("hasHighPriority"); + responseDocument.Data.ManyValue[2].Meta.Should().ContainKey("hasHighPriority"); hitCounter.HitExtensibilityPoints.Should().BeEquivalentTo(new[] { diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/Meta/TopLevelCountTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/Meta/TopLevelCountTests.cs index 13bb952..c85635d 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/Meta/TopLevelCountTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/Meta/TopLevelCountTests.cs @@ -4,7 +4,6 @@ using JsonApiDotNetCore.Resources; using JsonApiDotNetCore.Serialization.Objects; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; using TestBuildingBlocks; using Xunit; @@ -23,7 +22,7 @@ public TopLevelCountTests(IntegrationTestContext testContext.UseController(); - testContext.ConfigureServices(services => services.TryAddScoped(typeof(IResourceChangeTracker<>), typeof(NeverSameResourceChangeTracker<>))); + testContext.ConfigureServices(services => services.AddScoped(typeof(IResourceChangeTracker<>), typeof(NeverSameResourceChangeTracker<>))); var options = (JsonApiOptions)testContext.Factory.Services.GetRequiredService(); options.IncludeTotalResourceCount = true; @@ -33,7 +32,7 @@ public TopLevelCountTests(IntegrationTestContext public async Task Renders_resource_count_for_collection() { // Arrange - SupportTicket ticket = _fakers.SupportTicket.Generate(); + SupportTicket ticket = _fakers.SupportTicket.GenerateOne(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -50,8 +49,6 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Meta.ShouldNotBeNull(); - responseDocument.Meta.Should().ContainTotal(1); } @@ -69,8 +66,6 @@ public async Task Renders_resource_count_for_empty_collection() // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Meta.ShouldNotBeNull(); - responseDocument.Meta.Should().ContainTotal(0); } @@ -78,7 +73,7 @@ public async Task Renders_resource_count_for_empty_collection() public async Task Hides_resource_count_in_create_resource_response() { // Arrange - string newDescription = _fakers.SupportTicket.Generate().Description; + string newDescription = _fakers.SupportTicket.GenerateOne().Description; var requestBody = new { @@ -107,9 +102,9 @@ public async Task Hides_resource_count_in_create_resource_response() public async Task Hides_resource_count_in_update_resource_response() { // Arrange - SupportTicket existingTicket = _fakers.SupportTicket.Generate(); + SupportTicket existingTicket = _fakers.SupportTicket.GenerateOne(); - string newDescription = _fakers.SupportTicket.Generate().Description; + string newDescription = _fakers.SupportTicket.GenerateOne().Description; await _testContext.RunOnDatabaseAsync(async dbContext => { diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Filtering/FilterDataTypeTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Filtering/FilterDataTypeTests.cs index a74e6ee..7027b2a 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Filtering/FilterDataTypeTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Filtering/FilterDataTypeTests.cs @@ -1,11 +1,14 @@ using System.Globalization; using System.Net; using System.Reflection; +using System.Text.Json.Serialization; using System.Web; using FluentAssertions; using FluentAssertions.Extensions; using Humanizer; +using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Serialization.Objects; +using Microsoft.Extensions.DependencyInjection; using TestBuildingBlocks; using Xunit; @@ -22,6 +25,13 @@ public FilterDataTypeTests(IntegrationTestContext(); + + var options = (JsonApiOptions)testContext.Factory.Services.GetRequiredService(); + + if (!options.SerializerOptions.Converters.Any(converter => converter is JsonStringEnumConverter)) + { + options.SerializerOptions.Converters.Add(new JsonStringEnumConverter()); + } } [Theory] @@ -62,8 +72,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Data.ManyValue.ShouldHaveCount(1); - responseDocument.Data.ManyValue[0].Attributes.ShouldContainKey(attributeName).With(value => value.Should().Be(value)); + responseDocument.Data.ManyValue.Should().HaveCount(1); + responseDocument.Data.ManyValue[0].Attributes.Should().ContainKey(attributeName).WhoseValue.Should().Be(propertyValue); } [Fact] @@ -90,8 +100,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Data.ManyValue.ShouldHaveCount(1); - responseDocument.Data.ManyValue[0].Attributes.ShouldContainKey("someDecimal").With(value => value.Should().Be(resource.SomeDecimal)); + responseDocument.Data.ManyValue.Should().HaveCount(1); + responseDocument.Data.ManyValue[0].Attributes.Should().ContainKey("someDecimal").WhoseValue.Should().Be(resource.SomeDecimal); } [Fact] @@ -118,8 +128,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Data.ManyValue.ShouldHaveCount(1); - responseDocument.Data.ManyValue[0].Attributes.ShouldContainKey("someGuid").With(value => value.Should().Be(resource.SomeGuid)); + responseDocument.Data.ManyValue.Should().HaveCount(1); + responseDocument.Data.ManyValue[0].Attributes.Should().ContainKey("someGuid").WhoseValue.Should().Be(resource.SomeGuid); } [Fact] @@ -146,10 +156,9 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Data.ManyValue.ShouldHaveCount(1); + responseDocument.Data.ManyValue.Should().HaveCount(1); - responseDocument.Data.ManyValue[0].Attributes.ShouldContainKey("someDateTimeInLocalZone") - .With(value => value.Should().Be(resource.SomeDateTimeInLocalZone)); + responseDocument.Data.ManyValue[0].Attributes.Should().ContainKey("someDateTimeInLocalZone").WhoseValue.Should().Be(resource.SomeDateTimeInLocalZone); } [Fact] @@ -176,10 +185,9 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Data.ManyValue.ShouldHaveCount(1); + responseDocument.Data.ManyValue.Should().HaveCount(1); - responseDocument.Data.ManyValue[0].Attributes.ShouldContainKey("someDateTimeInUtcZone") - .With(value => value.Should().Be(resource.SomeDateTimeInUtcZone)); + responseDocument.Data.ManyValue[0].Attributes.Should().ContainKey("someDateTimeInUtcZone").WhoseValue.Should().Be(resource.SomeDateTimeInUtcZone); } [Fact] @@ -206,8 +214,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Data.ManyValue.ShouldHaveCount(1); - responseDocument.Data.ManyValue[0].Attributes.ShouldContainKey("someDateTimeOffset").With(value => value.Should().Be(resource.SomeDateTimeOffset)); + responseDocument.Data.ManyValue.Should().HaveCount(1); + responseDocument.Data.ManyValue[0].Attributes.Should().ContainKey("someDateTimeOffset").WhoseValue.Should().Be(resource.SomeDateTimeOffset); } [Fact] @@ -234,8 +242,64 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Data.ManyValue.ShouldHaveCount(1); - responseDocument.Data.ManyValue[0].Attributes.ShouldContainKey("someTimeSpan").With(value => value.Should().Be(resource.SomeTimeSpan)); + responseDocument.Data.ManyValue.Should().HaveCount(1); + responseDocument.Data.ManyValue[0].Attributes.Should().ContainKey("someTimeSpan").WhoseValue.Should().Be(resource.SomeTimeSpan); + } + + [Fact] + public async Task Can_filter_equality_on_type_DateOnly() + { + // Arrange + var resource = new FilterableResource + { + SomeDateOnly = DateOnly.FromDateTime(27.January(2003)) + }; + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + await dbContext.ClearTableAsync(); + dbContext.FilterableResources.AddRange(resource, new FilterableResource()); + await dbContext.SaveChangesAsync(); + }); + + string route = $"/filterableResources?filter=equals(someDateOnly,'{resource.SomeDateOnly:O}')"; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); + + // Assert + httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); + + responseDocument.Data.ManyValue.Should().HaveCount(1); + responseDocument.Data.ManyValue[0].Attributes.Should().ContainKey("someDateOnly").WhoseValue.Should().Be(resource.SomeDateOnly); + } + + [Fact] + public async Task Can_filter_equality_on_type_TimeOnly() + { + // Arrange + var resource = new FilterableResource + { + SomeTimeOnly = new TimeOnly(23, 59, 59, 999) + }; + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + await dbContext.ClearTableAsync(); + dbContext.FilterableResources.AddRange(resource, new FilterableResource()); + await dbContext.SaveChangesAsync(); + }); + + string route = $"/filterableResources?filter=equals(someTimeOnly,'{resource.SomeTimeOnly:O}')"; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); + + // Assert + httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); + + responseDocument.Data.ManyValue.Should().HaveCount(1); + responseDocument.Data.ManyValue[0].Attributes.Should().ContainKey("someTimeOnly").WhoseValue.Should().Be(resource.SomeTimeOnly); } [Fact] @@ -254,7 +318,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await dbContext.SaveChangesAsync(); }); - const string route = "/filterableResources?filter=equals(someInt32,'ABC')"; + var parameterValue = new MarkedText("equals(someInt32,^'ABC')", '^'); + string route = $"/filterableResources?filter={parameterValue.Text}"; // Act (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); @@ -262,13 +327,13 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); error.Title.Should().Be("The specified filter is invalid."); - error.Detail.Should().StartWith("Failed to convert 'ABC' of type 'String' to type 'Int32'."); - error.Source.ShouldNotBeNull(); + error.Detail.Should().Be($"Failed to convert 'ABC' of type 'String' to type 'Int32'. {parameterValue}"); + error.Source.Should().NotBeNull(); error.Source.Parameter.Should().Be("filter"); } @@ -283,6 +348,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => [InlineData(nameof(FilterableResource.SomeNullableDateTime))] [InlineData(nameof(FilterableResource.SomeNullableDateTimeOffset))] [InlineData(nameof(FilterableResource.SomeNullableTimeSpan))] + [InlineData(nameof(FilterableResource.SomeNullableDateOnly))] + [InlineData(nameof(FilterableResource.SomeNullableTimeOnly))] [InlineData(nameof(FilterableResource.SomeNullableEnum))] public async Task Can_filter_is_null_on_type(string propertyName) { @@ -303,6 +370,8 @@ public async Task Can_filter_is_null_on_type(string propertyName) SomeNullableDateTime = 1.January(2001).AsUtc(), SomeNullableDateTimeOffset = 1.January(2001).AsUtc(), SomeNullableTimeSpan = TimeSpan.FromHours(1), + SomeNullableDateOnly = DateOnly.FromDateTime(1.January(2001)), + SomeNullableTimeOnly = new TimeOnly(1, 0), SomeNullableEnum = DayOfWeek.Friday }; @@ -322,8 +391,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Data.ManyValue.ShouldHaveCount(1); - responseDocument.Data.ManyValue[0].Attributes.ShouldContainKey(attributeName).With(value => value.Should().BeNull()); + responseDocument.Data.ManyValue.Should().HaveCount(1); + responseDocument.Data.ManyValue[0].Attributes.Should().ContainKey(attributeName).WhoseValue.Should().BeNull(); } [Theory] @@ -337,6 +406,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => [InlineData(nameof(FilterableResource.SomeNullableDateTime))] [InlineData(nameof(FilterableResource.SomeNullableDateTimeOffset))] [InlineData(nameof(FilterableResource.SomeNullableTimeSpan))] + [InlineData(nameof(FilterableResource.SomeNullableDateOnly))] + [InlineData(nameof(FilterableResource.SomeNullableTimeOnly))] [InlineData(nameof(FilterableResource.SomeNullableEnum))] public async Task Can_filter_is_not_null_on_type(string propertyName) { @@ -353,6 +424,8 @@ public async Task Can_filter_is_not_null_on_type(string propertyName) SomeNullableDateTime = 1.January(2001).AsUtc(), SomeNullableDateTimeOffset = 1.January(2001).AsUtc(), SomeNullableTimeSpan = TimeSpan.FromHours(1), + SomeNullableDateOnly = DateOnly.FromDateTime(1.January(2001)), + SomeNullableTimeOnly = new TimeOnly(1, 0), SomeNullableEnum = DayOfWeek.Friday }; @@ -372,7 +445,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Data.ManyValue.ShouldHaveCount(1); - responseDocument.Data.ManyValue[0].Attributes.ShouldContainKey(attributeName).With(value => value.Should().NotBeNull()); + responseDocument.Data.ManyValue.Should().HaveCount(1); + responseDocument.Data.ManyValue[0].Attributes.Should().ContainKey(attributeName).WhoseValue.Should().NotBeNull(); } } diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Filtering/FilterDbContext.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Filtering/FilterDbContext.cs index 58277b3..0d02f23 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Filtering/FilterDbContext.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Filtering/FilterDbContext.cs @@ -5,7 +5,8 @@ namespace JsonApiDotNetCoreMongoDbTests.IntegrationTests.QueryStrings.Filtering; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class FilterDbContext(IMongoDatabase database) : MongoDbContextShim(database) +public sealed class FilterDbContext(IMongoDatabase database) + : MongoDbContextShim(database) { public MongoDbSetShim FilterableResources => Set(); } diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Filtering/FilterDepthTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Filtering/FilterDepthTests.cs index fccf739..144fda5 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Filtering/FilterDepthTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Filtering/FilterDepthTests.cs @@ -25,7 +25,7 @@ public FilterDepthTests(IntegrationTestContext posts = _fakers.BlogPost.Generate(2); + List posts = _fakers.BlogPost.GenerateList(2); posts[0].Caption = "One"; posts[1].Caption = "Two"; @@ -44,7 +44,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Data.ManyValue.ShouldHaveCount(1); + responseDocument.Data.ManyValue.Should().HaveCount(1); responseDocument.Data.ManyValue[0].Id.Should().Be(posts[1].StringId); } @@ -60,7 +60,7 @@ public async Task Cannot_filter_on_ManyToOne_relationship() // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); @@ -81,7 +81,7 @@ public async Task Cannot_filter_on_OneToMany_relationship() // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); @@ -102,7 +102,7 @@ public async Task Cannot_filter_on_ManyToMany_relationship() // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Filtering/FilterOperatorTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Filtering/FilterOperatorTests.cs index efc7075..e14889a 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Filtering/FilterOperatorTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Filtering/FilterOperatorTests.cs @@ -33,6 +33,18 @@ public sealed class FilterOperatorTests : IClassFixture _testContext; public FilterOperatorTests(IntegrationTestContext testContext) @@ -68,8 +80,12 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Data.ManyValue.ShouldHaveCount(1); - responseDocument.Data.ManyValue[0].Attributes.ShouldContainKey("someString").With(value => value.Should().Be(resource.SomeString)); + responseDocument.Data.ManyValue.Should().HaveCount(1); + responseDocument.Data.ManyValue[0].Attributes.Should().ContainKey("someString").WhoseValue.Should().Be(resource.SomeString); + + responseDocument.Links.Should().NotBeNull(); + responseDocument.Links.Self.Should().Be("http://localhost/filterableResources?filter=equals(someString,'This%2c+that+%26+more+%2b+some')"); + responseDocument.Links.First.Should().Be("http://localhost/filterableResources?filter=equals(someString,%27This,%20that%20%26%20more%20%2B%20some%27)"); } [Fact] @@ -84,12 +100,13 @@ public async Task Cannot_filter_equality_on_two_attributes() // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); error.Title.Should().Be("Comparing attributes against each other is not supported when using MongoDB."); error.Detail.Should().BeNull(); + error.Source.Should().BeNull(); } [Theory] @@ -130,8 +147,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Data.ManyValue.ShouldHaveCount(1); - responseDocument.Data.ManyValue[0].Attributes.ShouldContainKey("someInt32").With(value => value.Should().Be(resource.SomeInt32)); + responseDocument.Data.ManyValue.Should().HaveCount(1); + responseDocument.Data.ManyValue[0].Attributes.Should().ContainKey("someInt32").WhoseValue.Should().Be(resource.SomeInt32); } [Theory] @@ -172,8 +189,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Data.ManyValue.ShouldHaveCount(1); - responseDocument.Data.ManyValue[0].Attributes.ShouldContainKey("someDouble").With(value => value.Should().Be(resource.SomeDouble)); + responseDocument.Data.ManyValue.Should().HaveCount(1); + responseDocument.Data.ManyValue[0].Attributes.Should().ContainKey("someDouble").WhoseValue.Should().Be(resource.SomeDouble); } [Theory] @@ -222,10 +239,9 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Data.ManyValue.ShouldHaveCount(1); + responseDocument.Data.ManyValue.Should().HaveCount(1); - responseDocument.Data.ManyValue[0].Attributes.ShouldContainKey("someDateTimeInUtcZone") - .With(value => value.Should().Be(resource.SomeDateTimeInUtcZone)); + responseDocument.Data.ManyValue[0].Attributes.Should().ContainKey("someDateTimeInUtcZone").WhoseValue.Should().Be(resource.SomeDateTimeInUtcZone); } [Theory] @@ -274,9 +290,9 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Data.ManyValue.ShouldHaveCount(1); + responseDocument.Data.ManyValue.Should().HaveCount(1); - responseDocument.Data.ManyValue[0].Attributes.ShouldContainKey("someDateTimeOffset").With(value => value.Should().Be(resource.SomeDateTimeOffset)); + responseDocument.Data.ManyValue[0].Attributes.Should().ContainKey("someDateTimeOffset").WhoseValue.Should().Be(resource.SomeDateTimeOffset); } [Theory] @@ -316,8 +332,98 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Data.ManyValue.ShouldHaveCount(1); - responseDocument.Data.ManyValue[0].Attributes.ShouldContainKey("someTimeSpan").With(value => value.Should().Be(resource.SomeTimeSpan)); + responseDocument.Data.ManyValue.Should().HaveCount(1); + responseDocument.Data.ManyValue[0].Attributes.Should().ContainKey("someTimeSpan").WhoseValue.Should().Be(resource.SomeTimeSpan); + } + + [Theory] + [InlineData(IsoDateOnlyLowerBound, IsoDateOnlyUpperBound, ComparisonOperator.LessThan, IsoDateOnlyInTheRange)] + [InlineData(IsoDateOnlyLowerBound, IsoDateOnlyUpperBound, ComparisonOperator.LessThan, IsoDateOnlyUpperBound)] + [InlineData(IsoDateOnlyLowerBound, IsoDateOnlyUpperBound, ComparisonOperator.LessOrEqual, IsoDateOnlyInTheRange)] + [InlineData(IsoDateOnlyLowerBound, IsoDateOnlyUpperBound, ComparisonOperator.LessOrEqual, IsoDateOnlyLowerBound)] + [InlineData(IsoDateOnlyUpperBound, IsoDateOnlyLowerBound, ComparisonOperator.GreaterThan, IsoDateOnlyInTheRange)] + [InlineData(IsoDateOnlyUpperBound, IsoDateOnlyLowerBound, ComparisonOperator.GreaterThan, IsoDateOnlyLowerBound)] + [InlineData(IsoDateOnlyUpperBound, IsoDateOnlyLowerBound, ComparisonOperator.GreaterOrEqual, IsoDateOnlyInTheRange)] + [InlineData(IsoDateOnlyUpperBound, IsoDateOnlyLowerBound, ComparisonOperator.GreaterOrEqual, IsoDateOnlyUpperBound)] + [InlineData(InvariantDateOnlyLowerBound, InvariantDateOnlyUpperBound, ComparisonOperator.LessThan, InvariantDateOnlyInTheRange)] + [InlineData(InvariantDateOnlyLowerBound, InvariantDateOnlyUpperBound, ComparisonOperator.LessThan, InvariantDateOnlyUpperBound)] + [InlineData(InvariantDateOnlyLowerBound, InvariantDateOnlyUpperBound, ComparisonOperator.LessOrEqual, InvariantDateOnlyInTheRange)] + [InlineData(InvariantDateOnlyLowerBound, InvariantDateOnlyUpperBound, ComparisonOperator.LessOrEqual, InvariantDateOnlyLowerBound)] + [InlineData(InvariantDateOnlyUpperBound, InvariantDateOnlyLowerBound, ComparisonOperator.GreaterThan, InvariantDateOnlyInTheRange)] + [InlineData(InvariantDateOnlyUpperBound, InvariantDateOnlyLowerBound, ComparisonOperator.GreaterThan, InvariantDateOnlyLowerBound)] + [InlineData(InvariantDateOnlyUpperBound, InvariantDateOnlyLowerBound, ComparisonOperator.GreaterOrEqual, InvariantDateOnlyInTheRange)] + [InlineData(InvariantDateOnlyUpperBound, InvariantDateOnlyLowerBound, ComparisonOperator.GreaterOrEqual, InvariantDateOnlyUpperBound)] + public async Task Can_filter_comparison_on_DateOnly(string matchingValue, string nonMatchingValue, ComparisonOperator filterOperator, string filterValue) + { + // Arrange + var resource = new FilterableResource + { + SomeDateOnly = DateOnly.Parse(matchingValue, CultureInfo.InvariantCulture) + }; + + var otherResource = new FilterableResource + { + SomeDateOnly = DateOnly.Parse(nonMatchingValue, CultureInfo.InvariantCulture) + }; + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + await dbContext.ClearTableAsync(); + dbContext.FilterableResources.AddRange(resource, otherResource); + await dbContext.SaveChangesAsync(); + }); + + string route = $"/filterableResources?filter={filterOperator.ToString().Camelize()}(someDateOnly,'{filterValue}')"; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); + + // Assert + httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); + + responseDocument.Data.ManyValue.Should().HaveCount(1); + responseDocument.Data.ManyValue[0].Attributes.Should().ContainKey("someDateOnly").WhoseValue.Should().Be(resource.SomeDateOnly); + } + + [Theory] + [InlineData(TimeOnlyLowerBound, TimeOnlyUpperBound, ComparisonOperator.LessThan, TimeOnlyInTheRange)] + [InlineData(TimeOnlyLowerBound, TimeOnlyUpperBound, ComparisonOperator.LessThan, TimeOnlyUpperBound)] + [InlineData(TimeOnlyLowerBound, TimeOnlyUpperBound, ComparisonOperator.LessOrEqual, TimeOnlyInTheRange)] + [InlineData(TimeOnlyLowerBound, TimeOnlyUpperBound, ComparisonOperator.LessOrEqual, TimeOnlyLowerBound)] + [InlineData(TimeOnlyUpperBound, TimeOnlyLowerBound, ComparisonOperator.GreaterThan, TimeOnlyInTheRange)] + [InlineData(TimeOnlyUpperBound, TimeOnlyLowerBound, ComparisonOperator.GreaterThan, TimeOnlyLowerBound)] + [InlineData(TimeOnlyUpperBound, TimeOnlyLowerBound, ComparisonOperator.GreaterOrEqual, TimeOnlyInTheRange)] + [InlineData(TimeOnlyUpperBound, TimeOnlyLowerBound, ComparisonOperator.GreaterOrEqual, TimeOnlyUpperBound)] + public async Task Can_filter_comparison_on_TimeOnly(string matchingValue, string nonMatchingValue, ComparisonOperator filterOperator, string filterValue) + { + // Arrange + var resource = new FilterableResource + { + SomeTimeOnly = TimeOnly.Parse(matchingValue, CultureInfo.InvariantCulture) + }; + + var otherResource = new FilterableResource + { + SomeTimeOnly = TimeOnly.Parse(nonMatchingValue, CultureInfo.InvariantCulture) + }; + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + await dbContext.ClearTableAsync(); + dbContext.FilterableResources.AddRange(resource, otherResource); + await dbContext.SaveChangesAsync(); + }); + + string route = $"/filterableResources?filter={filterOperator.ToString().Camelize()}(someTimeOnly,'{filterValue}')"; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); + + // Assert + httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); + + responseDocument.Data.ManyValue.Should().HaveCount(1); + responseDocument.Data.ManyValue[0].Attributes.Should().ContainKey("someTimeOnly").WhoseValue.Should().Be(resource.SomeTimeOnly); } [Theory] @@ -354,8 +460,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Data.ManyValue.ShouldHaveCount(1); - responseDocument.Data.ManyValue[0].Attributes.ShouldContainKey("someString").With(value => value.Should().Be(resource.SomeString)); + responseDocument.Data.ManyValue.Should().HaveCount(1); + responseDocument.Data.ManyValue[0].Attributes.Should().ContainKey("someString").WhoseValue.Should().Be(resource.SomeString); } [Theory] @@ -390,8 +496,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Data.ManyValue.ShouldHaveCount(1); - responseDocument.Data.ManyValue[0].Attributes.ShouldContainKey("someString").With(value => value.Should().Be(resource.SomeString)); + responseDocument.Data.ManyValue.Should().HaveCount(1); + responseDocument.Data.ManyValue[0].Attributes.Should().ContainKey("someString").WhoseValue.Should().Be(resource.SomeString); } [Fact] @@ -406,7 +512,7 @@ public async Task Cannot_filter_on_has() // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); @@ -427,7 +533,7 @@ public async Task Cannot_filter_on_has_with_nested_condition() // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); @@ -448,7 +554,7 @@ public async Task Cannot_filter_on_count() // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); @@ -494,7 +600,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Data.ManyValue.ShouldHaveCount(1); + responseDocument.Data.ManyValue.Should().HaveCount(1); responseDocument.Data.ManyValue[0].Id.Should().Be(resource1.StringId); } } diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Filtering/FilterTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Filtering/FilterTests.cs index c432a77..d572e4d 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Filtering/FilterTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Filtering/FilterTests.cs @@ -24,7 +24,7 @@ public FilterTests(IntegrationTestContext public async Task Can_filter_on_ID() { // Arrange - List accounts = _fakers.WebAccount.Generate(2); + List accounts = _fakers.WebAccount.GenerateList(2); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -41,8 +41,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Data.ManyValue.ShouldHaveCount(1); + responseDocument.Data.ManyValue.Should().HaveCount(1); responseDocument.Data.ManyValue[0].Id.Should().Be(accounts[0].StringId); - responseDocument.Data.ManyValue[0].Attributes.ShouldContainKey("userName").With(value => value.Should().Be(accounts[0].UserName)); + responseDocument.Data.ManyValue[0].Attributes.Should().ContainKey("userName").WhoseValue.Should().Be(accounts[0].UserName); } } diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Filtering/FilterableResource.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Filtering/FilterableResource.cs index 17ec845..6554ce6 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Filtering/FilterableResource.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Filtering/FilterableResource.cs @@ -79,12 +79,28 @@ public sealed class FilterableResource : HexStringMongoIdentifiable [Attr] public TimeSpan? SomeNullableTimeSpan { get; set; } + [Attr] + public DateOnly SomeDateOnly { get; set; } + + [Attr] + public DateOnly? SomeNullableDateOnly { get; set; } + + [Attr] + public TimeOnly SomeTimeOnly { get; set; } + + [Attr] + public TimeOnly? SomeNullableTimeOnly { get; set; } + [Attr] public DayOfWeek SomeEnum { get; set; } [Attr] public DayOfWeek? SomeNullableEnum { get; set; } + [HasOne] + [BsonIgnore] + public FilterableResource? Parent { get; set; } + [HasMany] [BsonIgnore] public ICollection Children { get; set; } = new List(); diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Includes/IncludeTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Includes/IncludeTests.cs index 2fcbfe5..e72fbfc 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Includes/IncludeTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Includes/IncludeTests.cs @@ -31,7 +31,7 @@ public async Task Cannot_include_in_primary_resources() // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Pagination/PaginationWithTotalCountTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Pagination/PaginationWithTotalCountTests.cs index 96e031b..03567be 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Pagination/PaginationWithTotalCountTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Pagination/PaginationWithTotalCountTests.cs @@ -31,7 +31,7 @@ public PaginationWithTotalCountTests(IntegrationTestContext posts = _fakers.BlogPost.Generate(2); + List posts = _fakers.BlogPost.GenerateList(2); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -48,10 +48,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Data.ManyValue.ShouldHaveCount(1); + responseDocument.Data.ManyValue.Should().HaveCount(1); responseDocument.Data.ManyValue[0].Id.Should().Be(posts[1].StringId); - responseDocument.Links.ShouldNotBeNull(); + responseDocument.Links.Should().NotBeNull(); responseDocument.Links.Self.Should().Be($"{HostPrefix}{route}"); responseDocument.Links.First.Should().Be($"{HostPrefix}/blogPosts?page%5Bsize%5D=1"); responseDocument.Links.Last.Should().Be($"{HostPrefix}/blogPosts?page%5Bnumber%5D=2&page%5Bsize%5D=1"); @@ -66,7 +66,7 @@ public async Task Uses_default_page_number_and_size() var options = (JsonApiOptions)_testContext.Factory.Services.GetRequiredService(); options.DefaultPageSize = new PageSize(2); - List posts = _fakers.BlogPost.Generate(3); + List posts = _fakers.BlogPost.GenerateList(3); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -83,11 +83,11 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Data.ManyValue.ShouldHaveCount(2); + responseDocument.Data.ManyValue.Should().HaveCount(2); responseDocument.Data.ManyValue[0].Id.Should().Be(posts[0].StringId); responseDocument.Data.ManyValue[1].Id.Should().Be(posts[1].StringId); - responseDocument.Links.ShouldNotBeNull(); + responseDocument.Links.Should().NotBeNull(); responseDocument.Links.Self.Should().Be($"{HostPrefix}{route}"); responseDocument.Links.First.Should().Be(responseDocument.Links.Self); responseDocument.Links.Last.Should().Be($"{HostPrefix}{route}?page%5Bnumber%5D=2"); @@ -102,7 +102,7 @@ public async Task Returns_all_resources_when_pagination_is_disabled() var options = (JsonApiOptions)_testContext.Factory.Services.GetRequiredService(); options.DefaultPageSize = null; - List posts = _fakers.BlogPost.Generate(25); + List posts = _fakers.BlogPost.GenerateList(25); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -119,9 +119,9 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Data.ManyValue.ShouldHaveCount(25); + responseDocument.Data.ManyValue.Should().HaveCount(25); - responseDocument.Links.ShouldNotBeNull(); + responseDocument.Links.Should().NotBeNull(); responseDocument.Links.Self.Should().Be($"{HostPrefix}{route}"); responseDocument.Links.First.Should().BeNull(); responseDocument.Links.Last.Should().BeNull(); diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Pagination/RangeValidationTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Pagination/RangeValidationTests.cs index 28a7c06..2790fb8 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Pagination/RangeValidationTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Pagination/RangeValidationTests.cs @@ -24,7 +24,7 @@ public RangeValidationTests(IntegrationTestContext blogs = _fakers.Blog.Generate(3); + List blogs = _fakers.Blog.GenerateList(3); await _testContext.RunOnDatabaseAsync(async dbContext => { diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/QueryStringDbContext.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/QueryStringDbContext.cs index a1e48b6..a80dda2 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/QueryStringDbContext.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/QueryStringDbContext.cs @@ -5,7 +5,8 @@ namespace JsonApiDotNetCoreMongoDbTests.IntegrationTests.QueryStrings; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class QueryStringDbContext(IMongoDatabase database) : MongoDbContextShim(database) +public sealed class QueryStringDbContext(IMongoDatabase database) + : MongoDbContextShim(database) { public MongoDbSetShim Blogs => Set(); public MongoDbSetShim Posts => Set(); diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Sorting/SortTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Sorting/SortTests.cs index a5269d7..6c6fc94 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Sorting/SortTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/Sorting/SortTests.cs @@ -26,7 +26,7 @@ public SortTests(IntegrationTestContext t public async Task Can_sort_in_primary_resources() { // Arrange - List posts = _fakers.BlogPost.Generate(3); + List posts = _fakers.BlogPost.GenerateList(3); posts[0].Caption = "B"; posts[1].Caption = "A"; posts[2].Caption = "C"; @@ -46,7 +46,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Data.ManyValue.ShouldHaveCount(3); + responseDocument.Data.ManyValue.Should().HaveCount(3); responseDocument.Data.ManyValue[0].Id.Should().Be(posts[1].StringId); responseDocument.Data.ManyValue[1].Id.Should().Be(posts[0].StringId); responseDocument.Data.ManyValue[2].Id.Should().Be(posts[2].StringId); @@ -64,7 +64,7 @@ public async Task Cannot_sort_on_OneToMany_relationship() // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); @@ -85,7 +85,7 @@ public async Task Cannot_sort_on_ManyToMany_relationship() // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); @@ -106,7 +106,7 @@ public async Task Cannot_sort_on_ManyToOne_relationship() // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); @@ -119,7 +119,7 @@ public async Task Cannot_sort_on_ManyToOne_relationship() public async Task Can_sort_descending_by_ID() { // Arrange - List accounts = _fakers.WebAccount.Generate(3); + List accounts = _fakers.WebAccount.GenerateList(3); accounts[0].Id = "5ff752c4f7c9a9a8373991b2"; accounts[1].Id = "5ff752c3f7c9a9a8373991b1"; accounts[2].Id = "5ff752c2f7c9a9a8373991b0"; @@ -143,7 +143,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Data.ManyValue.ShouldHaveCount(3); + responseDocument.Data.ManyValue.Should().HaveCount(3); responseDocument.Data.ManyValue[0].Id.Should().Be(accounts[1].StringId); responseDocument.Data.ManyValue[1].Id.Should().Be(accounts[2].StringId); responseDocument.Data.ManyValue[2].Id.Should().Be(accounts[0].StringId); @@ -153,7 +153,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Sorts_by_ID_if_none_specified() { // Arrange - List accounts = _fakers.WebAccount.Generate(4); + List accounts = _fakers.WebAccount.GenerateList(4); accounts[0].Id = "5ff8a7bcb2a9b83724282718"; accounts[1].Id = "5ff8a7bcb2a9b83724282717"; accounts[2].Id = "5ff8a7bbb2a9b83724282716"; @@ -174,7 +174,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Data.ManyValue.ShouldHaveCount(4); + responseDocument.Data.ManyValue.Should().HaveCount(4); responseDocument.Data.ManyValue[0].Id.Should().Be(accounts[2].StringId); responseDocument.Data.ManyValue[1].Id.Should().Be(accounts[1].StringId); responseDocument.Data.ManyValue[2].Id.Should().Be(accounts[0].StringId); diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/SparseFieldSets/ResultCapturingRepository.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/SparseFieldSets/ResultCapturingRepository.cs index f00897c..80fc442 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/SparseFieldSets/ResultCapturingRepository.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/SparseFieldSets/ResultCapturingRepository.cs @@ -14,8 +14,9 @@ namespace JsonApiDotNetCoreMongoDbTests.IntegrationTests.QueryStrings.SparseFiel public sealed class ResultCapturingRepository( IMongoDataAccess mongoDataAccess, ITargetedFields targetedFields, IResourceGraph resourceGraph, IResourceFactory resourceFactory, IEnumerable constraintProviders, IResourceDefinitionAccessor resourceDefinitionAccessor, IQueryableBuilder queryableBuilder, - ResourceCaptureStore captureStore) : MongoRepository(mongoDataAccess, targetedFields, resourceGraph, resourceFactory, constraintProviders, - resourceDefinitionAccessor, queryableBuilder) + ResourceCaptureStore captureStore) + : MongoRepository(mongoDataAccess, targetedFields, resourceGraph, resourceFactory, constraintProviders, resourceDefinitionAccessor, + queryableBuilder) where TResource : class, IIdentifiable { private readonly ResourceCaptureStore _captureStore = captureStore; diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/SparseFieldSets/SparseFieldSetTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/SparseFieldSets/SparseFieldSetTests.cs index 8b94f13..8879c97 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/SparseFieldSets/SparseFieldSetTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/SparseFieldSets/SparseFieldSetTests.cs @@ -3,7 +3,6 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Serialization.Objects; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; using TestBuildingBlocks; using Xunit; @@ -26,11 +25,11 @@ public SparseFieldSetTests(IntegrationTestContext { - services.TryAddSingleton(); - services.AddResourceRepository>(); services.AddResourceRepository>(); services.AddResourceRepository>(); + + services.AddSingleton(); }); } @@ -46,7 +45,7 @@ public async Task Cannot_select_fields_with_relationship_in_primary_resources() // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); @@ -62,7 +61,7 @@ public async Task Can_select_attribute_in_primary_resources() var store = _testContext.Factory.Services.GetRequiredService(); store.Clear(); - BlogPost post = _fakers.BlogPost.Generate(); + BlogPost post = _fakers.BlogPost.GenerateOne(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -79,10 +78,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Data.ManyValue.ShouldHaveCount(1); + responseDocument.Data.ManyValue.Should().HaveCount(1); responseDocument.Data.ManyValue[0].Id.Should().Be(post.StringId); - responseDocument.Data.ManyValue[0].Attributes.ShouldHaveCount(1); - responseDocument.Data.ManyValue[0].Attributes.ShouldContainKey("caption").With(value => value.Should().Be(post.Caption)); + responseDocument.Data.ManyValue[0].Attributes.Should().HaveCount(1); + responseDocument.Data.ManyValue[0].Attributes.Should().ContainKey("caption").WhoseValue.Should().Be(post.Caption); responseDocument.Data.ManyValue[0].Relationships.Should().BeNull(); var postCaptured = (BlogPost)store.Resources.Should().ContainSingle(resource => resource is BlogPost).Which; @@ -102,7 +101,7 @@ public async Task Cannot_select_relationship_in_primary_resources() // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); @@ -118,7 +117,7 @@ public async Task Can_select_attribute_in_primary_resource_by_ID() var store = _testContext.Factory.Services.GetRequiredService(); store.Clear(); - BlogPost post = _fakers.BlogPost.Generate(); + BlogPost post = _fakers.BlogPost.GenerateOne(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -134,10 +133,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Data.SingleValue.ShouldNotBeNull(); + responseDocument.Data.SingleValue.Should().NotBeNull(); responseDocument.Data.SingleValue.Id.Should().Be(post.StringId); - responseDocument.Data.SingleValue.Attributes.ShouldHaveCount(1); - responseDocument.Data.SingleValue.Attributes.ShouldContainKey("url").With(value => value.Should().Be(post.Url)); + responseDocument.Data.SingleValue.Attributes.Should().HaveCount(1); + responseDocument.Data.SingleValue.Attributes.Should().ContainKey("url").WhoseValue.Should().Be(post.Url); responseDocument.Data.SingleValue.Relationships.Should().BeNull(); var postCaptured = (BlogPost)store.Resources.Should().ContainSingle(resource => resource is BlogPost).Which; @@ -149,7 +148,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Cannot_select_fields_of_ManyToOne_relationship() { // Arrange - BlogPost post = _fakers.BlogPost.Generate(); + BlogPost post = _fakers.BlogPost.GenerateOne(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -165,7 +164,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); @@ -178,7 +177,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Cannot_select_fields_of_OneToMany_relationship() { // Arrange - WebAccount account = _fakers.WebAccount.Generate(); + WebAccount account = _fakers.WebAccount.GenerateOne(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -194,7 +193,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); @@ -207,7 +206,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Cannot_select_fields_of_ManyToMany_relationship() { // Arrange - BlogPost post = _fakers.BlogPost.Generate(); + BlogPost post = _fakers.BlogPost.GenerateOne(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -223,7 +222,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); @@ -239,7 +238,7 @@ public async Task Can_select_ID() var store = _testContext.Factory.Services.GetRequiredService(); store.Clear(); - BlogPost post = _fakers.BlogPost.Generate(); + BlogPost post = _fakers.BlogPost.GenerateOne(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -256,10 +255,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Data.ManyValue.ShouldHaveCount(1); + responseDocument.Data.ManyValue.Should().HaveCount(1); responseDocument.Data.ManyValue[0].Id.Should().Be(post.StringId); - responseDocument.Data.ManyValue[0].Attributes.ShouldHaveCount(1); - responseDocument.Data.ManyValue[0].Attributes.ShouldContainKey("caption").With(value => value.Should().Be(post.Caption)); + responseDocument.Data.ManyValue[0].Attributes.Should().HaveCount(1); + responseDocument.Data.ManyValue[0].Attributes.Should().ContainKey("caption").WhoseValue.Should().Be(post.Caption); responseDocument.Data.ManyValue[0].Relationships.Should().BeNull(); var postCaptured = (BlogPost)store.Resources.Should().ContainSingle(resource => resource is BlogPost).Which; @@ -275,7 +274,7 @@ public async Task Can_select_empty_fieldset() var store = _testContext.Factory.Services.GetRequiredService(); store.Clear(); - BlogPost post = _fakers.BlogPost.Generate(); + BlogPost post = _fakers.BlogPost.GenerateOne(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -292,7 +291,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Data.ManyValue.ShouldHaveCount(1); + responseDocument.Data.ManyValue.Should().HaveCount(1); responseDocument.Data.ManyValue[0].Id.Should().Be(post.StringId); responseDocument.Data.ManyValue[0].Attributes.Should().BeNull(); responseDocument.Data.ManyValue[0].Relationships.Should().BeNull(); @@ -309,7 +308,7 @@ public async Task Fetches_all_scalar_properties_when_fieldset_contains_readonly_ var store = _testContext.Factory.Services.GetRequiredService(); store.Clear(); - Blog blog = _fakers.Blog.Generate(); + Blog blog = _fakers.Blog.GenerateOne(); blog.IsPublished = true; await _testContext.RunOnDatabaseAsync(async dbContext => @@ -326,10 +325,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Data.SingleValue.ShouldNotBeNull(); + responseDocument.Data.SingleValue.Should().NotBeNull(); responseDocument.Data.SingleValue.Id.Should().Be(blog.StringId); - responseDocument.Data.SingleValue.Attributes.ShouldHaveCount(1); - responseDocument.Data.SingleValue.Attributes.ShouldContainKey("showAdvertisements").With(value => value.Should().Be(blog.ShowAdvertisements)); + responseDocument.Data.SingleValue.Attributes.Should().HaveCount(1); + responseDocument.Data.SingleValue.Attributes.Should().ContainKey("showAdvertisements").WhoseValue.Should().Be(blog.ShowAdvertisements); responseDocument.Data.SingleValue.Relationships.Should().BeNull(); var blogCaptured = (Blog)store.Resources.Should().ContainSingle(resource => resource is Blog).Which; diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/WebAccount.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/WebAccount.cs index de3c673..e3c2e1c 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/WebAccount.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/QueryStrings/WebAccount.cs @@ -12,7 +12,7 @@ public sealed class WebAccount : HexStringMongoIdentifiable [Attr] public string UserName { get; set; } = null!; - [Attr(Capabilities = ~AttrCapabilities.AllowView)] + [Attr(Capabilities = AttrCapabilities.All & ~AttrCapabilities.AllowView)] public string Password { get; set; } = null!; [Attr] diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Creating/CreateResourceTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Creating/CreateResourceTests.cs index ccc1ec6..36006e2 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Creating/CreateResourceTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Creating/CreateResourceTests.cs @@ -26,7 +26,7 @@ public CreateResourceTests(IntegrationTestContext value.Should().Be(newWorkItem.Description)); - responseDocument.Data.SingleValue.Attributes.ShouldContainKey("dueAt").With(value => value.Should().Be(newWorkItem.DueAt)); + responseDocument.Data.SingleValue.Attributes.Should().ContainKey("description").WhoseValue.Should().Be(newWorkItem.Description); + responseDocument.Data.SingleValue.Attributes.Should().ContainKey("dueAt").WhoseValue.Should().Be(newWorkItem.DueAt); responseDocument.Data.SingleValue.Relationships.Should().BeNull(); - string newWorkItemId = responseDocument.Data.SingleValue.Id.ShouldNotBeNull(); + string newWorkItemId = responseDocument.Data.SingleValue.Id.Should().NotBeNull().And.Subject; await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -90,7 +90,7 @@ public async Task Cannot_create_resource_with_int_ID() // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.InternalServerError); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.InternalServerError); @@ -124,13 +124,13 @@ public async Task Can_create_resource_without_attributes_or_relationships() // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.Created); - responseDocument.Data.SingleValue.ShouldNotBeNull(); + responseDocument.Data.SingleValue.Should().NotBeNull(); responseDocument.Data.SingleValue.Type.Should().Be("workItems"); - responseDocument.Data.SingleValue.Attributes.ShouldContainKey("description").With(value => value.Should().BeNull()); - responseDocument.Data.SingleValue.Attributes.ShouldContainKey("dueAt").With(value => value.Should().BeNull()); + responseDocument.Data.SingleValue.Attributes.Should().ContainKey("description").WhoseValue.Should().BeNull(); + responseDocument.Data.SingleValue.Attributes.Should().ContainKey("dueAt").WhoseValue.Should().BeNull(); responseDocument.Data.SingleValue.Relationships.Should().BeNull(); - string newWorkItemId = responseDocument.Data.SingleValue.Id.ShouldNotBeNull(); + string newWorkItemId = responseDocument.Data.SingleValue.Id.Should().NotBeNull().And.Subject; await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -166,14 +166,14 @@ public async Task Cannot_create_resource_with_client_generated_ID() // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.Forbidden); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.Forbidden); error.Title.Should().Be("Failed to deserialize request body: The use of client-generated IDs is disabled."); error.Detail.Should().BeNull(); - error.Source.ShouldNotBeNull(); + error.Source.Should().NotBeNull(); error.Source.Pointer.Should().Be("/data/id"); - error.Meta.ShouldContainKey("requestBody").With(value => value.ShouldNotBeNull().ToString().ShouldNotBeEmpty()); + error.Meta.Should().HaveRequestBody(); } } diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Creating/CreateResourceWithClientGeneratedIdTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Creating/CreateResourceWithClientGeneratedIdTests.cs index c9c732a..1f40db8 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Creating/CreateResourceWithClientGeneratedIdTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Creating/CreateResourceWithClientGeneratedIdTests.cs @@ -32,7 +32,7 @@ public CreateResourceWithClientGeneratedIdTests(IntegrationTestContext value.Should().Be(groupName)); + responseDocument.Data.SingleValue.Attributes.Should().ContainKey("name").WhoseValue.Should().Be(groupName); responseDocument.Data.SingleValue.Relationships.Should().BeNull(); await _testContext.RunOnDatabaseAsync(async dbContext => @@ -76,7 +76,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_create_resource_with_client_generated_string_ID_having_side_effects_with_fieldset() { // Arrange - WorkItemGroup newGroup = _fakers.WorkItemGroup.Generate(); + WorkItemGroup newGroup = _fakers.WorkItemGroup.GenerateOne(); newGroup.Id = "free-format-client-generated-id-2"; var requestBody = new @@ -102,11 +102,11 @@ public async Task Can_create_resource_with_client_generated_string_ID_having_sid string groupName = $"{newGroup.Name}{ImplicitlyChangingWorkItemGroupDefinition.Suffix}"; - responseDocument.Data.SingleValue.ShouldNotBeNull(); + responseDocument.Data.SingleValue.Should().NotBeNull(); responseDocument.Data.SingleValue.Type.Should().Be("workItemGroups"); responseDocument.Data.SingleValue.Id.Should().Be(newGroup.StringId); - responseDocument.Data.SingleValue.Attributes.ShouldHaveCount(1); - responseDocument.Data.SingleValue.Attributes.ShouldContainKey("name").With(value => value.Should().Be(groupName)); + responseDocument.Data.SingleValue.Attributes.Should().HaveCount(1); + responseDocument.Data.SingleValue.Attributes.Should().ContainKey("name").WhoseValue.Should().Be(groupName); responseDocument.Data.SingleValue.Relationships.Should().BeNull(); await _testContext.RunOnDatabaseAsync(async dbContext => @@ -121,7 +121,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_create_resource_with_client_generated_string_ID_having_no_side_effects() { // Arrange - RgbColor newColor = _fakers.RgbColor.Generate(); + RgbColor newColor = _fakers.RgbColor.GenerateOne(); newColor.Id = "free-format-client-generated-id-3"; var requestBody = new @@ -159,7 +159,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_create_resource_with_client_generated_string_ID_having_no_side_effects_with_fieldset() { // Arrange - RgbColor newColor = _fakers.RgbColor.Generate(); + RgbColor newColor = _fakers.RgbColor.GenerateOne(); newColor.Id = "free-format-client-generated-id-4"; var requestBody = new @@ -197,10 +197,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Cannot_create_resource_for_existing_client_generated_ID() { // Arrange - RgbColor existingColor = _fakers.RgbColor.Generate(); + RgbColor existingColor = _fakers.RgbColor.GenerateOne(); existingColor.Id = "free-format-client-generated-id-5"; - string newDisplayName = _fakers.RgbColor.Generate().DisplayName; + string newDisplayName = _fakers.RgbColor.GenerateOne().DisplayName; await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -229,7 +229,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.Conflict); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.Conflict); diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Creating/CreateResourceWithToManyRelationshipTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Creating/CreateResourceWithToManyRelationshipTests.cs index e0d92de..c560e56 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Creating/CreateResourceWithToManyRelationshipTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Creating/CreateResourceWithToManyRelationshipTests.cs @@ -24,7 +24,7 @@ public CreateResourceWithToManyRelationshipTests(IntegrationTestContext { @@ -62,7 +62,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Creating/CreateResourceWithToOneRelationshipTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Creating/CreateResourceWithToOneRelationshipTests.cs index be6732c..12d5711 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Creating/CreateResourceWithToOneRelationshipTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Creating/CreateResourceWithToOneRelationshipTests.cs @@ -24,10 +24,10 @@ public CreateResourceWithToOneRelationshipTests(IntegrationTestContext { @@ -67,7 +67,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Deleting/DeleteResourceTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Deleting/DeleteResourceTests.cs index 3e1bb87..70e3307 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Deleting/DeleteResourceTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Deleting/DeleteResourceTests.cs @@ -24,7 +24,7 @@ public DeleteResourceTests(IntegrationTestContext { @@ -44,9 +44,9 @@ await _testContext.RunOnDatabaseAsync(async dbContext => await _testContext.RunOnDatabaseAsync(async dbContext => { - WorkItem? workItemsInDatabase = await dbContext.WorkItems.FirstWithIdOrDefaultAsync(existingWorkItem.Id); + WorkItem? workItemInDatabase = await dbContext.WorkItems.FirstWithIdOrDefaultAsync(existingWorkItem.Id); - workItemsInDatabase.Should().BeNull(); + workItemInDatabase.Should().BeNull(); }); } @@ -64,7 +64,7 @@ public async Task Cannot_delete_unknown_resource() // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.NotFound); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.NotFound); diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Fetching/FetchRelationshipTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Fetching/FetchRelationshipTests.cs index 6a9e32c..af5dee5 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Fetching/FetchRelationshipTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Fetching/FetchRelationshipTests.cs @@ -24,7 +24,7 @@ public FetchRelationshipTests(IntegrationTestContext { @@ -40,7 +40,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); @@ -53,7 +53,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Cannot_get_OneToMany_relationship() { // Arrange - UserAccount userAccount = _fakers.UserAccount.Generate(); + UserAccount userAccount = _fakers.UserAccount.GenerateOne(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -69,7 +69,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); @@ -82,7 +82,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Cannot_get_ManyToMany_relationship() { // Arrange - WorkItem workItem = _fakers.WorkItem.Generate(); + WorkItem workItem = _fakers.WorkItem.GenerateOne(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -98,7 +98,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Fetching/FetchResourceTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Fetching/FetchResourceTests.cs index 39a52d6..dc2aa26 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Fetching/FetchResourceTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Fetching/FetchResourceTests.cs @@ -25,7 +25,7 @@ public FetchResourceTests(IntegrationTestContext workItems = _fakers.WorkItem.Generate(2); + List workItems = _fakers.WorkItem.GenerateList(2); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -42,20 +42,20 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Data.ManyValue.ShouldHaveCount(2); + responseDocument.Data.ManyValue.Should().HaveCount(2); ResourceObject item1 = responseDocument.Data.ManyValue.Single(resource => resource.Id == workItems[0].StringId); item1.Type.Should().Be("workItems"); - item1.Attributes.ShouldContainKey("description").With(value => value.Should().Be(workItems[0].Description)); - item1.Attributes.ShouldContainKey("dueAt").With(value => value.Should().Be(workItems[0].DueAt)); - item1.Attributes.ShouldContainKey("priority").With(value => value.Should().Be(workItems[0].Priority)); + item1.Attributes.Should().ContainKey("description").WhoseValue.Should().Be(workItems[0].Description); + item1.Attributes.Should().ContainKey("dueAt").WhoseValue.Should().Be(workItems[0].DueAt); + item1.Attributes.Should().ContainKey("priority").WhoseValue.Should().Be(workItems[0].Priority); item1.Relationships.Should().BeNull(); ResourceObject item2 = responseDocument.Data.ManyValue.Single(resource => resource.Id == workItems[1].StringId); item2.Type.Should().Be("workItems"); - item2.Attributes.ShouldContainKey("description").With(value => value.Should().Be(workItems[1].Description)); - item2.Attributes.ShouldContainKey("dueAt").With(value => value.Should().Be(workItems[1].DueAt)); - item2.Attributes.ShouldContainKey("priority").With(value => value.Should().Be(workItems[1].Priority)); + item2.Attributes.Should().ContainKey("description").WhoseValue.Should().Be(workItems[1].Description); + item2.Attributes.Should().ContainKey("dueAt").WhoseValue.Should().Be(workItems[1].DueAt); + item2.Attributes.Should().ContainKey("priority").WhoseValue.Should().Be(workItems[1].Priority); item2.Relationships.Should().BeNull(); } @@ -63,7 +63,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_get_primary_resource_by_ID() { // Arrange - WorkItem workItem = _fakers.WorkItem.Generate(); + WorkItem workItem = _fakers.WorkItem.GenerateOne(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -79,12 +79,12 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Data.SingleValue.ShouldNotBeNull(); + responseDocument.Data.SingleValue.Should().NotBeNull(); responseDocument.Data.SingleValue.Type.Should().Be("workItems"); responseDocument.Data.SingleValue.Id.Should().Be(workItem.StringId); - responseDocument.Data.SingleValue.Attributes.ShouldContainKey("description").With(value => value.Should().Be(workItem.Description)); - responseDocument.Data.SingleValue.Attributes.ShouldContainKey("dueAt").With(value => value.Should().Be(workItem.DueAt)); - responseDocument.Data.SingleValue.Attributes.ShouldContainKey("priority").With(value => value.Should().Be(workItem.Priority)); + responseDocument.Data.SingleValue.Attributes.Should().ContainKey("description").WhoseValue.Should().Be(workItem.Description); + responseDocument.Data.SingleValue.Attributes.Should().ContainKey("dueAt").WhoseValue.Should().Be(workItem.DueAt); + responseDocument.Data.SingleValue.Attributes.Should().ContainKey("priority").WhoseValue.Should().Be(workItem.Priority); responseDocument.Data.SingleValue.Relationships.Should().BeNull(); } @@ -102,7 +102,7 @@ public async Task Cannot_get_primary_resource_for_unknown_ID() // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.NotFound); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.NotFound); @@ -114,7 +114,7 @@ public async Task Cannot_get_primary_resource_for_unknown_ID() public async Task Cannot_get_secondary_ManyToOne_resource() { // Arrange - WorkItem workItem = _fakers.WorkItem.Generate(); + WorkItem workItem = _fakers.WorkItem.GenerateOne(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -130,7 +130,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); @@ -143,7 +143,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Cannot_get_secondary_OneToMany_resources() { // Arrange - UserAccount userAccount = _fakers.UserAccount.Generate(); + UserAccount userAccount = _fakers.UserAccount.GenerateOne(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -159,7 +159,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); @@ -172,7 +172,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Cannot_get_secondary_ManyToMany_resources() { // Arrange - WorkItem workItem = _fakers.WorkItem.Generate(); + WorkItem workItem = _fakers.WorkItem.GenerateOne(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -188,7 +188,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/ReadWriteDbContext.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/ReadWriteDbContext.cs index 32a0dd9..6cd2ad0 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/ReadWriteDbContext.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/ReadWriteDbContext.cs @@ -5,7 +5,8 @@ namespace JsonApiDotNetCoreMongoDbTests.IntegrationTests.ReadWrite; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class ReadWriteDbContext(IMongoDatabase database) : MongoDbContextShim(database) +public sealed class ReadWriteDbContext(IMongoDatabase database) + : MongoDbContextShim(database) { public MongoDbSetShim WorkItems => Set(); public MongoDbSetShim WorkTags => Set(); diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Updating/Relationships/AddToToManyRelationshipTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Updating/Relationships/AddToToManyRelationshipTests.cs index c2fd820..0e60f81 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Updating/Relationships/AddToToManyRelationshipTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Updating/Relationships/AddToToManyRelationshipTests.cs @@ -24,8 +24,8 @@ public AddToToManyRelationshipTests(IntegrationTestContext { @@ -54,7 +54,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); @@ -67,8 +67,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Cannot_add_to_ManyToMany_relationship() { // Arrange - WorkItem existingWorkItem = _fakers.WorkItem.Generate(); - WorkTag existingTag = _fakers.WorkTag.Generate(); + WorkItem existingWorkItem = _fakers.WorkItem.GenerateOne(); + WorkTag existingTag = _fakers.WorkTag.GenerateOne(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -97,7 +97,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Updating/Relationships/RemoveFromToManyRelationshipTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Updating/Relationships/RemoveFromToManyRelationshipTests.cs index eab8111..aeb987d 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Updating/Relationships/RemoveFromToManyRelationshipTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Updating/Relationships/RemoveFromToManyRelationshipTests.cs @@ -24,8 +24,8 @@ public RemoveFromToManyRelationshipTests(IntegrationTestContext { @@ -54,7 +54,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); @@ -67,8 +67,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Cannot_remove_from_ManyToMany_relationship() { // Arrange - WorkItem existingWorkItem = _fakers.WorkItem.Generate(); - existingWorkItem.Tags = _fakers.WorkTag.Generate(1).ToHashSet(); + WorkItem existingWorkItem = _fakers.WorkItem.GenerateOne(); + existingWorkItem.Tags = _fakers.WorkTag.GenerateSet(1); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -97,7 +97,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Updating/Relationships/ReplaceToManyRelationshipTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Updating/Relationships/ReplaceToManyRelationshipTests.cs index 9df3deb..7d22d4d 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Updating/Relationships/ReplaceToManyRelationshipTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Updating/Relationships/ReplaceToManyRelationshipTests.cs @@ -24,8 +24,8 @@ public ReplaceToManyRelationshipTests(IntegrationTestContext { @@ -54,7 +54,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); @@ -67,8 +67,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Cannot_replace_ManyToMany_relationship() { // Arrange - WorkItem existingWorkItem = _fakers.WorkItem.Generate(); - WorkTag existingTag = _fakers.WorkTag.Generate(); + WorkItem existingWorkItem = _fakers.WorkItem.GenerateOne(); + WorkTag existingTag = _fakers.WorkTag.GenerateOne(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -97,7 +97,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Updating/Relationships/UpdateToOneRelationshipTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Updating/Relationships/UpdateToOneRelationshipTests.cs index ddd0f1d..545ff89 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Updating/Relationships/UpdateToOneRelationshipTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Updating/Relationships/UpdateToOneRelationshipTests.cs @@ -24,8 +24,8 @@ public UpdateToOneRelationshipTests(IntegrationTestContext { @@ -51,7 +51,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Updating/Resources/ReplaceToManyRelationshipTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Updating/Resources/ReplaceToManyRelationshipTests.cs index f7fa710..005c180 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Updating/Resources/ReplaceToManyRelationshipTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Updating/Resources/ReplaceToManyRelationshipTests.cs @@ -24,8 +24,8 @@ public ReplaceToManyRelationshipTests(IntegrationTestContext { @@ -65,7 +65,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); @@ -78,8 +78,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Cannot_replace_ManyToMany_relationship() { // Arrange - WorkItem existingWorkItem = _fakers.WorkItem.Generate(); - WorkTag existingTag = _fakers.WorkTag.Generate(); + WorkItem existingWorkItem = _fakers.WorkItem.GenerateOne(); + WorkTag existingTag = _fakers.WorkTag.GenerateOne(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -119,7 +119,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Updating/Resources/UpdateResourceTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Updating/Resources/UpdateResourceTests.cs index 672b836..123fa48 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Updating/Resources/UpdateResourceTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Updating/Resources/UpdateResourceTests.cs @@ -34,7 +34,7 @@ public UpdateResourceTests(IntegrationTestContext { @@ -80,8 +80,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_partially_update_resource_with_string_ID() { // Arrange - WorkItemGroup existingGroup = _fakers.WorkItemGroup.Generate(); - string newName = _fakers.WorkItemGroup.Generate().Name; + WorkItemGroup existingGroup = _fakers.WorkItemGroup.GenerateOne(); + string newName = _fakers.WorkItemGroup.GenerateOne().Name; await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -112,11 +112,11 @@ await _testContext.RunOnDatabaseAsync(async dbContext => string groupName = $"{newName}{ImplicitlyChangingWorkItemGroupDefinition.Suffix}"; - responseDocument.Data.SingleValue.ShouldNotBeNull(); + responseDocument.Data.SingleValue.Should().NotBeNull(); responseDocument.Data.SingleValue.Type.Should().Be("workItemGroups"); responseDocument.Data.SingleValue.Id.Should().Be(existingGroup.StringId); - responseDocument.Data.SingleValue.Attributes.ShouldContainKey("name").With(value => value.Should().Be(groupName)); - responseDocument.Data.SingleValue.Attributes.ShouldContainKey("isPublic").With(value => value.Should().Be(existingGroup.IsPublic)); + responseDocument.Data.SingleValue.Attributes.Should().ContainKey("name").WhoseValue.Should().Be(groupName); + responseDocument.Data.SingleValue.Attributes.Should().ContainKey("isPublic").WhoseValue.Should().Be(existingGroup.IsPublic); responseDocument.Data.SingleValue.Relationships.Should().BeNull(); await _testContext.RunOnDatabaseAsync(async dbContext => @@ -132,8 +132,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_completely_update_resource_with_string_ID() { // Arrange - RgbColor existingColor = _fakers.RgbColor.Generate(); - string newDisplayName = _fakers.RgbColor.Generate().DisplayName; + RgbColor existingColor = _fakers.RgbColor.GenerateOne(); + string newDisplayName = _fakers.RgbColor.GenerateOne().DisplayName; await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -176,8 +176,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_update_resource_without_side_effects() { // Arrange - UserAccount existingUserAccount = _fakers.UserAccount.Generate(); - UserAccount newUserAccount = _fakers.UserAccount.Generate(); + UserAccount existingUserAccount = _fakers.UserAccount.GenerateOne(); + UserAccount newUserAccount = _fakers.UserAccount.GenerateOne(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -222,8 +222,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_update_resource_with_side_effects() { // Arrange - WorkItem existingWorkItem = _fakers.WorkItem.Generate(); - string newDescription = _fakers.WorkItem.Generate().Description!; + WorkItem existingWorkItem = _fakers.WorkItem.GenerateOne(); + string newDescription = _fakers.WorkItem.GenerateOne().Description!; await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -255,13 +255,13 @@ await _testContext.RunOnDatabaseAsync(async dbContext => string itemDescription = $"{newDescription}{ImplicitlyChangingWorkItemDefinition.Suffix}"; - responseDocument.Data.SingleValue.ShouldNotBeNull(); + responseDocument.Data.SingleValue.Should().NotBeNull(); responseDocument.Data.SingleValue.Type.Should().Be("workItems"); responseDocument.Data.SingleValue.Id.Should().Be(existingWorkItem.StringId); - responseDocument.Data.SingleValue.Attributes.ShouldContainKey("description").With(value => value.Should().Be(itemDescription)); - responseDocument.Data.SingleValue.Attributes.ShouldContainKey("dueAt").With(value => value.Should().BeNull()); - responseDocument.Data.SingleValue.Attributes.ShouldContainKey("priority").With(value => value.Should().Be(existingWorkItem.Priority)); - responseDocument.Data.SingleValue.Attributes.ShouldContainKey("isImportant").With(value => value.Should().Be(existingWorkItem.IsImportant)); + responseDocument.Data.SingleValue.Attributes.Should().ContainKey("description").WhoseValue.Should().Be(itemDescription); + responseDocument.Data.SingleValue.Attributes.Should().ContainKey("dueAt").WhoseValue.Should().BeNull(); + responseDocument.Data.SingleValue.Attributes.Should().ContainKey("priority").WhoseValue.Should().Be(existingWorkItem.Priority); + responseDocument.Data.SingleValue.Attributes.Should().ContainKey("isImportant").WhoseValue.Should().Be(existingWorkItem.IsImportant); responseDocument.Data.SingleValue.Relationships.Should().BeNull(); await _testContext.RunOnDatabaseAsync(async dbContext => @@ -278,8 +278,8 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_update_resource_with_side_effects_with_primary_fieldset() { // Arrange - WorkItem existingWorkItem = _fakers.WorkItem.Generate(); - string newDescription = _fakers.WorkItem.Generate().Description!; + WorkItem existingWorkItem = _fakers.WorkItem.GenerateOne(); + string newDescription = _fakers.WorkItem.GenerateOne().Description!; await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -311,12 +311,12 @@ await _testContext.RunOnDatabaseAsync(async dbContext => string itemDescription = $"{newDescription}{ImplicitlyChangingWorkItemDefinition.Suffix}"; - responseDocument.Data.SingleValue.ShouldNotBeNull(); + responseDocument.Data.SingleValue.Should().NotBeNull(); responseDocument.Data.SingleValue.Type.Should().Be("workItems"); responseDocument.Data.SingleValue.Id.Should().Be(existingWorkItem.StringId); - responseDocument.Data.SingleValue.Attributes.ShouldHaveCount(2); - responseDocument.Data.SingleValue.Attributes.ShouldContainKey("description").With(value => value.Should().Be(itemDescription)); - responseDocument.Data.SingleValue.Attributes.ShouldContainKey("priority").With(value => value.Should().Be(existingWorkItem.Priority)); + responseDocument.Data.SingleValue.Attributes.Should().HaveCount(2); + responseDocument.Data.SingleValue.Attributes.Should().ContainKey("description").WhoseValue.Should().Be(itemDescription); + responseDocument.Data.SingleValue.Attributes.Should().ContainKey("priority").WhoseValue.Should().Be(existingWorkItem.Priority); responseDocument.Data.SingleValue.Relationships.Should().BeNull(); await _testContext.RunOnDatabaseAsync(async dbContext => @@ -352,7 +352,7 @@ public async Task Cannot_update_resource_on_unknown_resource_ID_in_url() // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.NotFound); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.NotFound); diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Updating/Resources/UpdateToOneRelationshipTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Updating/Resources/UpdateToOneRelationshipTests.cs index c1e99fc..1f14cc3 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Updating/Resources/UpdateToOneRelationshipTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/Updating/Resources/UpdateToOneRelationshipTests.cs @@ -24,8 +24,8 @@ public UpdateToOneRelationshipTests(IntegrationTestContext { @@ -62,7 +62,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/WorkItem.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/WorkItem.cs index 4eecb06..bd13296 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/WorkItem.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ReadWrite/WorkItem.cs @@ -18,7 +18,7 @@ public sealed class WorkItem : HexStringMongoIdentifiable [Attr] public WorkItemPriority Priority { get; set; } - [Attr(Capabilities = ~(AttrCapabilities.AllowCreate | AttrCapabilities.AllowChange))] + [Attr(Capabilities = AttrCapabilities.All & ~(AttrCapabilities.AllowCreate | AttrCapabilities.AllowChange))] [BsonIgnore] public bool IsImportant { diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ResourceDefinitions/Reading/ResourceDefinitionReadTests.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ResourceDefinitions/Reading/ResourceDefinitionReadTests.cs index df3364e..e05f114 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ResourceDefinitions/Reading/ResourceDefinitionReadTests.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ResourceDefinitions/Reading/ResourceDefinitionReadTests.cs @@ -3,7 +3,6 @@ using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Serialization.Objects; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; using TestBuildingBlocks; using Xunit; @@ -26,12 +25,12 @@ public ResourceDefinitionReadTests(IntegrationTestContext { - services.TryAddSingleton(); - services.TryAddSingleton(); - - services.AddResourceDefinition(); - services.AddResourceDefinition(); services.AddResourceDefinition(); + services.AddResourceDefinition(); + services.AddResourceDefinition(); + + services.AddSingleton(); + services.AddSingleton(); }); var options = (JsonApiOptions)testContext.Factory.Services.GetRequiredService(); @@ -53,7 +52,7 @@ public async Task Filter_from_resource_definition_is_applied() var settingsProvider = (TestClientSettingsProvider)_testContext.Factory.Services.GetRequiredService(); settingsProvider.HidePlanetsWithPrivateName(); - List planets = _fakers.Planet.Generate(4); + List planets = _fakers.Planet.GenerateList(4); planets[0].PrivateName = "A"; planets[2].PrivateName = "B"; @@ -72,7 +71,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Data.ManyValue.ShouldHaveCount(2); + responseDocument.Data.ManyValue.Should().HaveCount(2); responseDocument.Data.ManyValue[0].Id.Should().Be(planets[1].StringId); responseDocument.Data.ManyValue[1].Id.Should().Be(planets[3].StringId); @@ -101,7 +100,7 @@ public async Task Filter_from_resource_definition_and_query_string_are_applied() var settingsProvider = (TestClientSettingsProvider)_testContext.Factory.Services.GetRequiredService(); settingsProvider.HidePlanetsWithPrivateName(); - List planets = _fakers.Planet.Generate(4); + List planets = _fakers.Planet.GenerateList(4); planets[0].HasRingSystem = true; planets[0].PrivateName = "A"; @@ -128,7 +127,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Data.ManyValue.ShouldHaveCount(1); + responseDocument.Data.ManyValue.Should().HaveCount(1); responseDocument.Data.ManyValue[0].Id.Should().Be(planets[3].StringId); responseDocument.Meta.Should().ContainTotal(1); @@ -152,7 +151,7 @@ public async Task Sort_from_resource_definition_is_applied() // Arrange var hitCounter = _testContext.Factory.Services.GetRequiredService(); - List stars = _fakers.Star.Generate(3); + List stars = _fakers.Star.GenerateList(3); stars[0].SolarMass = 500m; stars[0].SolarRadius = 1m; @@ -178,7 +177,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Data.ManyValue.ShouldHaveCount(3); + responseDocument.Data.ManyValue.Should().HaveCount(3); responseDocument.Data.ManyValue[0].Id.Should().Be(stars[1].StringId); responseDocument.Data.ManyValue[1].Id.Should().Be(stars[0].StringId); responseDocument.Data.ManyValue[2].Id.Should().Be(stars[2].StringId); @@ -204,7 +203,7 @@ public async Task Sort_from_query_string_is_applied() // Arrange var hitCounter = _testContext.Factory.Services.GetRequiredService(); - List stars = _fakers.Star.Generate(3); + List stars = _fakers.Star.GenerateList(3); stars[0].Name = "B"; stars[0].SolarRadius = 10m; @@ -230,7 +229,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Data.ManyValue.ShouldHaveCount(3); + responseDocument.Data.ManyValue.Should().HaveCount(3); responseDocument.Data.ManyValue[0].Id.Should().Be(stars[2].StringId); responseDocument.Data.ManyValue[1].Id.Should().Be(stars[0].StringId); responseDocument.Data.ManyValue[2].Id.Should().Be(stars[1].StringId); @@ -256,7 +255,7 @@ public async Task Page_size_from_resource_definition_is_applied() // Arrange var hitCounter = _testContext.Factory.Services.GetRequiredService(); - List stars = _fakers.Star.Generate(10); + List stars = _fakers.Star.GenerateList(10); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -273,7 +272,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Data.ManyValue.ShouldHaveCount(5); + responseDocument.Data.ManyValue.Should().HaveCount(5); hitCounter.HitExtensibilityPoints.Should().BeEquivalentTo(new[] { @@ -298,7 +297,7 @@ public async Task Attribute_inclusion_from_resource_definition_is_applied_for_om // Arrange var hitCounter = _testContext.Factory.Services.GetRequiredService(); - Star star = _fakers.Star.Generate(); + Star star = _fakers.Star.GenerateOne(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -314,10 +313,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Data.SingleValue.ShouldNotBeNull(); + responseDocument.Data.SingleValue.Should().NotBeNull(); responseDocument.Data.SingleValue.Id.Should().Be(star.StringId); - responseDocument.Data.SingleValue.Attributes.ShouldContainKey("name").With(value => value.Should().Be(star.Name)); - responseDocument.Data.SingleValue.Attributes.ShouldContainKey("kind").With(value => value.Should().Be(star.Kind)); + responseDocument.Data.SingleValue.Attributes.Should().ContainKey("name").WhoseValue.Should().Be(star.Name); + responseDocument.Data.SingleValue.Attributes.Should().ContainKey("kind").WhoseValue.Should().Be(star.Kind); responseDocument.Data.SingleValue.Relationships.Should().BeNull(); hitCounter.HitExtensibilityPoints.Should().BeEquivalentTo(new[] @@ -338,7 +337,7 @@ public async Task Attribute_inclusion_from_resource_definition_is_applied_for_fi // Arrange var hitCounter = _testContext.Factory.Services.GetRequiredService(); - Star star = _fakers.Star.Generate(); + Star star = _fakers.Star.GenerateOne(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -354,11 +353,11 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Data.SingleValue.ShouldNotBeNull(); + responseDocument.Data.SingleValue.Should().NotBeNull(); responseDocument.Data.SingleValue.Id.Should().Be(star.StringId); - responseDocument.Data.SingleValue.Attributes.ShouldHaveCount(2); - responseDocument.Data.SingleValue.Attributes.ShouldContainKey("name").With(value => value.Should().Be(star.Name)); - responseDocument.Data.SingleValue.Attributes.ShouldContainKey("solarRadius").With(value => value.Should().Be(star.SolarRadius)); + responseDocument.Data.SingleValue.Attributes.Should().HaveCount(2); + responseDocument.Data.SingleValue.Attributes.Should().ContainKey("name").WhoseValue.Should().Be(star.Name); + responseDocument.Data.SingleValue.Attributes.Should().ContainKey("solarRadius").WhoseValue.Should().Be(star.SolarRadius); responseDocument.Data.SingleValue.Relationships.Should().BeNull(); hitCounter.HitExtensibilityPoints.Should().BeEquivalentTo(new[] @@ -379,7 +378,7 @@ public async Task Attribute_exclusion_from_resource_definition_is_applied_for_om // Arrange var hitCounter = _testContext.Factory.Services.GetRequiredService(); - Star star = _fakers.Star.Generate(); + Star star = _fakers.Star.GenerateOne(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -395,9 +394,9 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Data.SingleValue.ShouldNotBeNull(); + responseDocument.Data.SingleValue.Should().NotBeNull(); responseDocument.Data.SingleValue.Id.Should().Be(star.StringId); - responseDocument.Data.SingleValue.Attributes.ShouldContainKey("name").With(value => value.Should().Be(star.Name)); + responseDocument.Data.SingleValue.Attributes.Should().ContainKey("name").WhoseValue.Should().Be(star.Name); responseDocument.Data.SingleValue.Attributes.Should().NotContainKey("isVisibleFromEarth"); responseDocument.Data.SingleValue.Relationships.Should().BeNull(); @@ -419,7 +418,7 @@ public async Task Attribute_exclusion_from_resource_definition_is_applied_for_fi // Arrange var hitCounter = _testContext.Factory.Services.GetRequiredService(); - Star star = _fakers.Star.Generate(); + Star star = _fakers.Star.GenerateOne(); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -435,10 +434,10 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Data.SingleValue.ShouldNotBeNull(); + responseDocument.Data.SingleValue.Should().NotBeNull(); responseDocument.Data.SingleValue.Id.Should().Be(star.StringId); - responseDocument.Data.SingleValue.Attributes.ShouldHaveCount(1); - responseDocument.Data.SingleValue.Attributes.ShouldContainKey("name").With(value => value.Should().Be(star.Name)); + responseDocument.Data.SingleValue.Attributes.Should().HaveCount(1); + responseDocument.Data.SingleValue.Attributes.Should().ContainKey("name").WhoseValue.Should().Be(star.Name); responseDocument.Data.SingleValue.Relationships.Should().BeNull(); hitCounter.HitExtensibilityPoints.Should().BeEquivalentTo(new[] @@ -459,7 +458,7 @@ public async Task Queryable_parameter_handler_from_resource_definition_is_applie // Arrange var hitCounter = _testContext.Factory.Services.GetRequiredService(); - List moons = _fakers.Moon.Generate(2); + List moons = _fakers.Moon.GenerateList(2); moons[0].SolarRadius = .5m; moons[1].SolarRadius = 50m; @@ -478,7 +477,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Data.ManyValue.ShouldHaveCount(1); + responseDocument.Data.ManyValue.Should().HaveCount(1); responseDocument.Data.ManyValue[0].Id.Should().Be(moons[1].StringId); hitCounter.HitExtensibilityPoints.Should().BeEquivalentTo(new[] @@ -502,7 +501,7 @@ public async Task Queryable_parameter_handler_from_resource_definition_and_query // Arrange var hitCounter = _testContext.Factory.Services.GetRequiredService(); - List moons = _fakers.Moon.Generate(4); + List moons = _fakers.Moon.GenerateList(4); moons[0].Name = "Alpha1"; moons[0].SolarRadius = 1m; @@ -531,7 +530,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK); - responseDocument.Data.ManyValue.ShouldHaveCount(1); + responseDocument.Data.ManyValue.Should().HaveCount(1); responseDocument.Data.ManyValue[0].Id.Should().Be(moons[2].StringId); hitCounter.HitExtensibilityPoints.Should().BeEquivalentTo(new[] @@ -555,8 +554,8 @@ public async Task Queryable_parameter_handler_from_resource_definition_is_not_ap // Arrange var hitCounter = _testContext.Factory.Services.GetRequiredService(); - Planet planet = _fakers.Planet.Generate(); - planet.Moons = _fakers.Moon.Generate(1).ToHashSet(); + Planet planet = _fakers.Planet.GenerateOne(); + planet.Moons = _fakers.Moon.GenerateSet(1); await _testContext.RunOnDatabaseAsync(async dbContext => { @@ -572,13 +571,13 @@ await _testContext.RunOnDatabaseAsync(async dbContext => // Assert httpResponse.ShouldHaveStatusCode(HttpStatusCode.BadRequest); - responseDocument.Errors.ShouldHaveCount(1); + responseDocument.Errors.Should().HaveCount(1); ErrorObject error = responseDocument.Errors[0]; error.StatusCode.Should().Be(HttpStatusCode.BadRequest); error.Title.Should().Be("Custom query string parameters cannot be used on nested resource endpoints."); error.Detail.Should().Be("Query string parameter 'isLargerThanTheSun' cannot be used on a nested resource endpoint."); - error.Source.ShouldNotBeNull(); + error.Source.Should().NotBeNull(); error.Source.Parameter.Should().Be("isLargerThanTheSun"); hitCounter.HitExtensibilityPoints.Should().BeEquivalentTo(new[] diff --git a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ResourceDefinitions/Reading/UniverseDbContext.cs b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ResourceDefinitions/Reading/UniverseDbContext.cs index 901ce69..417d995 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ResourceDefinitions/Reading/UniverseDbContext.cs +++ b/test/JsonApiDotNetCoreMongoDbTests/IntegrationTests/ResourceDefinitions/Reading/UniverseDbContext.cs @@ -5,7 +5,8 @@ namespace JsonApiDotNetCoreMongoDbTests.IntegrationTests.ResourceDefinitions.Reading; [UsedImplicitly(ImplicitUseTargetFlags.Members)] -public sealed class UniverseDbContext(IMongoDatabase database) : MongoDbContextShim(database) +public sealed class UniverseDbContext(IMongoDatabase database) + : MongoDbContextShim(database) { public MongoDbSetShim Stars => Set(); public MongoDbSetShim Planets => Set(); diff --git a/test/JsonApiDotNetCoreMongoDbTests/JsonApiDotNetCoreMongoDbTests.csproj b/test/JsonApiDotNetCoreMongoDbTests/JsonApiDotNetCoreMongoDbTests.csproj index d25aea3..22338d5 100644 --- a/test/JsonApiDotNetCoreMongoDbTests/JsonApiDotNetCoreMongoDbTests.csproj +++ b/test/JsonApiDotNetCoreMongoDbTests/JsonApiDotNetCoreMongoDbTests.csproj @@ -1,6 +1,6 @@ - net8.0;net6.0 + net9.0;net8.0 diff --git a/test/TestBuildingBlocks/FakerExtensions.cs b/test/TestBuildingBlocks/FakerExtensions.cs index aff1cd0..75714e6 100644 --- a/test/TestBuildingBlocks/FakerExtensions.cs +++ b/test/TestBuildingBlocks/FakerExtensions.cs @@ -1,14 +1,13 @@ using System.Diagnostics; using System.Reflection; using Bogus; -using FluentAssertions.Extensions; using Xunit; namespace TestBuildingBlocks; public static class FakerExtensions { - public static Faker MakeDeterministic(this Faker faker) + public static Faker MakeDeterministic(this Faker faker, DateTime? systemTimeUtc = null) where T : class { int seed = GetFakerSeed(); @@ -16,7 +15,7 @@ public static Faker MakeDeterministic(this Faker faker) // Setting the system DateTime to kind Utc, so that faker calls like PastOffset() don't depend on the system time zone. // See https://docs.microsoft.com/en-us/dotnet/api/system.datetimeoffset.op_implicit?view=net-6.0#remarks - faker.UseDateTimeReference(1.January(2020).At(1, 1, 1).AsUtc()); + faker.UseDateTimeReference(systemTimeUtc ?? IntegrationTest.DefaultDateTimeUtc.UtcDateTime); return faker; } @@ -80,4 +79,27 @@ private static int GetDeterministicHashCode(string source) return hash1 + hash2 * 1566083941; } } + + // The methods below exist so that a non-nullable return type is inferred. + // The Bogus NuGet package is not annotated for nullable reference types. + + public static T GenerateOne(this Faker faker) + where T : class + { + return faker.Generate(); + } + +#pragma warning disable AV1130 // Return type in method signature should be an interface to an unchangeable collection + public static List GenerateList(this Faker faker, int count) + where T : class + { + return faker.Generate(count); + } + + public static HashSet GenerateSet(this Faker faker, int count) + where T : class + { + return faker.Generate(count).ToHashSet(); + } +#pragma warning restore AV1130 // Return type in method signature should be an interface to an unchangeable collection } diff --git a/test/TestBuildingBlocks/FluentExtensions.cs b/test/TestBuildingBlocks/FluentExtensions.cs new file mode 100644 index 0000000..1ceebc0 --- /dev/null +++ b/test/TestBuildingBlocks/FluentExtensions.cs @@ -0,0 +1,45 @@ +using FluentAssertions; +using FluentAssertions.Numeric; +using FluentAssertions.Primitives; +using JetBrains.Annotations; +using SysNotNull = System.Diagnostics.CodeAnalysis.NotNullAttribute; + +// ReSharper disable UnusedMethodReturnValue.Global + +namespace TestBuildingBlocks; + +public static class FluentExtensions +{ + private const decimal NumericPrecision = 0.00000000001M; + + /// + /// Same as , but with + /// default precision. + /// + [CustomAssertion] + public static AndConstraint> BeApproximately(this NullableNumericAssertions parent, decimal? expectedValue, + string because = "", params object[] becauseArgs) + { + return parent.BeApproximately(expectedValue, NumericPrecision, because, becauseArgs); + } + + // Workaround for source.Should().NotBeNull().And.Subject having declared type 'object'. + [System.Diagnostics.Contracts.Pure] + public static StrongReferenceTypeAssertions RefShould([SysNotNull] this T? actualValue) + where T : class + { + actualValue.Should().NotBeNull(); + return new StrongReferenceTypeAssertions(actualValue); + } + + public static void With(this T subject, [InstantHandle] Action continuation) + { + continuation(subject); + } + + public sealed class StrongReferenceTypeAssertions(TReference subject) + : ReferenceTypeAssertions>(subject) + { + protected override string Identifier => "subject"; + } +} diff --git a/test/TestBuildingBlocks/FluentMetaExtensions.cs b/test/TestBuildingBlocks/FluentMetaExtensions.cs new file mode 100644 index 0000000..7ed040c --- /dev/null +++ b/test/TestBuildingBlocks/FluentMetaExtensions.cs @@ -0,0 +1,37 @@ +using System.Text.Json; +using FluentAssertions; +using FluentAssertions.Collections; + +namespace TestBuildingBlocks; + +public static class FluentMetaExtensions +{ + /// + /// Asserts that a "meta" dictionary contains a single element named "total" with the specified value. + /// + [CustomAssertion] +#pragma warning disable AV1553 // Do not use optional parameters with default value null for strings, collections or tasks + public static void ContainTotal(this GenericDictionaryAssertions, string, object?> source, int expected, + string? keyName = null) +#pragma warning restore AV1553 // Do not use optional parameters with default value null for strings, collections or tasks + { + JsonElement element = GetMetaJsonElement(source, keyName ?? "total"); + element.GetInt32().Should().Be(expected); + } + + /// + /// Asserts that a "meta" dictionary contains a single element named "requestBody" that isn't empty. + /// + [CustomAssertion] + public static void HaveRequestBody(this GenericDictionaryAssertions, string, object?> source) + { + JsonElement element = GetMetaJsonElement(source, "requestBody"); + element.ToString().Should().NotBeEmpty(); + } + + private static JsonElement GetMetaJsonElement(GenericDictionaryAssertions, string, object?> source, string metaKey) + { + object? value = source.ContainKey(metaKey).WhoseValue; + return value.Should().BeOfType().Subject; + } +} diff --git a/test/TestBuildingBlocks/IntegrationTest.cs b/test/TestBuildingBlocks/IntegrationTest.cs index 50fc3fd..e74c892 100644 --- a/test/TestBuildingBlocks/IntegrationTest.cs +++ b/test/TestBuildingBlocks/IntegrationTest.cs @@ -1,6 +1,7 @@ using System.Net.Http.Headers; using System.Text; using System.Text.Json; +using FluentAssertions.Extensions; using JsonApiDotNetCore.Middleware; using Xunit; @@ -12,14 +13,21 @@ namespace TestBuildingBlocks; /// public abstract class IntegrationTest : IAsyncLifetime { - private static readonly SemaphoreSlim ThrottleSemaphore; + private static readonly MediaTypeHeaderValue DefaultMediaType = MediaTypeHeaderValue.Parse(HeaderConstants.MediaType); + + private static readonly MediaTypeWithQualityHeaderValue OperationsMediaType = + MediaTypeWithQualityHeaderValue.Parse(HeaderConstants.AtomicOperationsMediaType); + + private static readonly SemaphoreSlim ThrottleSemaphore = GetDefaultThrottleSemaphore(); + + internal static DateTimeOffset DefaultDateTimeUtc { get; } = 1.January(2020).At(1, 2, 3).AsUtc(); protected abstract JsonSerializerOptions SerializerOptions { get; } - static IntegrationTest() + private static SemaphoreSlim GetDefaultThrottleSemaphore() { int maxConcurrentTestRuns = OperatingSystem.IsWindows() && Environment.GetEnvironmentVariable("CI") != null ? 32 : 64; - ThrottleSemaphore = new SemaphoreSlim(maxConcurrentTestRuns); + return new SemaphoreSlim(maxConcurrentTestRuns); } public async Task<(HttpResponseMessage httpResponse, TResponseDocument responseDocument)> ExecuteGetAsync(string requestUrl, @@ -28,32 +36,38 @@ static IntegrationTest() return await ExecuteRequestAsync(HttpMethod.Get, requestUrl, null, null, setRequestHeaders); } +#pragma warning disable AV1553 // Do not use optional parameters with default value null for strings, collections or tasks public async Task<(HttpResponseMessage httpResponse, TResponseDocument responseDocument)> ExecutePostAsync(string requestUrl, - object requestBody, string contentType = HeaderConstants.MediaType, Action? setRequestHeaders = null) + object requestBody, string? contentType = null, Action? setRequestHeaders = null) +#pragma warning restore AV1553 // Do not use optional parameters with default value null for strings, collections or tasks { - return await ExecuteRequestAsync(HttpMethod.Post, requestUrl, requestBody, contentType, setRequestHeaders); + MediaTypeHeaderValue mediaType = contentType == null ? DefaultMediaType : MediaTypeHeaderValue.Parse(contentType); + + return await ExecuteRequestAsync(HttpMethod.Post, requestUrl, requestBody, mediaType, setRequestHeaders); } public async Task<(HttpResponseMessage httpResponse, TResponseDocument responseDocument)> ExecutePostAtomicAsync(string requestUrl, - object requestBody, string contentType = HeaderConstants.AtomicOperationsMediaType, Action? setRequestHeaders = null) + object requestBody) { - return await ExecuteRequestAsync(HttpMethod.Post, requestUrl, requestBody, contentType, setRequestHeaders); + Action setRequestHeaders = headers => headers.Accept.Add(OperationsMediaType); + + return await ExecuteRequestAsync(HttpMethod.Post, requestUrl, requestBody, OperationsMediaType, setRequestHeaders); } public async Task<(HttpResponseMessage httpResponse, TResponseDocument responseDocument)> ExecutePatchAsync(string requestUrl, - object requestBody, string contentType = HeaderConstants.MediaType, Action? setRequestHeaders = null) + object requestBody, Action? setRequestHeaders = null) { - return await ExecuteRequestAsync(HttpMethod.Patch, requestUrl, requestBody, contentType, setRequestHeaders); + return await ExecuteRequestAsync(HttpMethod.Patch, requestUrl, requestBody, DefaultMediaType, setRequestHeaders); } public async Task<(HttpResponseMessage httpResponse, TResponseDocument responseDocument)> ExecuteDeleteAsync(string requestUrl, - object? requestBody = null, string contentType = HeaderConstants.MediaType, Action? setRequestHeaders = null) + object? requestBody = null, Action? setRequestHeaders = null) { - return await ExecuteRequestAsync(HttpMethod.Delete, requestUrl, requestBody, contentType, setRequestHeaders); + return await ExecuteRequestAsync(HttpMethod.Delete, requestUrl, requestBody, DefaultMediaType, setRequestHeaders); } private async Task<(HttpResponseMessage httpResponse, TResponseDocument responseDocument)> ExecuteRequestAsync(HttpMethod method, - string requestUrl, object? requestBody, string? contentType, Action? setRequestHeaders) + string requestUrl, object? requestBody, MediaTypeHeaderValue? contentType, Action? setRequestHeaders) { using var request = new HttpRequestMessage(method, requestUrl); string? requestText = SerializeRequest(requestBody); @@ -66,7 +80,7 @@ static IntegrationTest() if (contentType != null) { - request.Content.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType); + request.Content.Headers.ContentType = contentType; } } diff --git a/test/TestBuildingBlocks/IntegrationTestContext.cs b/test/TestBuildingBlocks/IntegrationTestContext.cs index f479821..b16cd2f 100644 --- a/test/TestBuildingBlocks/IntegrationTestContext.cs +++ b/test/TestBuildingBlocks/IntegrationTestContext.cs @@ -115,9 +115,7 @@ private WebApplicationFactory CreateFactory() services.AddJsonApiMongoDb(); }); - // We have placed an appsettings.json in the TestBuildingBlock project folder and set the content root to there. Note that controllers - // are not discovered in the content root but are registered manually using IntegrationTestContext.UseController. - return factory.WithWebHostBuilder(builder => builder.UseSolutionRelativeContentRoot($"test/{nameof(TestBuildingBlocks)}")); + return factory; } private void ConfigureJsonApiOptions(JsonApiOptions options) @@ -129,6 +127,11 @@ private void ConfigureJsonApiOptions(JsonApiOptions options) public void ConfigureServices(Action configureServices) { + if (_configureServices != null && _configureServices != configureServices) + { + throw new InvalidOperationException($"Do not call {nameof(ConfigureServices)} multiple times."); + } + _configureServices = configureServices; } @@ -169,6 +172,13 @@ public void ConfigureServices(Action? configureServices) _configureServices = configureServices; } + protected override void ConfigureWebHost(IWebHostBuilder builder) + { + // We have placed an appsettings.json in the TestBuildingBlocks project directory and set the content root to there. Note that + // controllers are not discovered in the content root, but are registered manually using IntegrationTestContext.UseController. + builder.UseSolutionRelativeContentRoot($"test/{nameof(TestBuildingBlocks)}"); + } + protected override IHostBuilder CreateHostBuilder() { // @formatter:wrap_chained_method_calls chop_always diff --git a/test/TestBuildingBlocks/MarkedText.cs b/test/TestBuildingBlocks/MarkedText.cs new file mode 100644 index 0000000..003fd0a --- /dev/null +++ b/test/TestBuildingBlocks/MarkedText.cs @@ -0,0 +1,44 @@ +using System.Diagnostics; +using JetBrains.Annotations; + +namespace TestBuildingBlocks; + +[PublicAPI] +[DebuggerDisplay($"{{{nameof(Source)}}}")] +public sealed class MarkedText +{ + public string Source { get; } + public int Position { get; } + public string Text { get; } + + public MarkedText(string source, char marker) + { + ArgumentNullException.ThrowIfNull(source); + + Source = source; + Position = GetPositionFromMarker(marker); + Text = source.Replace(marker.ToString(), string.Empty); + } + + private int GetPositionFromMarker(char marker) + { + int position = Source.IndexOf(marker); + + if (position == -1) + { + throw new InvalidOperationException("Marker not found."); + } + + if (Source.IndexOf(marker, position + 1) != -1) + { + throw new InvalidOperationException("Multiple markers found."); + } + + return position; + } + + public override string ToString() + { + return $"Failed at position {Position + 1}: {Source}"; + } +} diff --git a/test/TestBuildingBlocks/MongoDbSetShim.cs b/test/TestBuildingBlocks/MongoDbSetShim.cs index 182ac6e..8a2801f 100644 --- a/test/TestBuildingBlocks/MongoDbSetShim.cs +++ b/test/TestBuildingBlocks/MongoDbSetShim.cs @@ -42,7 +42,7 @@ public void AddRange(IEnumerable entities) internal override async Task PersistAsync(CancellationToken cancellationToken) { - if (_entitiesToInsert.Any()) + if (_entitiesToInsert.Count > 0) { if (_entitiesToInsert.Count == 1) { diff --git a/test/TestBuildingBlocks/MongoRunnerProvider.cs b/test/TestBuildingBlocks/MongoRunnerProvider.cs index 96af01f..eb0fe49 100644 --- a/test/TestBuildingBlocks/MongoRunnerProvider.cs +++ b/test/TestBuildingBlocks/MongoRunnerProvider.cs @@ -7,7 +7,12 @@ internal sealed class MongoRunnerProvider { public static readonly MongoRunnerProvider Instance = new(); +#if NET8_0 private readonly object _lockObject = new(); +#else + private readonly Lock _lockObject = new(); +#endif + private IMongoRunner? _runner; private int _useCounter; @@ -62,20 +67,14 @@ private sealed class MongoRunnerWrapper(MongoRunnerProvider owner, IMongoRunner public void Import(string database, string collection, string inputFilePath, string? additionalArguments = null, bool drop = false) { - if (_underlyingMongoRunner == null) - { - throw new ObjectDisposedException(nameof(IMongoRunner)); - } + ObjectDisposedException.ThrowIf(_underlyingMongoRunner == null, this); _underlyingMongoRunner.Import(database, collection, inputFilePath, additionalArguments, drop); } public void Export(string database, string collection, string outputFilePath, string? additionalArguments = null) { - if (_underlyingMongoRunner == null) - { - throw new ObjectDisposedException(nameof(IMongoRunner)); - } + ObjectDisposedException.ThrowIf(_underlyingMongoRunner == null, this); _underlyingMongoRunner.Export(database, collection, outputFilePath, additionalArguments); } diff --git a/test/TestBuildingBlocks/NullabilityAssertionExtensions.cs b/test/TestBuildingBlocks/NullabilityAssertionExtensions.cs deleted file mode 100644 index a4242b4..0000000 --- a/test/TestBuildingBlocks/NullabilityAssertionExtensions.cs +++ /dev/null @@ -1,43 +0,0 @@ -using FluentAssertions; -using JetBrains.Annotations; -using SysNotNull = System.Diagnostics.CodeAnalysis.NotNullAttribute; - -// ReSharper disable PossibleMultipleEnumeration -#pragma warning disable CS8777 // Parameter must have a non-null value when exiting. - -namespace TestBuildingBlocks; - -public static class NullabilityAssertionExtensions -{ - [CustomAssertion] - public static T ShouldNotBeNull([SysNotNull] this T? subject) - { - subject.Should().NotBeNull(); - return subject!; - } - - [CustomAssertion] - public static void ShouldNotBeEmpty([SysNotNull] this string? subject) - { - subject.Should().NotBeEmpty(); - } - - [CustomAssertion] - public static void ShouldHaveCount([SysNotNull] this IEnumerable? subject, int expected) - { - subject.Should().HaveCount(expected); - } - - [CustomAssertion] - public static TValue? ShouldContainKey([SysNotNull] this IDictionary? subject, TKey expected) - { - subject.Should().ContainKey(expected); - - return subject![expected]; - } - - public static void With(this T subject, [InstantHandle] Action continuation) - { - continuation(subject); - } -} diff --git a/test/TestBuildingBlocks/ObjectAssertionsExtensions.cs b/test/TestBuildingBlocks/ObjectAssertionsExtensions.cs deleted file mode 100644 index 1e833be..0000000 --- a/test/TestBuildingBlocks/ObjectAssertionsExtensions.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Text.Json; -using FluentAssertions; -using FluentAssertions.Collections; -using FluentAssertions.Numeric; -using JetBrains.Annotations; - -namespace TestBuildingBlocks; - -[PublicAPI] -public static class ObjectAssertionsExtensions -{ - private const decimal NumericPrecision = 0.00000000001M; - - /// - /// Same as , but with - /// default precision. - /// - [CustomAssertion] - public static AndConstraint> BeApproximately(this NullableNumericAssertions parent, decimal? expectedValue, - string because = "", params object[] becauseArgs) - { - return parent.BeApproximately(expectedValue, NumericPrecision, because, becauseArgs); - } - - /// - /// Asserts that a "meta" dictionary contains a single element named "total" with the specified value. - /// - [CustomAssertion] - public static void ContainTotal(this GenericDictionaryAssertions, string, object?> source, int expectedTotal) - { - source.ContainKey("total").WhoseValue.Should().BeOfType().Subject.GetInt32().Should().Be(expectedTotal); - } -} diff --git a/test/TestBuildingBlocks/AssemblyInfo.cs b/test/TestBuildingBlocks/Properties/AssemblyInfo.cs similarity index 100% rename from test/TestBuildingBlocks/AssemblyInfo.cs rename to test/TestBuildingBlocks/Properties/AssemblyInfo.cs diff --git a/test/TestBuildingBlocks/ResourceTypeFinder.cs b/test/TestBuildingBlocks/ResourceTypeFinder.cs index c4d6ecf..a1db4f1 100644 --- a/test/TestBuildingBlocks/ResourceTypeFinder.cs +++ b/test/TestBuildingBlocks/ResourceTypeFinder.cs @@ -1,5 +1,6 @@ using System.Collections.Concurrent; using System.Reflection; +using JsonApiDotNetCore.MongoDb; using JsonApiDotNetCore.Resources; #pragma warning disable AV1008 // Class should not be static @@ -16,16 +17,16 @@ public static IReadOnlySet GetResourceClrTypesInNamespace(Assembly assembl IReadOnlySet resourceClrTypesInAssembly = ResourceTypesPerAssembly.GetOrAdd(assembly, GetResourceClrTypesInAssembly); string namespaceKey = codeNamespace ?? string.Empty; - return ResourceTypesPerNamespace.GetOrAdd(namespaceKey, _ => FilterTypesInNamespace(resourceClrTypesInAssembly, codeNamespace).ToHashSet()); + return ResourceTypesPerNamespace.GetOrAdd(namespaceKey, _ => FilterTypesInNamespace(resourceClrTypesInAssembly, codeNamespace)); } private static IReadOnlySet GetResourceClrTypesInAssembly(Assembly assembly) { - return assembly.GetTypes().Where(type => type.IsAssignableTo(typeof(IIdentifiable))).ToHashSet(); + return assembly.GetTypes().Where(type => type.IsAssignableTo(typeof(IIdentifiable))).ToHashSet().AsReadOnly(); } - private static IEnumerable FilterTypesInNamespace(IEnumerable resourceClrTypesInAssembly, string? codeNamespace) + private static IReadOnlySet FilterTypesInNamespace(IEnumerable resourceClrTypesInAssembly, string? codeNamespace) { - return resourceClrTypesInAssembly.Where(resourceClrType => resourceClrType.Namespace == codeNamespace); + return resourceClrTypesInAssembly.Where(resourceClrType => resourceClrType.Namespace == codeNamespace).ToHashSet().AsReadOnly(); } } diff --git a/test/TestBuildingBlocks/ServiceCollectionExtensions.cs b/test/TestBuildingBlocks/ServiceCollectionExtensions.cs index 7ee211a..886a5f0 100644 --- a/test/TestBuildingBlocks/ServiceCollectionExtensions.cs +++ b/test/TestBuildingBlocks/ServiceCollectionExtensions.cs @@ -9,6 +9,9 @@ internal static class ServiceCollectionExtensions { public static void ReplaceControllers(this IServiceCollection services, TestControllerProvider provider) { + ArgumentNullException.ThrowIfNull(services); + ArgumentNullException.ThrowIfNull(provider); + services.AddMvcCore().ConfigureApplicationPartManager(manager => { RemoveExistingControllerFeatureProviders(manager); diff --git a/test/TestBuildingBlocks/TestBuildingBlocks.csproj b/test/TestBuildingBlocks/TestBuildingBlocks.csproj index d7b0934..060d021 100644 --- a/test/TestBuildingBlocks/TestBuildingBlocks.csproj +++ b/test/TestBuildingBlocks/TestBuildingBlocks.csproj @@ -1,6 +1,6 @@  - net8.0;net6.0 + net9.0;net8.0 @@ -22,6 +22,6 @@ - + diff --git a/test/TestBuildingBlocks/TestControllerProvider.cs b/test/TestBuildingBlocks/TestControllerProvider.cs index 664fe34..4f183e5 100644 --- a/test/TestBuildingBlocks/TestControllerProvider.cs +++ b/test/TestBuildingBlocks/TestControllerProvider.cs @@ -5,9 +5,9 @@ namespace TestBuildingBlocks; internal sealed class TestControllerProvider : ControllerFeatureProvider { - private readonly ISet _allowedControllerTypes = new HashSet(); + private readonly HashSet _allowedControllerTypes = []; - internal ISet ControllerAssemblies { get; } = new HashSet(); + internal HashSet ControllerAssemblies { get; } = []; public void AddController(Type controller) { diff --git a/tests.runsettings b/tests.runsettings index db83eb9..1f4ca55 100644 --- a/tests.runsettings +++ b/tests.runsettings @@ -1,5 +1,8 @@ + + true + true @@ -7,7 +10,8 @@ - ObsoleteAttribute,GeneratedCodeAttribute + **/test/**/*.* + ObsoleteAttribute,GeneratedCodeAttribute,TestSDKAutoGeneratedCode true From b969800a7d7b2c187d1afdae38503a24be9ffe69 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sun, 27 Apr 2025 01:15:47 +0200 Subject: [PATCH 13/14] Update to latest version of MongoDB.Driver --- package-versions.props | 6 +-- .../Repositories/MongoRepository.cs | 8 ++-- .../TestBuildingBlocks/MongoRunnerProvider.cs | 47 +++++++++++++++---- .../TestBuildingBlocks.csproj | 5 +- 4 files changed, 47 insertions(+), 19 deletions(-) diff --git a/package-versions.props b/package-versions.props index 14ed6fe..785cca5 100644 --- a/package-versions.props +++ b/package-versions.props @@ -2,16 +2,16 @@ 5.6.0 - 2.28.0 + 3.3.0 35.6.* 6.0.* - 2.0.* + 3.0.* 7.2.* 2.4.* 2.0.* - 2.28.* + 3.3.* 17.13.* 2.9.* 2.8.* diff --git a/src/JsonApiDotNetCore.MongoDb/Repositories/MongoRepository.cs b/src/JsonApiDotNetCore.MongoDb/Repositories/MongoRepository.cs index 0221b00..3a0edfc 100644 --- a/src/JsonApiDotNetCore.MongoDb/Repositories/MongoRepository.cs +++ b/src/JsonApiDotNetCore.MongoDb/Repositories/MongoRepository.cs @@ -74,7 +74,7 @@ public virtual async Task> GetAsync(QueryLayer qu { ArgumentNullException.ThrowIfNull(queryLayer); - IMongoQueryable query = ApplyQueryLayer(queryLayer); + IQueryable query = ApplyQueryLayer(queryLayer); List? resources = await query.ToListAsync(cancellationToken); return resources.AsReadOnly(); } @@ -89,12 +89,12 @@ public virtual Task CountAsync(FilterExpression? topFilter, CancellationTok Filter = topFilter }; - IMongoQueryable query = ApplyQueryLayer(layer); + IQueryable query = ApplyQueryLayer(layer); return query.CountAsync(cancellationToken); } #pragma warning disable AV1130 // Return type in method signature should be an interface to an unchangeable collection - protected virtual IMongoQueryable ApplyQueryLayer(QueryLayer queryLayer) + protected virtual IQueryable ApplyQueryLayer(QueryLayer queryLayer) #pragma warning restore AV1130 // Return type in method signature should be an interface to an unchangeable collection { ArgumentNullException.ThrowIfNull(queryLayer); @@ -127,7 +127,7 @@ protected virtual IMongoQueryable ApplyQueryLayer(QueryLayer queryLay var context = QueryableBuilderContext.CreateRoot(source, typeof(Queryable), _mongoDataAccess.EntityModel, null); Expression expression = _queryableBuilder.ApplyQuery(queryLayer, context); - return (IMongoQueryable)source.Provider.CreateQuery(expression); + return source.Provider.CreateQuery(expression); } protected virtual IQueryable GetAll() diff --git a/test/TestBuildingBlocks/MongoRunnerProvider.cs b/test/TestBuildingBlocks/MongoRunnerProvider.cs index eb0fe49..6339deb 100644 --- a/test/TestBuildingBlocks/MongoRunnerProvider.cs +++ b/test/TestBuildingBlocks/MongoRunnerProvider.cs @@ -1,4 +1,7 @@ using EphemeralMongo; +using MongoDB.Bson; +using MongoDB.Bson.Serialization; +using MongoDB.Bson.Serialization.Serializers; namespace TestBuildingBlocks; @@ -6,6 +9,7 @@ namespace TestBuildingBlocks; internal sealed class MongoRunnerProvider { public static readonly MongoRunnerProvider Instance = new(); + private static readonly GuidSerializer StandardGuidSerializer = new(GuidRepresentation.Standard); #if NET8_0 private readonly object _lockObject = new(); @@ -26,11 +30,13 @@ public IMongoRunner Get() { if (_runner == null) { + BsonSerializer.TryRegisterSerializer(StandardGuidSerializer); + var runnerOptions = new MongoRunnerOptions { // Single-node replica set mode is required for transaction support in MongoDB. UseSingleNodeReplicaSet = true, - AdditionalArguments = "--quiet" + AdditionalArguments = ["--quiet"] }; _runner = MongoRunner.Run(runnerOptions); @@ -63,20 +69,45 @@ private sealed class MongoRunnerWrapper(MongoRunnerProvider owner, IMongoRunner private readonly MongoRunnerProvider _owner = owner; private IMongoRunner? _underlyingMongoRunner = underlyingMongoRunner; - public string ConnectionString => _underlyingMongoRunner?.ConnectionString ?? throw new ObjectDisposedException(nameof(IMongoRunner)); + public string ConnectionString + { + get + { + ObjectDisposedException.ThrowIf(_underlyingMongoRunner == null, typeof(IMongoRunner)); + return _underlyingMongoRunner.ConnectionString; + } + } + + public void Import(string database, string collection, string inputFilePath, string[]? additionalArguments = null, bool drop = false, + CancellationToken cancellationToken = default) + { + ObjectDisposedException.ThrowIf(_underlyingMongoRunner == null, typeof(IMongoRunner)); + + _underlyingMongoRunner.Import(database, collection, inputFilePath, additionalArguments, drop, cancellationToken); + } + + public async Task ImportAsync(string database, string collection, string inputFilePath, string[]? additionalArguments = null, bool drop = false, + CancellationToken cancellationToken = default) + { + ObjectDisposedException.ThrowIf(_underlyingMongoRunner == null, typeof(IMongoRunner)); + + await _underlyingMongoRunner.ImportAsync(database, collection, inputFilePath, additionalArguments, drop, cancellationToken); + } - public void Import(string database, string collection, string inputFilePath, string? additionalArguments = null, bool drop = false) + public void Export(string database, string collection, string outputFilePath, string[]? additionalArguments = null, + CancellationToken cancellationToken = default) { - ObjectDisposedException.ThrowIf(_underlyingMongoRunner == null, this); + ObjectDisposedException.ThrowIf(_underlyingMongoRunner == null, typeof(IMongoRunner)); - _underlyingMongoRunner.Import(database, collection, inputFilePath, additionalArguments, drop); + _underlyingMongoRunner.Export(database, collection, outputFilePath, additionalArguments, cancellationToken); } - public void Export(string database, string collection, string outputFilePath, string? additionalArguments = null) + public async Task ExportAsync(string database, string collection, string outputFilePath, string[]? additionalArguments = null, + CancellationToken cancellationToken = default) { - ObjectDisposedException.ThrowIf(_underlyingMongoRunner == null, this); + ObjectDisposedException.ThrowIf(_underlyingMongoRunner == null, typeof(IMongoRunner)); - _underlyingMongoRunner.Export(database, collection, outputFilePath, additionalArguments); + await _underlyingMongoRunner.ExportAsync(database, collection, outputFilePath, additionalArguments, cancellationToken); } public void Dispose() diff --git a/test/TestBuildingBlocks/TestBuildingBlocks.csproj b/test/TestBuildingBlocks/TestBuildingBlocks.csproj index 060d021..79c9947 100644 --- a/test/TestBuildingBlocks/TestBuildingBlocks.csproj +++ b/test/TestBuildingBlocks/TestBuildingBlocks.csproj @@ -12,10 +12,7 @@ - - - - + From 8d465fdce7bcb67af1fce1e801c31a462e10efc1 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sun, 27 Apr 2025 01:19:49 +0200 Subject: [PATCH 14/14] Update to JsonApiDotNetCore 5.7.1 --- package-versions.props | 2 +- test/TestBuildingBlocks/IntegrationTest.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-versions.props b/package-versions.props index 785cca5..9bf2a49 100644 --- a/package-versions.props +++ b/package-versions.props @@ -1,7 +1,7 @@ - 5.6.0 + 5.7.1 3.3.0 diff --git a/test/TestBuildingBlocks/IntegrationTest.cs b/test/TestBuildingBlocks/IntegrationTest.cs index e74c892..4fc245a 100644 --- a/test/TestBuildingBlocks/IntegrationTest.cs +++ b/test/TestBuildingBlocks/IntegrationTest.cs @@ -13,10 +13,10 @@ namespace TestBuildingBlocks; /// public abstract class IntegrationTest : IAsyncLifetime { - private static readonly MediaTypeHeaderValue DefaultMediaType = MediaTypeHeaderValue.Parse(HeaderConstants.MediaType); + private static readonly MediaTypeHeaderValue DefaultMediaType = MediaTypeHeaderValue.Parse(JsonApiMediaType.Default.ToString()); private static readonly MediaTypeWithQualityHeaderValue OperationsMediaType = - MediaTypeWithQualityHeaderValue.Parse(HeaderConstants.AtomicOperationsMediaType); + MediaTypeWithQualityHeaderValue.Parse(JsonApiMediaType.AtomicOperations.ToString()); private static readonly SemaphoreSlim ThrottleSemaphore = GetDefaultThrottleSemaphore(); pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy