diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 191e32f..8546a5a 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,13 +3,13 @@ "isRoot": true, "tools": { "jetbrains.resharper.globaltools": { - "version": "2021.1.3", + "version": "2021.3.0", "commands": [ "jb" ] }, "regitlint": { - "version": "2.1.4", + "version": "6.0.6", "commands": [ "regitlint" ] @@ -21,7 +21,7 @@ ] }, "dotnet-reportgenerator-globaltool": { - "version": "4.8.11", + "version": "5.0.0", "commands": [ "reportgenerator" ] diff --git a/Build.ps1 b/Build.ps1 index bb8ab69..e973ea3 100644 --- a/Build.ps1 +++ b/Build.ps1 @@ -8,7 +8,8 @@ function CheckLastExitCode { function RunInspectCode { $outputPath = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), 'jetbrains-inspectcode-results.xml') - dotnet jb inspectcode JsonApiDotNetCore.MongoDb.sln --output="$outputPath" --profile=WarningSeverities.DotSettings --properties:Configuration=Release --severity=WARNING --verbosity=WARN -dsl=GlobalAll -dsl=SolutionPersonal -dsl=ProjectPersonal + # passing --build instead of --no-build as workaround for https://youtrack.jetbrains.com/issue/RSRP-487054 + dotnet jb inspectcode JsonApiDotNetCore.MongoDb.sln --build --output="$outputPath" --profile=WarningSeverities.DotSettings --properties:Configuration=Release --severity=WARNING --verbosity=WARN -dsl=GlobalAll -dsl=GlobalPerProduct -dsl=SolutionPersonal -dsl=ProjectPersonal CheckLastExitCode [xml]$xml = Get-Content "$outputPath" @@ -47,7 +48,7 @@ function RunCleanupCode { $mergeCommitHash = git rev-parse "HEAD" $targetCommitHash = git rev-parse "$env:APPVEYOR_REPO_BRANCH" - dotnet regitlint -s JsonApiDotNetCore.MongoDb.sln --print-command --jb --profile --jb --profile='\"JADNC Full Cleanup\"' --jb --properties:Configuration=Release --jb --verbosity=WARN -f commits -a $mergeCommitHash -b $targetCommitHash --fail-on-diff --print-diff + dotnet regitlint -s JsonApiDotNetCore.MongoDb.sln --print-command --disable-jb-path-hack --jb --profile='\"JADNC Full Cleanup\"' --jb --properties:Configuration=Release --jb --verbosity=WARN -f commits -a $mergeCommitHash -b $targetCommitHash --fail-on-diff --print-diff CheckLastExitCode } } @@ -73,10 +74,10 @@ function CreateNuGetPackage { $versionSuffix = $suffixSegments -join "-" } else { - # Get the version suffix from the auto-incrementing build number. Example: "123" => "pre-0123". + # Get the version suffix from the auto-incrementing build number. Example: "123" => "master-0123". if ($env:APPVEYOR_BUILD_NUMBER) { $revision = "{0:D4}" -f [convert]::ToInt32($env:APPVEYOR_BUILD_NUMBER, 10) - $versionSuffix = "pre-$revision" + $versionSuffix = "$($env:APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH ?? $env:APPVEYOR_REPO_BRANCH)-$revision" } else { $versionSuffix = "pre-0001" @@ -84,10 +85,10 @@ function CreateNuGetPackage { } if ([string]::IsNullOrWhitespace($versionSuffix)) { - dotnet pack .\src\JsonApiDotNetCore.MongoDb -c Release -o .\artifacts + dotnet pack --no-restore --no-build --configuration Release --output .\artifacts } else { - dotnet pack .\src\JsonApiDotNetCore.MongoDb -c Release -o .\artifacts --version-suffix=$versionSuffix + dotnet pack --no-restore --no-build --configuration Release --output .\artifacts --version-suffix=$versionSuffix } CheckLastExitCode diff --git a/CodingGuidelines.ruleset b/CodingGuidelines.ruleset index 9447b10..05545fb 100644 --- a/CodingGuidelines.ruleset +++ b/CodingGuidelines.ruleset @@ -26,6 +26,6 @@ - + \ No newline at end of file diff --git a/Directory.Build.props b/Directory.Build.props index b7265fa..02d86f7 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,31 +1,34 @@ - netcoreapp3.1 - 3.1.* - 4.2.* - 2.12.* - $(SolutionDir)CodingGuidelines.ruleset + net6.0 + 6.0.* + 5.0.0-pre1 + 2.14.* + 5.0.0 + $(MSBuildThisFileDirectory)CodingGuidelines.ruleset + 9999 + enable + enable + false + false - - - + + + - $(NoWarn);1591 + $(NoWarn);1591;NU5104 true true - 33.0.2 - 3.0.3 - 5.10.3 + 3.1.0 4.16.1 - 2.4.* - 16.10.0 + 17.0.0 diff --git a/JsonApiDotNetCore.MongoDb.sln b/JsonApiDotNetCore.MongoDb.sln index 4a2fce5..9514464 100644 --- a/JsonApiDotNetCore.MongoDb.sln +++ b/JsonApiDotNetCore.MongoDb.sln @@ -1,6 +1,7 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30804.86 + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31919.166 MinimumVisualStudioVersion = 15.0.26124.0 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{7E29AA10-F938-4CF8-9CAB-7ACD2D6DC784}" EndProject @@ -12,10 +13,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GettingStarted", "src\Examp EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JsonApiDotNetCoreMongoDbExample", "src\Examples\JsonApiDotNetCoreMongoDbExample\JsonApiDotNetCoreMongoDbExample.csproj", "{11CC33C8-27D7-44D2-B402-76E3A33285A0}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JsonApiDotNetCoreMongoDbExampleTests", "test\JsonApiDotNetCoreMongoDbExampleTests\JsonApiDotNetCoreMongoDbExampleTests.csproj", "{24CE53FA-9C49-4E20-A060-4A43DFB8C8F1}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JsonApiDotNetCore.MongoDb", "src\JsonApiDotNetCore.MongoDb\JsonApiDotNetCore.MongoDb.csproj", "{FD312677-2A62-4B8F-A965-879B059F1755}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JsonApiDotNetCoreMongoDbTests", "test\JsonApiDotNetCoreMongoDbTests\JsonApiDotNetCoreMongoDbTests.csproj", "{911271DD-29BC-40BC-A9CC-29BE7163B66B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestBuildingBlocks", "test\TestBuildingBlocks\TestBuildingBlocks.csproj", "{6A0AC606-F11E-4090-B12A-C547BD17EE56}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -50,18 +53,6 @@ Global {11CC33C8-27D7-44D2-B402-76E3A33285A0}.Release|x64.Build.0 = Release|Any CPU {11CC33C8-27D7-44D2-B402-76E3A33285A0}.Release|x86.ActiveCfg = Release|Any CPU {11CC33C8-27D7-44D2-B402-76E3A33285A0}.Release|x86.Build.0 = Release|Any CPU - {24CE53FA-9C49-4E20-A060-4A43DFB8C8F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {24CE53FA-9C49-4E20-A060-4A43DFB8C8F1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {24CE53FA-9C49-4E20-A060-4A43DFB8C8F1}.Debug|x64.ActiveCfg = Debug|Any CPU - {24CE53FA-9C49-4E20-A060-4A43DFB8C8F1}.Debug|x64.Build.0 = Debug|Any CPU - {24CE53FA-9C49-4E20-A060-4A43DFB8C8F1}.Debug|x86.ActiveCfg = Debug|Any CPU - {24CE53FA-9C49-4E20-A060-4A43DFB8C8F1}.Debug|x86.Build.0 = Debug|Any CPU - {24CE53FA-9C49-4E20-A060-4A43DFB8C8F1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {24CE53FA-9C49-4E20-A060-4A43DFB8C8F1}.Release|Any CPU.Build.0 = Release|Any CPU - {24CE53FA-9C49-4E20-A060-4A43DFB8C8F1}.Release|x64.ActiveCfg = Release|Any CPU - {24CE53FA-9C49-4E20-A060-4A43DFB8C8F1}.Release|x64.Build.0 = Release|Any CPU - {24CE53FA-9C49-4E20-A060-4A43DFB8C8F1}.Release|x86.ActiveCfg = Release|Any CPU - {24CE53FA-9C49-4E20-A060-4A43DFB8C8F1}.Release|x86.Build.0 = Release|Any CPU {FD312677-2A62-4B8F-A965-879B059F1755}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FD312677-2A62-4B8F-A965-879B059F1755}.Debug|Any CPU.Build.0 = Debug|Any CPU {FD312677-2A62-4B8F-A965-879B059F1755}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -74,6 +65,30 @@ Global {FD312677-2A62-4B8F-A965-879B059F1755}.Release|x64.Build.0 = Release|Any CPU {FD312677-2A62-4B8F-A965-879B059F1755}.Release|x86.ActiveCfg = Release|Any CPU {FD312677-2A62-4B8F-A965-879B059F1755}.Release|x86.Build.0 = Release|Any CPU + {911271DD-29BC-40BC-A9CC-29BE7163B66B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {911271DD-29BC-40BC-A9CC-29BE7163B66B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {911271DD-29BC-40BC-A9CC-29BE7163B66B}.Debug|x64.ActiveCfg = Debug|Any CPU + {911271DD-29BC-40BC-A9CC-29BE7163B66B}.Debug|x64.Build.0 = Debug|Any CPU + {911271DD-29BC-40BC-A9CC-29BE7163B66B}.Debug|x86.ActiveCfg = Debug|Any CPU + {911271DD-29BC-40BC-A9CC-29BE7163B66B}.Debug|x86.Build.0 = Debug|Any CPU + {911271DD-29BC-40BC-A9CC-29BE7163B66B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {911271DD-29BC-40BC-A9CC-29BE7163B66B}.Release|Any CPU.Build.0 = Release|Any CPU + {911271DD-29BC-40BC-A9CC-29BE7163B66B}.Release|x64.ActiveCfg = Release|Any CPU + {911271DD-29BC-40BC-A9CC-29BE7163B66B}.Release|x64.Build.0 = Release|Any CPU + {911271DD-29BC-40BC-A9CC-29BE7163B66B}.Release|x86.ActiveCfg = Release|Any CPU + {911271DD-29BC-40BC-A9CC-29BE7163B66B}.Release|x86.Build.0 = Release|Any CPU + {6A0AC606-F11E-4090-B12A-C547BD17EE56}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6A0AC606-F11E-4090-B12A-C547BD17EE56}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6A0AC606-F11E-4090-B12A-C547BD17EE56}.Debug|x64.ActiveCfg = Debug|Any CPU + {6A0AC606-F11E-4090-B12A-C547BD17EE56}.Debug|x64.Build.0 = Debug|Any CPU + {6A0AC606-F11E-4090-B12A-C547BD17EE56}.Debug|x86.ActiveCfg = Debug|Any CPU + {6A0AC606-F11E-4090-B12A-C547BD17EE56}.Debug|x86.Build.0 = Debug|Any CPU + {6A0AC606-F11E-4090-B12A-C547BD17EE56}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6A0AC606-F11E-4090-B12A-C547BD17EE56}.Release|Any CPU.Build.0 = Release|Any CPU + {6A0AC606-F11E-4090-B12A-C547BD17EE56}.Release|x64.ActiveCfg = Release|Any CPU + {6A0AC606-F11E-4090-B12A-C547BD17EE56}.Release|x64.Build.0 = Release|Any CPU + {6A0AC606-F11E-4090-B12A-C547BD17EE56}.Release|x86.ActiveCfg = Release|Any CPU + {6A0AC606-F11E-4090-B12A-C547BD17EE56}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -81,8 +96,9 @@ Global GlobalSection(NestedProjects) = preSolution {AA148569-62FF-4E1A-8E16-0E529FA38040} = {7E29AA10-F938-4CF8-9CAB-7ACD2D6DC784} {11CC33C8-27D7-44D2-B402-76E3A33285A0} = {AA148569-62FF-4E1A-8E16-0E529FA38040} - {24CE53FA-9C49-4E20-A060-4A43DFB8C8F1} = {19A533AA-E006-496D-A476-364DF2B637A1} {FD312677-2A62-4B8F-A965-879B059F1755} = {7E29AA10-F938-4CF8-9CAB-7ACD2D6DC784} + {911271DD-29BC-40BC-A9CC-29BE7163B66B} = {19A533AA-E006-496D-A476-364DF2B637A1} + {6A0AC606-F11E-4090-B12A-C547BD17EE56} = {19A533AA-E006-496D-A476-364DF2B637A1} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {83EBEC34-0CE5-4D16-93CF-EF5B5CCBC538} diff --git a/JsonApiDotNetCore.MongoDb.sln.DotSettings b/JsonApiDotNetCore.MongoDb.sln.DotSettings index e7bfd79..a3f03e1 100644 --- a/JsonApiDotNetCore.MongoDb.sln.DotSettings +++ b/JsonApiDotNetCore.MongoDb.sln.DotSettings @@ -27,6 +27,7 @@ JsonApiDotNetCore.MongoDb.ArgumentGuard.NotNull($EXPR$, $NAME$); SUGGESTION SUGGESTION SUGGESTION + WARNING SUGGESTION SUGGESTION SUGGESTION @@ -56,6 +57,10 @@ JsonApiDotNetCore.MongoDb.ArgumentGuard.NotNull($EXPR$, $NAME$); DO_NOT_SHOW HINT SUGGESTION + WARNING + WARNING + WARNING + WARNING SUGGESTION WARNING SUGGESTION @@ -69,6 +74,7 @@ JsonApiDotNetCore.MongoDb.ArgumentGuard.NotNull($EXPR$, $NAME$); SUGGESTION SUGGESTION SUGGESTION + WARNING WARNING WARNING WARNING @@ -76,9 +82,12 @@ JsonApiDotNetCore.MongoDb.ArgumentGuard.NotNull($EXPR$, $NAME$); SUGGESTION WARNING HINT + WARNING WARNING + WARNING + WARNING WARNING - <?xml version="1.0" encoding="utf-16"?><Profile name="JADNC Full Cleanup"><XMLReformatCode>True</XMLReformatCode><CSCodeStyleAttributes ArrangeTypeAccessModifier="True" ArrangeTypeMemberAccessModifier="True" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="True" ArrangeBraces="True" ArrangeAttributes="True" ArrangeArgumentsStyle="True" ArrangeCodeBodyStyle="True" ArrangeVarStyle="True" ArrangeTrailingCommas="True" ArrangeObjectCreation="True" ArrangeDefaultValue="True" /><CssAlphabetizeProperties>True</CssAlphabetizeProperties><JsInsertSemicolon>True</JsInsertSemicolon><FormatAttributeQuoteDescriptor>True</FormatAttributeQuoteDescriptor><CorrectVariableKindsDescriptor>True</CorrectVariableKindsDescriptor><VariablesToInnerScopesDescriptor>True</VariablesToInnerScopesDescriptor><StringToTemplatesDescriptor>True</StringToTemplatesDescriptor><JsReformatCode>True</JsReformatCode><JsFormatDocComments>True</JsFormatDocComments><RemoveRedundantQualifiersTs>True</RemoveRedundantQualifiersTs><OptimizeImportsTs>True</OptimizeImportsTs><OptimizeReferenceCommentsTs>True</OptimizeReferenceCommentsTs><PublicModifierStyleTs>True</PublicModifierStyleTs><ExplicitAnyTs>True</ExplicitAnyTs><TypeAnnotationStyleTs>True</TypeAnnotationStyleTs><RelativePathStyleTs>True</RelativePathStyleTs><AsInsteadOfCastTs>True</AsInsteadOfCastTs><HtmlReformatCode>True</HtmlReformatCode><AspOptimizeRegisterDirectives>True</AspOptimizeRegisterDirectives><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSUseAutoProperty>True</CSUseAutoProperty><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSMakeAutoPropertyGetOnly>True</CSMakeAutoPropertyGetOnly><CSArrangeQualifiers>True</CSArrangeQualifiers><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CssReformatCode>True</CssReformatCode><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSharpFormatDocComments>True</CSharpFormatDocComments><CSReorderTypeMembers>True</CSReorderTypeMembers><XAMLCollapseEmptyTags>False</XAMLCollapseEmptyTags></Profile> + <?xml version="1.0" encoding="utf-16"?><Profile name="JADNC Full Cleanup"><XMLReformatCode>True</XMLReformatCode><CSCodeStyleAttributes ArrangeTypeAccessModifier="True" ArrangeTypeMemberAccessModifier="True" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="True" ArrangeBraces="True" ArrangeAttributes="True" ArrangeArgumentsStyle="True" ArrangeCodeBodyStyle="True" ArrangeVarStyle="True" ArrangeTrailingCommas="True" ArrangeObjectCreation="True" ArrangeDefaultValue="True" ArrangeNamespaces="True" /><CssAlphabetizeProperties>True</CssAlphabetizeProperties><JsInsertSemicolon>True</JsInsertSemicolon><FormatAttributeQuoteDescriptor>True</FormatAttributeQuoteDescriptor><CorrectVariableKindsDescriptor>True</CorrectVariableKindsDescriptor><VariablesToInnerScopesDescriptor>True</VariablesToInnerScopesDescriptor><StringToTemplatesDescriptor>True</StringToTemplatesDescriptor><JsReformatCode>True</JsReformatCode><JsFormatDocComments>True</JsFormatDocComments><RemoveRedundantQualifiersTs>True</RemoveRedundantQualifiersTs><OptimizeImportsTs>True</OptimizeImportsTs><OptimizeReferenceCommentsTs>True</OptimizeReferenceCommentsTs><PublicModifierStyleTs>True</PublicModifierStyleTs><ExplicitAnyTs>True</ExplicitAnyTs><TypeAnnotationStyleTs>True</TypeAnnotationStyleTs><RelativePathStyleTs>True</RelativePathStyleTs><AsInsteadOfCastTs>True</AsInsteadOfCastTs><HtmlReformatCode>True</HtmlReformatCode><AspOptimizeRegisterDirectives>True</AspOptimizeRegisterDirectives><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSUseAutoProperty>True</CSUseAutoProperty><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSMakeAutoPropertyGetOnly>True</CSMakeAutoPropertyGetOnly><CSArrangeQualifiers>True</CSArrangeQualifiers><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><CssReformatCode>True</CssReformatCode><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><CSharpFormatDocComments>True</CSharpFormatDocComments><CSReorderTypeMembers>True</CSReorderTypeMembers><XAMLCollapseEmptyTags>False</XAMLCollapseEmptyTags></Profile> JADNC Full Cleanup Required Required @@ -86,10 +95,12 @@ JsonApiDotNetCore.MongoDb.ArgumentGuard.NotNull($EXPR$, $NAME$); Required Conditional False + False 1 1 1 1 + False True True True diff --git a/README.md b/README.md index f925080..e645d8b 100644 --- a/README.md +++ b/README.md @@ -15,76 +15,62 @@ dotnet add package JsonApiDotNetCore.MongoDb ### Models ```c# +#nullable enable + +[Resource] public class Book : MongoIdentifiable { [Attr] - public string Name { get; set; } + public string Name { get; set; } = null!; } ``` -### Controllers +### Middleware ```c# -public class BooksController : JsonApiController -{ - public BooksController(IJsonApiOptions options, ILoggerFactory loggerFactory, - IResourceService resourceService) - : base(options, loggerFactory, resourceService) - { - } -} -``` +// Program.cs -### Middleware +#nullable enable -```c# -public class Startup +WebApplicationBuilder builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddSingleton(_ => { - public IServiceProvider ConfigureServices(IServiceCollection services) - { - services.AddSingleton(_ => - { - var client = new MongoClient("mongodb://localhost:27017"); - return client.GetDatabase("ExampleDbName"); - }); - - services.AddJsonApi(resources: builder => - { - builder.Add(); - }); - services.AddJsonApiMongoDb(); - - services.AddResourceRepository>(); - } - - public void Configure(IApplicationBuilder app) - { - app.UseRouting(); - app.UseJsonApi(); - app.UseEndpoints(endpoints => endpoints.MapControllers()); - } -} + var client = new MongoClient("mongodb://localhost:27017"); + return client.GetDatabase("ExampleDbName"); +}); + +builder.Services.AddJsonApi(resources: resourceGraphBuilder => +{ + resourceGraphBuilder.Add(); +}); + +builder.Services.AddJsonApiMongoDb(); + +builder.Services.AddResourceRepository>(); + +// Configure the HTTP request pipeline. + +app.UseRouting(); +app.UseJsonApi(); +app.MapControllers(); + +app.Run(); ``` -Note: If your API project uses only MongoDB (not in combination with EF Core), then instead of + +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: + ```c# -public class Startup -{ - public IServiceProvider ConfigureServices(IServiceCollection services) - { - // ... - - services.AddJsonApi(facade => facade.AddCurrentAssembly()); - services.AddJsonApiMongoDb(); - - services.AddScoped(typeof(IResourceReadRepository<>), typeof(MongoRepository<>)); - services.AddScoped(typeof(IResourceReadRepository<,>), typeof(MongoRepository<,>)); - services.AddScoped(typeof(IResourceWriteRepository<>), typeof(MongoRepository<>)); - services.AddScoped(typeof(IResourceWriteRepository<,>), typeof(MongoRepository<,>)); - services.AddScoped(typeof(IResourceRepository<>), typeof(MongoRepository<>)); - services.AddScoped(typeof(IResourceRepository<,>), typeof(MongoRepository<,>)); - } -} +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<,>)); + ``` ## Limitations diff --git a/appveyor.yml b/appveyor.yml index a9427c0..e51d058 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,6 +1,6 @@ image: - - Ubuntu - - Visual Studio 2019 + - Ubuntu2004 + - Visual Studio 2022 version: '{build}' @@ -24,7 +24,7 @@ for: - matrix: only: - - image: Visual Studio 2019 + - image: Visual Studio 2022 artifacts: - path: .\**\artifacts\**\*.nupkg name: NuGet diff --git a/cleanupcode.ps1 b/cleanupcode.ps1 index 17b143e..2d9cdca 100644 --- a/cleanupcode.ps1 +++ b/cleanupcode.ps1 @@ -8,10 +8,10 @@ if ($LASTEXITCODE -ne 0) { throw "Tool restore failed with exit code $LASTEXITCODE" } -dotnet build -c Release +dotnet restore if ($LASTEXITCODE -ne 0) { - throw "Build failed with exit code $LASTEXITCODE" + throw "Package restore failed with exit code $LASTEXITCODE" } -dotnet regitlint -s JsonApiDotNetCore.MongoDb.sln --print-command --jb --profile --jb --profile='\"JADNC Full Cleanup\"' --jb --properties:Configuration=Release --jb --verbosity=WARN +dotnet regitlint -s JsonApiDotNetCore.MongoDb.sln --print-command --disable-jb-path-hack --jb --profile='\"JADNC Full Cleanup\"' --jb --properties:Configuration=Release --jb --verbosity=WARN diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..32a5184 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,13 @@ +coverage: + status: + project: + default: + target: auto + threshold: 10% + informational: true + patch: + default: + informational: true + +github_checks: + annotations: false diff --git a/inspectcode.ps1 b/inspectcode.ps1 index 2f983ca..0c55d7b 100644 --- a/inspectcode.ps1 +++ b/inspectcode.ps1 @@ -8,15 +8,9 @@ if ($LASTEXITCODE -ne 0) { throw "Tool restore failed with exit code $LASTEXITCODE" } -dotnet build -c Release - -if ($LASTEXITCODE -ne 0) { - throw "Build failed with exit code $LASTEXITCODE" -} - $outputPath = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), 'jetbrains-inspectcode-results.xml') $resultPath = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), 'jetbrains-inspectcode-results.html') -dotnet jb inspectcode JsonApiDotNetCore.MongoDb.sln --output="$outputPath" --profile=WarningSeverities.DotSettings --properties:Configuration=Release --severity=WARNING --verbosity=WARN -dsl=GlobalAll -dsl=SolutionPersonal -dsl=ProjectPersonal +dotnet jb inspectcode JsonApiDotNetCore.MongoDb.sln --build --output="$outputPath" --profile=WarningSeverities.DotSettings --properties:Configuration=Release --severity=WARNING --verbosity=WARN -dsl=GlobalAll -dsl=GlobalPerProduct -dsl=SolutionPersonal -dsl=ProjectPersonal if ($LASTEXITCODE -ne 0) { throw "Code inspection failed with exit code $LASTEXITCODE" diff --git a/logo.png b/logo.png new file mode 100644 index 0000000..78f1acd Binary files /dev/null and b/logo.png differ diff --git a/src/Examples/GettingStarted/Controllers/BooksController.cs b/src/Examples/GettingStarted/Controllers/BooksController.cs deleted file mode 100644 index e9a0b28..0000000 --- a/src/Examples/GettingStarted/Controllers/BooksController.cs +++ /dev/null @@ -1,16 +0,0 @@ -using GettingStarted.Models; -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Services; -using Microsoft.Extensions.Logging; - -namespace GettingStarted.Controllers -{ - public sealed class BooksController : JsonApiController - { - public BooksController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) - : base(options, loggerFactory, resourceService) - { - } - } -} diff --git a/src/Examples/GettingStarted/GettingStarted.csproj b/src/Examples/GettingStarted/GettingStarted.csproj index 1d4e58f..a5b25ea 100644 --- a/src/Examples/GettingStarted/GettingStarted.csproj +++ b/src/Examples/GettingStarted/GettingStarted.csproj @@ -1,6 +1,6 @@ - $(NetCoreAppVersion) + $(TargetFrameworkName) diff --git a/src/Examples/GettingStarted/Models/Book.cs b/src/Examples/GettingStarted/Models/Book.cs index 063b2f7..b9853d1 100644 --- a/src/Examples/GettingStarted/Models/Book.cs +++ b/src/Examples/GettingStarted/Models/Book.cs @@ -2,18 +2,18 @@ using JsonApiDotNetCore.MongoDb.Resources; using JsonApiDotNetCore.Resources.Annotations; -namespace GettingStarted.Models +namespace GettingStarted.Models; + +[UsedImplicitly(ImplicitUseTargetFlags.Members)] +[Resource] +public sealed class Book : MongoIdentifiable { - [UsedImplicitly(ImplicitUseTargetFlags.Members)] - public sealed class Book : MongoIdentifiable - { - [Attr] - public string Title { get; set; } + [Attr] + public string Title { get; set; } = null!; - [Attr] - public int PublishYear { get; set; } + [Attr] + public string Author { get; set; } = null!; - [Attr] - public string Author { get; set; } - } + [Attr] + public int PublishYear { get; set; } } diff --git a/src/Examples/GettingStarted/Program.cs b/src/Examples/GettingStarted/Program.cs index 68bca0a..e28c933 100644 --- a/src/Examples/GettingStarted/Program.cs +++ b/src/Examples/GettingStarted/Program.cs @@ -1,21 +1,72 @@ -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; +using GettingStarted.Models; +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.MongoDb.Configuration; +using JsonApiDotNetCore.MongoDb.Repositories; +using MongoDB.Driver; -namespace GettingStarted +WebApplicationBuilder builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddSingleton(_ => +{ + var client = new MongoClient(builder.Configuration.GetSection("DatabaseSettings:ConnectionString").Value); + return client.GetDatabase(builder.Configuration.GetSection("DatabaseSettings:Database").Value); +}); + +builder.Services.AddJsonApi(ConfigureJsonApiOptions, resources: resourceGraphBuilder => +{ + resourceGraphBuilder.Add(); +}); + +builder.Services.AddJsonApiMongoDb(); + +builder.Services.AddResourceRepository>(); + +WebApplication app = builder.Build(); + +// Configure the HTTP request pipeline. + +app.UseRouting(); +app.UseJsonApi(); +app.MapControllers(); + +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; +} + +static async Task CreateSampleDataAsync(IMongoDatabase database) { - internal static class Program + await database.DropCollectionAsync(nameof(Book)); + + await database.GetCollection(nameof(Book)).InsertManyAsync(new[] { - public static void Main(string[] args) + new Book { - CreateHostBuilder(args).Build().Run(); - } - - private static IHostBuilder CreateHostBuilder(string[] args) + Title = "Frankenstein", + PublishYear = 1818, + Author = "Mary Shelley" + }, + new Book + { + Title = "Robinson Crusoe", + PublishYear = 1719, + Author = "Daniel Defoe" + }, + new Book { - return Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); + Title = "Gulliver's Travels", + PublishYear = 1726, + Author = "Jonathan Swift" } - } + }); } diff --git a/src/Examples/GettingStarted/Startup.cs b/src/Examples/GettingStarted/Startup.cs deleted file mode 100644 index fdb6aab..0000000 --- a/src/Examples/GettingStarted/Startup.cs +++ /dev/null @@ -1,84 +0,0 @@ -using GettingStarted.Models; -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.MongoDb.Configuration; -using JsonApiDotNetCore.MongoDb.Repositories; -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using MongoDB.Driver; -using Newtonsoft.Json; - -namespace GettingStarted -{ - public sealed class Startup - { - private readonly IConfiguration _configuration; - - public Startup(IConfiguration configuration) - { - _configuration = configuration; - } - - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) - { - services.AddSingleton(_ => - { - var client = new MongoClient(_configuration.GetSection("DatabaseSettings:ConnectionString").Value); - return client.GetDatabase(_configuration.GetSection("DatabaseSettings:Database").Value); - }); - - services.AddJsonApi(ConfigureJsonApiOptions, resources: builder => - { - builder.Add(); - }); - - services.AddJsonApiMongoDb(); - - services.AddResourceRepository>(); - } - - private void ConfigureJsonApiOptions(JsonApiOptions options) - { - options.Namespace = "api"; - options.UseRelativeLinks = true; - options.IncludeTotalResourceCount = true; - options.SerializerSettings.Formatting = Formatting.Indented; - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app) - { - CreateSampleData(app.ApplicationServices.GetService()); - - app.UseRouting(); - app.UseJsonApi(); - app.UseEndpoints(endpoints => endpoints.MapControllers()); - } - - private static void CreateSampleData(IMongoDatabase db) - { - db.GetCollection(nameof(Book)).InsertMany(new[] - { - new Book - { - Title = "Frankenstein", - PublishYear = 1818, - Author = "Mary Shelley" - }, - new Book - { - Title = "Robinson Crusoe", - PublishYear = 1719, - Author = "Daniel Defoe" - }, - new Book - { - Title = "Gulliver's Travels", - PublishYear = 1726, - Author = "Jonathan Swift" - } - }); - } - } -} diff --git a/src/Examples/GettingStarted/appsettings.json b/src/Examples/GettingStarted/appsettings.json index 15c53ba..31455b7 100644 --- a/src/Examples/GettingStarted/appsettings.json +++ b/src/Examples/GettingStarted/appsettings.json @@ -7,7 +7,7 @@ "LogLevel": { "Default": "Warning", "Microsoft.Hosting.Lifetime": "Information", - "Microsoft.EntityFrameworkCore.Database.Command": "Information" + "Microsoft.EntityFrameworkCore": "Critical" } }, "AllowedHosts": "*" diff --git a/src/Examples/JsonApiDotNetCoreMongoDbExample/Controllers/OperationsController.cs b/src/Examples/JsonApiDotNetCoreMongoDbExample/Controllers/OperationsController.cs index b7d6ea3..8172459 100644 --- a/src/Examples/JsonApiDotNetCoreMongoDbExample/Controllers/OperationsController.cs +++ b/src/Examples/JsonApiDotNetCoreMongoDbExample/Controllers/OperationsController.cs @@ -3,16 +3,14 @@ using JsonApiDotNetCore.Controllers; using JsonApiDotNetCore.Middleware; using JsonApiDotNetCore.Resources; -using Microsoft.Extensions.Logging; -namespace JsonApiDotNetCoreMongoDbExample.Controllers +namespace JsonApiDotNetCoreMongoDbExample.Controllers; + +public sealed class OperationsController : JsonApiOperationsController { - public sealed class OperationsController : JsonApiOperationsController + public OperationsController(IJsonApiOptions options, IResourceGraph resourceGraph, ILoggerFactory loggerFactory, IOperationsProcessor processor, + IJsonApiRequest request, ITargetedFields targetedFields) + : base(options, resourceGraph, loggerFactory, processor, request, targetedFields) { - public OperationsController(IJsonApiOptions options, ILoggerFactory loggerFactory, IOperationsProcessor processor, IJsonApiRequest request, - ITargetedFields targetedFields) - : base(options, loggerFactory, processor, request, targetedFields) - { - } } } diff --git a/src/Examples/JsonApiDotNetCoreMongoDbExample/Controllers/TodoItemsController.cs b/src/Examples/JsonApiDotNetCoreMongoDbExample/Controllers/TodoItemsController.cs deleted file mode 100644 index 584b901..0000000 --- a/src/Examples/JsonApiDotNetCoreMongoDbExample/Controllers/TodoItemsController.cs +++ /dev/null @@ -1,16 +0,0 @@ -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Services; -using JsonApiDotNetCoreMongoDbExample.Models; -using Microsoft.Extensions.Logging; - -namespace JsonApiDotNetCoreMongoDbExample.Controllers -{ - public sealed class TodoItemsController : JsonApiController - { - public TodoItemsController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) - : base(options, loggerFactory, resourceService) - { - } - } -} diff --git a/src/Examples/JsonApiDotNetCoreMongoDbExample/Definitions/TodoItemDefinition.cs b/src/Examples/JsonApiDotNetCoreMongoDbExample/Definitions/TodoItemDefinition.cs new file mode 100644 index 0000000..8079def --- /dev/null +++ b/src/Examples/JsonApiDotNetCoreMongoDbExample/Definitions/TodoItemDefinition.cs @@ -0,0 +1,50 @@ +using System.ComponentModel; +using JetBrains.Annotations; +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Middleware; +using JsonApiDotNetCore.Queries.Expressions; +using JsonApiDotNetCore.Resources; +using JsonApiDotNetCoreMongoDbExample.Models; +using Microsoft.AspNetCore.Authentication; + +namespace JsonApiDotNetCoreMongoDbExample.Definitions; + +[UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] +public sealed class TodoItemDefinition : JsonApiResourceDefinition +{ + private readonly ISystemClock _systemClock; + + public TodoItemDefinition(IResourceGraph resourceGraph, ISystemClock systemClock) + : base(resourceGraph) + { + _systemClock = systemClock; + } + + public override SortExpression OnApplySort(SortExpression? existingSort) + { + return existingSort ?? GetDefaultSortOrder(); + } + + private SortExpression GetDefaultSortOrder() + { + return CreateSortExpressionFromLambda(new PropertySortOrder + { + (todoItem => todoItem.Priority, ListSortDirection.Descending), + (todoItem => todoItem.LastModifiedAt, ListSortDirection.Descending) + }); + } + + public override Task OnWritingAsync(TodoItem resource, WriteOperationKind writeOperation, CancellationToken cancellationToken) + { + if (writeOperation == WriteOperationKind.CreateResource) + { + resource.CreatedAt = _systemClock.UtcNow; + } + else if (writeOperation == WriteOperationKind.UpdateResource) + { + resource.LastModifiedAt = _systemClock.UtcNow; + } + + return Task.CompletedTask; + } +} diff --git a/src/Examples/JsonApiDotNetCoreMongoDbExample/JsonApiDotNetCoreMongoDbExample.csproj b/src/Examples/JsonApiDotNetCoreMongoDbExample/JsonApiDotNetCoreMongoDbExample.csproj index 1d4e58f..a5b25ea 100644 --- a/src/Examples/JsonApiDotNetCoreMongoDbExample/JsonApiDotNetCoreMongoDbExample.csproj +++ b/src/Examples/JsonApiDotNetCoreMongoDbExample/JsonApiDotNetCoreMongoDbExample.csproj @@ -1,6 +1,6 @@ - $(NetCoreAppVersion) + $(TargetFrameworkName) diff --git a/src/Examples/JsonApiDotNetCoreMongoDbExample/Models/TodoItem.cs b/src/Examples/JsonApiDotNetCoreMongoDbExample/Models/TodoItem.cs index 42962c2..e2adddd 100644 --- a/src/Examples/JsonApiDotNetCoreMongoDbExample/Models/TodoItem.cs +++ b/src/Examples/JsonApiDotNetCoreMongoDbExample/Models/TodoItem.cs @@ -1,20 +1,24 @@ -using System; +using System.ComponentModel.DataAnnotations; using JetBrains.Annotations; using JsonApiDotNetCore.MongoDb.Resources; using JsonApiDotNetCore.Resources.Annotations; -namespace JsonApiDotNetCoreMongoDbExample.Models +namespace JsonApiDotNetCoreMongoDbExample.Models; + +[UsedImplicitly(ImplicitUseTargetFlags.Members)] +[Resource] +public sealed class TodoItem : MongoIdentifiable { - [UsedImplicitly(ImplicitUseTargetFlags.Members)] - public sealed class TodoItem : MongoIdentifiable - { - [Attr] - public string Description { get; set; } + [Attr] + public string Description { get; set; } = null!; + + [Attr] + [Required] + public TodoItemPriority? Priority { get; set; } - [Attr] - public DateTimeOffset CreatedAt { get; set; } + [Attr(Capabilities = AttrCapabilities.AllowFilter | AttrCapabilities.AllowSort | AttrCapabilities.AllowView)] + public DateTimeOffset CreatedAt { get; set; } - [Attr] - public DateTimeOffset? CompletedAt { get; set; } - } + [Attr(PublicName = "modifiedAt", Capabilities = AttrCapabilities.AllowFilter | AttrCapabilities.AllowSort | AttrCapabilities.AllowView)] + public DateTimeOffset? LastModifiedAt { get; set; } } diff --git a/src/Examples/JsonApiDotNetCoreMongoDbExample/Models/TodoItemPriority.cs b/src/Examples/JsonApiDotNetCoreMongoDbExample/Models/TodoItemPriority.cs new file mode 100644 index 0000000..a782897 --- /dev/null +++ b/src/Examples/JsonApiDotNetCoreMongoDbExample/Models/TodoItemPriority.cs @@ -0,0 +1,11 @@ +using JetBrains.Annotations; + +namespace JsonApiDotNetCoreMongoDbExample.Models; + +[UsedImplicitly(ImplicitUseTargetFlags.Members)] +public enum TodoItemPriority +{ + Low, + Medium, + High +} diff --git a/src/Examples/JsonApiDotNetCoreMongoDbExample/Program.cs b/src/Examples/JsonApiDotNetCoreMongoDbExample/Program.cs index a50092c..5f4b0dc 100644 --- a/src/Examples/JsonApiDotNetCoreMongoDbExample/Program.cs +++ b/src/Examples/JsonApiDotNetCoreMongoDbExample/Program.cs @@ -1,22 +1,49 @@ -using JsonApiDotNetCoreMongoDbExample.Startups; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Hosting; +using System.Text.Json.Serialization; +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.MongoDb.Configuration; +using JsonApiDotNetCore.MongoDb.Repositories; +using JsonApiDotNetCore.Repositories; +using Microsoft.AspNetCore.Authentication; +using MongoDB.Driver; -namespace JsonApiDotNetCoreMongoDbExample +WebApplicationBuilder builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddSingleton(); + +builder.Services.AddSingleton(_ => +{ + var client = new MongoClient(builder.Configuration.GetSection("DatabaseSettings:ConnectionString").Value); + return client.GetDatabase(builder.Configuration.GetSection("DatabaseSettings:Database").Value); +}); + +builder.Services.AddJsonApi(ConfigureJsonApiOptions, 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<,>)); + +WebApplication app = builder.Build(); + +// Configure the HTTP request pipeline. + +app.UseRouting(); +app.UseJsonApi(); +app.MapControllers(); + +app.Run(); + +static void ConfigureJsonApiOptions(JsonApiOptions options) { - internal static class Program - { - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } - - private static IHostBuilder CreateHostBuilder(string[] args) - { - return Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); - } - } + options.Namespace = "api/v1"; + options.UseRelativeLinks = true; + options.IncludeTotalResourceCount = true; + options.SerializerOptions.WriteIndented = true; + options.SerializerOptions.Converters.Add(new JsonStringEnumConverter()); +#if DEBUG + options.IncludeExceptionStackTraceInErrors = true; + options.IncludeRequestBodyInErrors = true; +#endif } diff --git a/src/Examples/JsonApiDotNetCoreMongoDbExample/Startups/EmptyStartup.cs b/src/Examples/JsonApiDotNetCoreMongoDbExample/Startups/EmptyStartup.cs deleted file mode 100644 index e89762d..0000000 --- a/src/Examples/JsonApiDotNetCoreMongoDbExample/Startups/EmptyStartup.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.DependencyInjection; - -namespace JsonApiDotNetCoreMongoDbExample.Startups -{ - /// - /// Empty startup class, required for integration tests. Changes in ASP.NET Core 3 no longer allow Startup class to be defined in test projects. See - /// https://github.com/aspnet/AspNetCore/issues/15373. - /// - public abstract class EmptyStartup - { - public virtual void ConfigureServices(IServiceCollection services) - { - } - - public virtual void Configure(IApplicationBuilder app, IWebHostEnvironment environment) - { - } - } -} diff --git a/src/Examples/JsonApiDotNetCoreMongoDbExample/Startups/Startup.cs b/src/Examples/JsonApiDotNetCoreMongoDbExample/Startups/Startup.cs deleted file mode 100644 index 6902c42..0000000 --- a/src/Examples/JsonApiDotNetCoreMongoDbExample/Startups/Startup.cs +++ /dev/null @@ -1,66 +0,0 @@ -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.MongoDb.Configuration; -using JsonApiDotNetCore.MongoDb.Repositories; -using JsonApiDotNetCore.Repositories; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using MongoDB.Driver; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; - -namespace JsonApiDotNetCoreMongoDbExample.Startups -{ - public sealed class Startup : EmptyStartup - { - private readonly IConfiguration _configuration; - - public Startup(IConfiguration configuration) - { - _configuration = configuration; - } - - // This method gets called by the runtime. Use this method to add services to the container. - public override void ConfigureServices(IServiceCollection services) - { - services.AddSingleton(); - - services.AddSingleton(_ => - { - var client = new MongoClient(_configuration.GetSection("DatabaseSettings:ConnectionString").Value); - return client.GetDatabase(_configuration.GetSection("DatabaseSettings:Database").Value); - }); - - services.AddJsonApi(ConfigureJsonApiOptions, facade => facade.AddCurrentAssembly()); - services.AddJsonApiMongoDb(); - - services.AddScoped(typeof(IResourceReadRepository<>), typeof(MongoRepository<>)); - services.AddScoped(typeof(IResourceReadRepository<,>), typeof(MongoRepository<,>)); - services.AddScoped(typeof(IResourceWriteRepository<>), typeof(MongoRepository<>)); - services.AddScoped(typeof(IResourceWriteRepository<,>), typeof(MongoRepository<,>)); - services.AddScoped(typeof(IResourceRepository<>), typeof(MongoRepository<>)); - services.AddScoped(typeof(IResourceRepository<,>), typeof(MongoRepository<,>)); - } - - private void ConfigureJsonApiOptions(JsonApiOptions options) - { - options.IncludeExceptionStackTraceInErrors = true; - options.Namespace = "api/v1"; - options.DefaultPageSize = new PageSize(5); - options.IncludeTotalResourceCount = true; - options.ValidateModelState = true; - options.SerializerSettings.Formatting = Formatting.Indented; - options.SerializerSettings.Converters.Add(new StringEnumConverter()); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public override void Configure(IApplicationBuilder app, IWebHostEnvironment environment) - { - app.UseRouting(); - app.UseJsonApi(); - app.UseEndpoints(endpoints => endpoints.MapControllers()); - } - } -} diff --git a/src/Examples/JsonApiDotNetCoreMongoDbExample/appsettings.json b/src/Examples/JsonApiDotNetCoreMongoDbExample/appsettings.json index 9ce23b1..dde4b49 100644 --- a/src/Examples/JsonApiDotNetCoreMongoDbExample/appsettings.json +++ b/src/Examples/JsonApiDotNetCoreMongoDbExample/appsettings.json @@ -6,9 +6,8 @@ "Logging": { "LogLevel": { "Default": "Warning", - "Microsoft.Hosting.Lifetime": "Warning", - "Microsoft.EntityFrameworkCore.Update": "Critical", - "Microsoft.EntityFrameworkCore.Database.Command": "Critical" + "Microsoft.Hosting.Lifetime": "Information", + "Microsoft.EntityFrameworkCore": "Critical" } }, "AllowedHosts": "*" diff --git a/src/JsonApiDotNetCore.MongoDb/ArgumentGuard.cs b/src/JsonApiDotNetCore.MongoDb/ArgumentGuard.cs index 08392ec..20ca622 100644 --- a/src/JsonApiDotNetCore.MongoDb/ArgumentGuard.cs +++ b/src/JsonApiDotNetCore.MongoDb/ArgumentGuard.cs @@ -1,21 +1,19 @@ -using System; using JetBrains.Annotations; +using SysNotNull = System.Diagnostics.CodeAnalysis.NotNullAttribute; #pragma warning disable AV1008 // Class should not be static -namespace JsonApiDotNetCore.MongoDb +namespace JsonApiDotNetCore.MongoDb; + +internal static class ArgumentGuard { - internal static class ArgumentGuard + [AssertionMethod] + public static void NotNull([NoEnumeration] [SysNotNull] T? value, [InvokerParameterName] string name) + where T : class { - [AssertionMethod] - [ContractAnnotation("value: null => halt")] - public static void NotNull([CanBeNull] [NoEnumeration] T value, [NotNull] [InvokerParameterName] string name) - where T : class + if (value is null) { - if (value is null) - { - throw new ArgumentNullException(name); - } + throw new ArgumentNullException(name); } } } diff --git a/src/JsonApiDotNetCore.MongoDb/AtomicOperations/MongoTransaction.cs b/src/JsonApiDotNetCore.MongoDb/AtomicOperations/MongoTransaction.cs index 86ed9b4..d2e5a36 100644 --- a/src/JsonApiDotNetCore.MongoDb/AtomicOperations/MongoTransaction.cs +++ b/src/JsonApiDotNetCore.MongoDb/AtomicOperations/MongoTransaction.cs @@ -1,57 +1,54 @@ -using System.Threading; -using System.Threading.Tasks; using JetBrains.Annotations; using JsonApiDotNetCore.AtomicOperations; using JsonApiDotNetCore.MongoDb.Repositories; -namespace JsonApiDotNetCore.MongoDb.AtomicOperations +namespace JsonApiDotNetCore.MongoDb.AtomicOperations; + +/// +[PublicAPI] +public sealed class MongoTransaction : IOperationsTransaction { - /// - [PublicAPI] - public sealed class MongoTransaction : IOperationsTransaction - { - private readonly IMongoDataAccess _mongoDataAccess; - private readonly bool _ownsTransaction; + private readonly IMongoDataAccess _mongoDataAccess; + private readonly bool _ownsTransaction; - /// - public string TransactionId => _mongoDataAccess.TransactionId; + /// + public string TransactionId => _mongoDataAccess.TransactionId!; - public MongoTransaction(IMongoDataAccess mongoDataAccess, bool ownsTransaction) - { - ArgumentGuard.NotNull(mongoDataAccess, nameof(mongoDataAccess)); + public MongoTransaction(IMongoDataAccess mongoDataAccess, bool ownsTransaction) + { + ArgumentGuard.NotNull(mongoDataAccess, nameof(mongoDataAccess)); - _mongoDataAccess = mongoDataAccess; - _ownsTransaction = ownsTransaction; - } + _mongoDataAccess = mongoDataAccess; + _ownsTransaction = ownsTransaction; + } - /// - public Task BeforeProcessOperationAsync(CancellationToken cancellationToken) - { - return Task.CompletedTask; - } + /// + public Task BeforeProcessOperationAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } - /// - public Task AfterProcessOperationAsync(CancellationToken cancellationToken) - { - return Task.CompletedTask; - } + /// + public Task AfterProcessOperationAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } - /// - public async Task CommitAsync(CancellationToken cancellationToken) + /// + public async Task CommitAsync(CancellationToken cancellationToken) + { + if (_ownsTransaction && _mongoDataAccess.ActiveSession != null) { - if (_ownsTransaction) - { - await _mongoDataAccess.ActiveSession.CommitTransactionAsync(cancellationToken); - } + await _mongoDataAccess.ActiveSession.CommitTransactionAsync(cancellationToken); } + } - /// - public async ValueTask DisposeAsync() + /// + public async ValueTask DisposeAsync() + { + if (_ownsTransaction) { - if (_ownsTransaction) - { - await _mongoDataAccess.DisposeAsync(); - } + await _mongoDataAccess.DisposeAsync(); } } } diff --git a/src/JsonApiDotNetCore.MongoDb/AtomicOperations/MongoTransactionFactory.cs b/src/JsonApiDotNetCore.MongoDb/AtomicOperations/MongoTransactionFactory.cs index 7a4c198..ba3f9a9 100644 --- a/src/JsonApiDotNetCore.MongoDb/AtomicOperations/MongoTransactionFactory.cs +++ b/src/JsonApiDotNetCore.MongoDb/AtomicOperations/MongoTransactionFactory.cs @@ -1,42 +1,39 @@ -using System.Threading; -using System.Threading.Tasks; using JsonApiDotNetCore.AtomicOperations; using JsonApiDotNetCore.MongoDb.Repositories; -namespace JsonApiDotNetCore.MongoDb.AtomicOperations +namespace JsonApiDotNetCore.MongoDb.AtomicOperations; + +/// +/// Provides transaction support for atomic:operation requests using MongoDB. +/// +public sealed class MongoTransactionFactory : IOperationsTransactionFactory { - /// - /// Provides transaction support for atomic:operation requests using MongoDB. - /// - public sealed class MongoTransactionFactory : IOperationsTransactionFactory + private readonly IMongoDataAccess _mongoDataAccess; + + public MongoTransactionFactory(IMongoDataAccess mongoDataAccess) { - private readonly IMongoDataAccess _mongoDataAccess; + ArgumentGuard.NotNull(mongoDataAccess, nameof(mongoDataAccess)); - public MongoTransactionFactory(IMongoDataAccess mongoDataAccess) - { - ArgumentGuard.NotNull(mongoDataAccess, nameof(mongoDataAccess)); + _mongoDataAccess = mongoDataAccess; + } - _mongoDataAccess = mongoDataAccess; - } + /// + public async Task BeginTransactionAsync(CancellationToken cancellationToken) + { + bool transactionCreated = await CreateOrJoinTransactionAsync(cancellationToken); + return new MongoTransaction(_mongoDataAccess, transactionCreated); + } - /// - public async Task BeginTransactionAsync(CancellationToken cancellationToken) - { - bool transactionCreated = await CreateOrJoinTransactionAsync(cancellationToken); - return new MongoTransaction(_mongoDataAccess, transactionCreated); - } + private async Task CreateOrJoinTransactionAsync(CancellationToken cancellationToken) + { + _mongoDataAccess.ActiveSession ??= await _mongoDataAccess.MongoDatabase.Client.StartSessionAsync(cancellationToken: cancellationToken); - private async Task CreateOrJoinTransactionAsync(CancellationToken cancellationToken) + if (_mongoDataAccess.ActiveSession.IsInTransaction) { - _mongoDataAccess.ActiveSession ??= await _mongoDataAccess.MongoDatabase.Client.StartSessionAsync(cancellationToken: cancellationToken); - - if (_mongoDataAccess.ActiveSession.IsInTransaction) - { - return false; - } - - _mongoDataAccess.ActiveSession.StartTransaction(); - return true; + return false; } + + _mongoDataAccess.ActiveSession.StartTransaction(); + return true; } } diff --git a/src/JsonApiDotNetCore.MongoDb/Configuration/ServiceCollectionExtensions.cs b/src/JsonApiDotNetCore.MongoDb/Configuration/ServiceCollectionExtensions.cs index 62474fa..f914f91 100644 --- a/src/JsonApiDotNetCore.MongoDb/Configuration/ServiceCollectionExtensions.cs +++ b/src/JsonApiDotNetCore.MongoDb/Configuration/ServiceCollectionExtensions.cs @@ -1,26 +1,25 @@ using JetBrains.Annotations; using JsonApiDotNetCore.AtomicOperations; using JsonApiDotNetCore.MongoDb.AtomicOperations; +using JsonApiDotNetCore.MongoDb.Queries.Internal; using JsonApiDotNetCore.MongoDb.Repositories; -using JsonApiDotNetCore.MongoDb.Serialization.Building; -using JsonApiDotNetCore.Serialization.Building; +using JsonApiDotNetCore.Queries.Internal; using Microsoft.Extensions.DependencyInjection; -namespace JsonApiDotNetCore.MongoDb.Configuration +namespace JsonApiDotNetCore.MongoDb.Configuration; + +public static class ServiceCollectionExtensions { - public static class ServiceCollectionExtensions + /// + /// Expands JsonApiDotNetCore configuration for usage with MongoDB. + /// + [PublicAPI] + public static IServiceCollection AddJsonApiMongoDb(this IServiceCollection services) { - /// - /// Expands JsonApiDotNetCore configuration for usage with MongoDB. - /// - [PublicAPI] - public static IServiceCollection AddJsonApiMongoDb(this IServiceCollection services) - { - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); - return services; - } + return services; } } diff --git a/src/JsonApiDotNetCore.MongoDb/Errors/AttributeComparisonInFilterNotSupportedException.cs b/src/JsonApiDotNetCore.MongoDb/Errors/AttributeComparisonInFilterNotSupportedException.cs index f6f85c7..4b7508b 100644 --- a/src/JsonApiDotNetCore.MongoDb/Errors/AttributeComparisonInFilterNotSupportedException.cs +++ b/src/JsonApiDotNetCore.MongoDb/Errors/AttributeComparisonInFilterNotSupportedException.cs @@ -3,21 +3,20 @@ using JsonApiDotNetCore.Errors; using JsonApiDotNetCore.Serialization.Objects; -namespace JsonApiDotNetCore.MongoDb.Errors +namespace JsonApiDotNetCore.MongoDb.Errors; + +/// +/// The error that is thrown when a filter compares two attributes. This is not supported by MongoDB.Driver. See +/// https://jira.mongodb.org/browse/CSHARP-1592. +/// +[PublicAPI] +public sealed class AttributeComparisonInFilterNotSupportedException : JsonApiException { - /// - /// The error that is thrown when a filter compares two attributes. This is not supported by MongoDB.Driver. See - /// https://jira.mongodb.org/browse/CSHARP-1592. - /// - [PublicAPI] - public sealed class AttributeComparisonInFilterNotSupportedException : JsonApiException - { - public AttributeComparisonInFilterNotSupportedException() - : base(new Error(HttpStatusCode.BadRequest) - { - Title = "Comparing attributes against each other is not supported when using MongoDB." - }) + public AttributeComparisonInFilterNotSupportedException() + : base(new 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 63c8f0f..2526e59 100644 --- a/src/JsonApiDotNetCore.MongoDb/Errors/UnsupportedRelationshipException.cs +++ b/src/JsonApiDotNetCore.MongoDb/Errors/UnsupportedRelationshipException.cs @@ -3,20 +3,19 @@ using JsonApiDotNetCore.Errors; using JsonApiDotNetCore.Serialization.Objects; -namespace JsonApiDotNetCore.MongoDb.Errors +namespace JsonApiDotNetCore.MongoDb.Errors; + +/// +/// The error that is thrown when the user attempts to fetch, create or update a relationship. +/// +[PublicAPI] +public sealed class UnsupportedRelationshipException : JsonApiException { - /// - /// The error that is thrown when the user attempts to fetch, create or update a relationship. - /// - [PublicAPI] - public sealed class UnsupportedRelationshipException : JsonApiException - { - public UnsupportedRelationshipException() - : base(new Error(HttpStatusCode.BadRequest) - { - Title = "Relationships are not supported when using MongoDB." - }) + public UnsupportedRelationshipException() + : base(new 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 33134cf..2cc225f 100644 --- a/src/JsonApiDotNetCore.MongoDb/JsonApiDotNetCore.MongoDb.csproj +++ b/src/JsonApiDotNetCore.MongoDb/JsonApiDotNetCore.MongoDb.csproj @@ -1,27 +1,39 @@ - 4.2.0 - $(NetCoreAppVersion) + $(TargetFrameworkName) + true true - jsonapi;json:api;dotnet;core;MongoDB + $(JsonApiDotNetCoreMongoDbVersionPrefix) + jsonapi;json:api;dotnet;asp.net;rest;web-api;MongoDB Persistence layer implementation for use of MongoDB in APIs using JsonApiDotNetCore. + json-api-dotnet https://github.com/json-api-dotnet/JsonApiDotNetCore.MongoDb MIT false + See https://github.com/json-api-dotnet/JsonApiDotNetCore.MongoDb/releases. + logo.png true true embedded + + + True + + + + + diff --git a/src/JsonApiDotNetCore.MongoDb/Queries/Internal/HideRelationshipsSparseFieldSetCache.cs b/src/JsonApiDotNetCore.MongoDb/Queries/Internal/HideRelationshipsSparseFieldSetCache.cs new file mode 100644 index 0000000..414b5f7 --- /dev/null +++ b/src/JsonApiDotNetCore.MongoDb/Queries/Internal/HideRelationshipsSparseFieldSetCache.cs @@ -0,0 +1,50 @@ +using System.Collections.Immutable; +using JsonApiDotNetCore.Configuration; +using JsonApiDotNetCore.Queries; +using JsonApiDotNetCore.Queries.Internal; +using JsonApiDotNetCore.Resources; +using JsonApiDotNetCore.Resources.Annotations; + +namespace JsonApiDotNetCore.MongoDb.Queries.Internal; + +/// +public sealed class HideRelationshipsSparseFieldSetCache : ISparseFieldSetCache +{ + private readonly SparseFieldSetCache _innerCache; + + public HideRelationshipsSparseFieldSetCache(IEnumerable constraintProviders, + IResourceDefinitionAccessor resourceDefinitionAccessor) + { + ArgumentGuard.NotNull(constraintProviders, nameof(constraintProviders)); + ArgumentGuard.NotNull(resourceDefinitionAccessor, nameof(resourceDefinitionAccessor)); + + _innerCache = new SparseFieldSetCache(constraintProviders, resourceDefinitionAccessor); + } + + /// + public IImmutableSet GetSparseFieldSetForQuery(ResourceType resourceType) + { + return _innerCache.GetSparseFieldSetForQuery(resourceType); + } + + /// + public IImmutableSet GetIdAttributeSetForRelationshipQuery(ResourceType resourceType) + { + return _innerCache.GetIdAttributeSetForRelationshipQuery(resourceType); + } + + /// + public IImmutableSet GetSparseFieldSetForSerializer(ResourceType resourceType) + { + IImmutableSet fieldSet = _innerCache.GetSparseFieldSetForSerializer(resourceType); + + ResourceFieldAttribute[] relationships = fieldSet.Where(field => field is RelationshipAttribute).ToArray(); + return fieldSet.Except(relationships); + } + + /// + public void Reset() + { + _innerCache.Reset(); + } +} diff --git a/src/JsonApiDotNetCore.MongoDb/Queries/Internal/QueryableBuilding/MongoQueryableBuilder.cs b/src/JsonApiDotNetCore.MongoDb/Queries/Internal/QueryableBuilding/MongoQueryableBuilder.cs index 6660e26..87f4715 100644 --- a/src/JsonApiDotNetCore.MongoDb/Queries/Internal/QueryableBuilding/MongoQueryableBuilder.cs +++ b/src/JsonApiDotNetCore.MongoDb/Queries/Internal/QueryableBuilding/MongoQueryableBuilder.cs @@ -1,43 +1,39 @@ -using System; using System.Linq.Expressions; using JetBrains.Annotations; -using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Queries; using JsonApiDotNetCore.Queries.Expressions; using JsonApiDotNetCore.Queries.Internal.QueryableBuilding; using JsonApiDotNetCore.Resources; using Microsoft.EntityFrameworkCore.Metadata; -namespace JsonApiDotNetCore.MongoDb.Queries.Internal.QueryableBuilding +namespace JsonApiDotNetCore.MongoDb.Queries.Internal.QueryableBuilding; + +/// +/// Drives conversion from into system trees. +/// +[PublicAPI] +public sealed class MongoQueryableBuilder : QueryableBuilder { - /// - /// Drives conversion from into system trees. - /// - [PublicAPI] - public sealed class MongoQueryableBuilder : QueryableBuilder - { - private readonly Type _elementType; - private readonly Type _extensionType; - private readonly LambdaParameterNameFactory _nameFactory; - private readonly LambdaScopeFactory _lambdaScopeFactory; + private readonly Type _elementType; + private readonly Type _extensionType; + private readonly LambdaParameterNameFactory _nameFactory; + private readonly LambdaScopeFactory _lambdaScopeFactory; - public MongoQueryableBuilder(Expression source, Type elementType, Type extensionType, LambdaParameterNameFactory nameFactory, - IResourceFactory resourceFactory, IResourceContextProvider resourceContextProvider, IModel entityModel, - LambdaScopeFactory lambdaScopeFactory = null) - : base(source, elementType, extensionType, nameFactory, resourceFactory, resourceContextProvider, entityModel, lambdaScopeFactory) - { - _elementType = elementType; - _extensionType = extensionType; - _nameFactory = nameFactory; - _lambdaScopeFactory = lambdaScopeFactory ?? new LambdaScopeFactory(nameFactory); - } + public MongoQueryableBuilder(Expression source, Type elementType, Type extensionType, LambdaParameterNameFactory nameFactory, + IResourceFactory resourceFactory, IModel entityModel, LambdaScopeFactory? lambdaScopeFactory = null) + : base(source, elementType, extensionType, nameFactory, resourceFactory, entityModel, lambdaScopeFactory) + { + _elementType = elementType; + _extensionType = extensionType; + _nameFactory = nameFactory; + _lambdaScopeFactory = lambdaScopeFactory ?? new LambdaScopeFactory(nameFactory); + } - protected override Expression ApplyFilter(Expression source, FilterExpression filter) - { - using LambdaScope lambdaScope = _lambdaScopeFactory.CreateScope(_elementType); + protected override Expression ApplyFilter(Expression source, FilterExpression filter) + { + using LambdaScope lambdaScope = _lambdaScopeFactory.CreateScope(_elementType); - var builder = new MongoWhereClauseBuilder(source, lambdaScope, _extensionType, _nameFactory); - return builder.ApplyWhere(filter); - } + var builder = new MongoWhereClauseBuilder(source, lambdaScope, _extensionType, _nameFactory); + return builder.ApplyWhere(filter); } } diff --git a/src/JsonApiDotNetCore.MongoDb/Queries/Internal/QueryableBuilding/MongoWhereClauseBuilder.cs b/src/JsonApiDotNetCore.MongoDb/Queries/Internal/QueryableBuilding/MongoWhereClauseBuilder.cs index da560be..fef7758 100644 --- a/src/JsonApiDotNetCore.MongoDb/Queries/Internal/QueryableBuilding/MongoWhereClauseBuilder.cs +++ b/src/JsonApiDotNetCore.MongoDb/Queries/Internal/QueryableBuilding/MongoWhereClauseBuilder.cs @@ -1,48 +1,46 @@ -using System; using System.Linq.Expressions; using JetBrains.Annotations; using JsonApiDotNetCore.Queries.Expressions; using JsonApiDotNetCore.Queries.Internal.QueryableBuilding; -namespace JsonApiDotNetCore.MongoDb.Queries.Internal.QueryableBuilding +namespace JsonApiDotNetCore.MongoDb.Queries.Internal.QueryableBuilding; + +/// +[PublicAPI] +public class MongoWhereClauseBuilder : WhereClauseBuilder { - /// - [PublicAPI] - public class MongoWhereClauseBuilder : WhereClauseBuilder + public MongoWhereClauseBuilder(Expression source, LambdaScope lambdaScope, Type extensionType, LambdaParameterNameFactory nameFactory) + : base(source, lambdaScope, extensionType, nameFactory) + { + } + + public override Expression VisitLiteralConstant(LiteralConstantExpression expression, Type? expressionType) { - public MongoWhereClauseBuilder(Expression source, LambdaScope lambdaScope, Type extensionType, LambdaParameterNameFactory nameFactory) - : base(source, lambdaScope, extensionType, nameFactory) + if (expressionType == typeof(DateTime) || expressionType == typeof(DateTime?)) { + DateTime? dateTime = TryParseDateTimeAsUtc(expression.Value, expressionType); + return Expression.Constant(dateTime); } - public override Expression VisitLiteralConstant(LiteralConstantExpression expression, Type expressionType) - { - if (expressionType == typeof(DateTime) || expressionType == typeof(DateTime?)) - { - DateTime? dateTime = TryParseDateTimeAsUtc(expression.Value, expressionType); - return Expression.Constant(dateTime); - } + return base.VisitLiteralConstant(expression, expressionType); + } - return base.VisitLiteralConstant(expression, expressionType); - } + private static DateTime? TryParseDateTimeAsUtc(string value, Type expressionType) + { + object convertedValue = Convert.ChangeType(value, expressionType); - private static DateTime? TryParseDateTimeAsUtc(string value, Type expressionType) + if (convertedValue is DateTime dateTime) { - object convertedValue = Convert.ChangeType(value, expressionType); - - if (convertedValue is DateTime dateTime) + // DateTime values in MongoDB are always stored in UTC, so any ambiguous filter value passed + // must be interpreted as such for correct comparison. + if (dateTime.Kind == DateTimeKind.Unspecified) { - // DateTime values in MongoDB are always stored in UTC, so any ambiguous filter value passed - // must be interpreted as such for correct comparison. - if (dateTime.Kind == DateTimeKind.Unspecified) - { - return DateTime.SpecifyKind(dateTime, DateTimeKind.Utc); - } - - return dateTime; + return DateTime.SpecifyKind(dateTime, DateTimeKind.Utc); } - return null; + return dateTime; } + + return null; } } diff --git a/src/JsonApiDotNetCore.MongoDb/Repositories/IMongoDataAccess.cs b/src/JsonApiDotNetCore.MongoDb/Repositories/IMongoDataAccess.cs index fe160fa..ff2d348 100644 --- a/src/JsonApiDotNetCore.MongoDb/Repositories/IMongoDataAccess.cs +++ b/src/JsonApiDotNetCore.MongoDb/Repositories/IMongoDataAccess.cs @@ -1,26 +1,24 @@ -using System; using MongoDB.Driver; -namespace JsonApiDotNetCore.MongoDb.Repositories +namespace JsonApiDotNetCore.MongoDb.Repositories; + +/// +/// Provides access to the MongoDB Driver and the optionally active session. +/// +public interface IMongoDataAccess : IAsyncDisposable { /// - /// Provides access to the MongoDB Driver and the optionally active session. + /// Provides access to the underlying MongoDB database, which data changes can be applied on. /// - public interface IMongoDataAccess : IAsyncDisposable - { - /// - /// Provides access to the underlying MongoDB database, which data changes can be applied on. - /// - IMongoDatabase MongoDatabase { get; } + IMongoDatabase MongoDatabase { get; } - /// - /// Provides access to the active session, if any. - /// - IClientSessionHandle ActiveSession { get; set; } + /// + /// Provides access to the active session, if any. + /// + IClientSessionHandle? ActiveSession { get; set; } - /// - /// Identifies the current transaction, if any. - /// - string TransactionId { get; } - } + /// + /// Identifies the current transaction, if any. + /// + string? TransactionId { get; } } diff --git a/src/JsonApiDotNetCore.MongoDb/Repositories/MongoDataAccess.cs b/src/JsonApiDotNetCore.MongoDb/Repositories/MongoDataAccess.cs index 69ccf70..6c449b6 100644 --- a/src/JsonApiDotNetCore.MongoDb/Repositories/MongoDataAccess.cs +++ b/src/JsonApiDotNetCore.MongoDb/Repositories/MongoDataAccess.cs @@ -1,40 +1,38 @@ -using System.Threading.Tasks; using MongoDB.Driver; -namespace JsonApiDotNetCore.MongoDb.Repositories +namespace JsonApiDotNetCore.MongoDb.Repositories; + +/// +public sealed class MongoDataAccess : IMongoDataAccess { /// - public sealed class MongoDataAccess : IMongoDataAccess - { - /// - public IMongoDatabase MongoDatabase { get; } + public IMongoDatabase MongoDatabase { get; } - /// - public IClientSessionHandle ActiveSession { get; set; } + /// + public IClientSessionHandle? ActiveSession { get; set; } - /// - public string TransactionId => ActiveSession != null && ActiveSession.IsInTransaction ? ActiveSession.GetHashCode().ToString() : null; + /// + public string? TransactionId => ActiveSession is { IsInTransaction: true } ? ActiveSession.GetHashCode().ToString() : null; - public MongoDataAccess(IMongoDatabase mongoDatabase) - { - ArgumentGuard.NotNull(mongoDatabase, nameof(mongoDatabase)); + public MongoDataAccess(IMongoDatabase mongoDatabase) + { + ArgumentGuard.NotNull(mongoDatabase, nameof(mongoDatabase)); - MongoDatabase = mongoDatabase; - } + MongoDatabase = mongoDatabase; + } - /// - public async ValueTask DisposeAsync() + /// + public async ValueTask DisposeAsync() + { + if (ActiveSession != null) { - if (ActiveSession != null) + if (ActiveSession.IsInTransaction) { - if (ActiveSession.IsInTransaction) - { - await ActiveSession.AbortTransactionAsync(); - } - - ActiveSession.Dispose(); - ActiveSession = null; + await ActiveSession.AbortTransactionAsync(); } + + ActiveSession.Dispose(); + ActiveSession = null; } } } diff --git a/src/JsonApiDotNetCore.MongoDb/Repositories/MongoEntityType.cs b/src/JsonApiDotNetCore.MongoDb/Repositories/MongoEntityType.cs index b30e221..639b123 100644 --- a/src/JsonApiDotNetCore.MongoDb/Repositories/MongoEntityType.cs +++ b/src/JsonApiDotNetCore.MongoDb/Repositories/MongoEntityType.cs @@ -1,97 +1,456 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using System.Linq.Expressions; +using System.Reflection; using JsonApiDotNetCore.Configuration; +using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; -namespace JsonApiDotNetCore.MongoDb.Repositories +namespace JsonApiDotNetCore.MongoDb.Repositories; + +internal sealed class MongoEntityType : IEntityType { - internal sealed class MongoEntityType : IEntityType - { - private readonly ResourceContext _resourceContext; - - public IModel Model { get; } - public Type ClrType => _resourceContext.ResourceType; - - public string Name => throw new NotImplementedException(); - public IEntityType BaseType => throw new NotImplementedException(); - public string DefiningNavigationName => throw new NotImplementedException(); - public IEntityType DefiningEntityType => throw new NotImplementedException(); - public object this[string name] => throw new NotImplementedException(); - - public MongoEntityType(ResourceContext resourceContext, MongoModel owner) - { - ArgumentGuard.NotNull(resourceContext, nameof(resourceContext)); - ArgumentGuard.NotNull(owner, nameof(owner)); - - _resourceContext = resourceContext; - Model = owner; - } - - public IEnumerable GetProperties() - { - return _resourceContext.Attributes.Select(attr => new MongoProperty(attr.Property, this)).ToArray(); - } - - public IAnnotation FindAnnotation(string name) - { - throw new NotImplementedException(); - } - - public IEnumerable GetAnnotations() - { - throw new NotImplementedException(); - } - - public IKey FindPrimaryKey() - { - throw new NotImplementedException(); - } - - public IKey FindKey(IReadOnlyList properties) - { - throw new NotImplementedException(); - } - - public IEnumerable GetKeys() - { - throw new NotImplementedException(); - } - - public IForeignKey FindForeignKey(IReadOnlyList properties, IKey principalKey, IEntityType principalEntityType) - { - throw new NotImplementedException(); - } - - public IEnumerable GetForeignKeys() - { - throw new NotImplementedException(); - } - - public IIndex FindIndex(IReadOnlyList properties) - { - throw new NotImplementedException(); - } - - public IEnumerable GetIndexes() - { - throw new NotImplementedException(); - } - - public IProperty FindProperty(string name) - { - throw new NotImplementedException(); - } - - public IServiceProperty FindServiceProperty(string name) - { - throw new NotImplementedException(); - } - - public IEnumerable GetServiceProperties() - { - throw new NotImplementedException(); - } + private readonly ResourceType _resourceType; + + IReadOnlyModel IReadOnlyTypeBase.Model => Model; + IReadOnlyEntityType IReadOnlyEntityType.BaseType => BaseType; + + public IModel Model { get; } + public Type ClrType => _resourceType.ClrType; + + public string Name => throw new NotImplementedException(); + public bool HasSharedClrType => throw new NotImplementedException(); + public bool IsPropertyBag => throw new NotImplementedException(); + public IEntityType BaseType => throw new NotImplementedException(); + public InstantiationBinding ConstructorBinding => throw new NotImplementedException(); + public object this[string name] => throw new NotImplementedException(); + + public MongoEntityType(ResourceType resourceType, MongoModel owner) + { + ArgumentGuard.NotNull(resourceType, nameof(resourceType)); + ArgumentGuard.NotNull(owner, nameof(owner)); + + _resourceType = resourceType; + Model = owner; + } + + public IEnumerable GetProperties() + { + return _resourceType.Attributes.Select(attr => new MongoProperty(attr.Property, this)).ToArray(); + } + + public IAnnotation FindAnnotation(string name) + { + throw new NotImplementedException(); + } + + public IEnumerable GetAnnotations() + { + throw new NotImplementedException(); + } + + public PropertyAccessMode GetPropertyAccessMode() + { + throw new NotImplementedException(); + } + + public PropertyAccessMode GetNavigationAccessMode() + { + throw new NotImplementedException(); + } + + public PropertyInfo FindIndexerPropertyInfo() + { + throw new NotImplementedException(); + } + + public ChangeTrackingStrategy GetChangeTrackingStrategy() + { + throw new NotImplementedException(); + } + + public IEnumerable> GetSeedData(bool providerValues = false) + { + throw new NotImplementedException(); + } + + public LambdaExpression GetQueryFilter() + { + throw new NotImplementedException(); + } + + public string GetDiscriminatorPropertyName() + { + throw new NotImplementedException(); + } + + public IEnumerable GetDerivedTypes() + { + throw new NotImplementedException(); + } + + public IEnumerable GetDirectlyDerivedTypes() + { + throw new NotImplementedException(); + } + + IEnumerable IReadOnlyEntityType.GetDirectlyDerivedTypes() + { + return GetDirectlyDerivedTypes(); + } + + IReadOnlyKey IReadOnlyEntityType.FindPrimaryKey() + { + return FindPrimaryKey(); + } + + public IKey FindKey(IReadOnlyList properties) + { + throw new NotImplementedException(); + } + + public IEnumerable GetDeclaredKeys() + { + throw new NotImplementedException(); + } + + public IForeignKey FindForeignKey(IReadOnlyList properties, IReadOnlyKey principalKey, IReadOnlyEntityType principalEntityType) + { + throw new NotImplementedException(); + } + + public IEnumerable FindForeignKeys(IReadOnlyList properties) + { + throw new NotImplementedException(); + } + + public IEnumerable GetKeys() + { + throw new NotImplementedException(); + } + + public IKey FindPrimaryKey() + { + throw new NotImplementedException(); + } + + IReadOnlyKey IReadOnlyEntityType.FindKey(IReadOnlyList properties) + { + return FindKey(properties); + } + + IEnumerable IReadOnlyEntityType.GetKeys() + { + return GetKeys(); + } + + IEnumerable IReadOnlyEntityType.GetDeclaredKeys() + { + return GetDeclaredKeys(); + } + + IReadOnlyForeignKey IReadOnlyEntityType.FindForeignKey(IReadOnlyList properties, IReadOnlyKey principalKey, + IReadOnlyEntityType principalEntityType) + { + return FindForeignKey(properties, principalKey, principalEntityType); + } + + IEnumerable IReadOnlyEntityType.FindForeignKeys(IReadOnlyList properties) + { + return FindForeignKeys(properties); + } + + IEnumerable IReadOnlyEntityType.FindDeclaredForeignKeys(IReadOnlyList properties) + { + return FindDeclaredForeignKeys(properties); + } + + public IEnumerable GetDeclaredForeignKeys() + { + throw new NotImplementedException(); + } + + public IEnumerable GetDerivedForeignKeys() + { + throw new NotImplementedException(); + } + + public IEnumerable GetForeignKeys() + { + throw new NotImplementedException(); + } + + public IEnumerable GetReferencingForeignKeys() + { + throw new NotImplementedException(); + } + + public IEnumerable GetDeclaredReferencingForeignKeys() + { + throw new NotImplementedException(); + } + + public IEnumerable FindDeclaredForeignKeys(IReadOnlyList properties) + { + throw new NotImplementedException(); + } + + IEnumerable IReadOnlyEntityType.GetDeclaredForeignKeys() + { + return GetDeclaredForeignKeys(); + } + + IEnumerable IReadOnlyEntityType.GetDerivedForeignKeys() + { + return GetDerivedForeignKeys(); + } + + IEnumerable IReadOnlyEntityType.GetForeignKeys() + { + return GetForeignKeys(); + } + + IEnumerable IReadOnlyEntityType.GetReferencingForeignKeys() + { + return GetReferencingForeignKeys(); + } + + IEnumerable IReadOnlyEntityType.GetDeclaredReferencingForeignKeys() + { + return GetDeclaredReferencingForeignKeys(); + } + + IReadOnlyNavigation IReadOnlyEntityType.FindDeclaredNavigation(string name) + { + return FindDeclaredNavigation(name); + } + + public IEnumerable GetDeclaredNavigations() + { + throw new NotImplementedException(); + } + + public INavigation FindDeclaredNavigation(string name) + { + throw new NotImplementedException(); + } + + IEnumerable IReadOnlyEntityType.GetDeclaredNavigations() + { + return GetDeclaredNavigations(); + } + + public IEnumerable GetDerivedNavigations() + { + throw new NotImplementedException(); + } + + public IEnumerable GetNavigations() + { + throw new NotImplementedException(); + } + + public ISkipNavigation FindSkipNavigation(string name) + { + throw new NotImplementedException(); + } + + IEnumerable IReadOnlyEntityType.GetNavigations() + { + return GetNavigations(); + } + + IReadOnlySkipNavigation IReadOnlyEntityType.FindSkipNavigation(string name) + { + return FindSkipNavigation(name); + } + + public IEnumerable GetDeclaredSkipNavigations() + { + throw new NotImplementedException(); + } + + public IEnumerable GetDerivedSkipNavigations() + { + throw new NotImplementedException(); + } + + public IEnumerable GetSkipNavigations() + { + throw new NotImplementedException(); + } + + public IIndex FindIndex(IReadOnlyList properties) + { + throw new NotImplementedException(); + } + + public IIndex FindIndex(string name) + { + throw new NotImplementedException(); + } + + IEnumerable IReadOnlyEntityType.GetSkipNavigations() + { + return GetSkipNavigations(); + } + + IReadOnlyIndex IReadOnlyEntityType.FindIndex(IReadOnlyList properties) + { + return FindIndex(properties); + } + + IReadOnlyIndex IReadOnlyEntityType.FindIndex(string name) + { + return FindIndex(name); + } + + IEnumerable IReadOnlyEntityType.GetDeclaredIndexes() + { + return GetDeclaredIndexes(); + } + + public IEnumerable GetDerivedIndexes() + { + throw new NotImplementedException(); + } + + public IEnumerable GetIndexes() + { + throw new NotImplementedException(); + } + + public IProperty FindProperty(string name) + { + throw new NotImplementedException(); + } + + public IEnumerable GetDeclaredIndexes() + { + throw new NotImplementedException(); + } + + IEnumerable IReadOnlyEntityType.GetDerivedIndexes() + { + return GetDerivedIndexes(); + } + + IEnumerable IReadOnlyEntityType.GetIndexes() + { + return GetIndexes(); + } + + IReadOnlyProperty IReadOnlyEntityType.FindProperty(string name) + { + return FindProperty(name); + } + + public IReadOnlyList FindProperties(IReadOnlyList propertyNames) + { + throw new NotImplementedException(); + } + + public IProperty FindDeclaredProperty(string name) + { + throw new NotImplementedException(); + } + + public IEnumerable GetDeclaredProperties() + { + throw new NotImplementedException(); + } + + IReadOnlyProperty IReadOnlyEntityType.FindDeclaredProperty(string name) + { + return FindDeclaredProperty(name); + } + + IEnumerable IReadOnlyEntityType.GetDeclaredProperties() + { + return GetDeclaredProperties(); + } + + public IEnumerable GetDerivedProperties() + { + throw new NotImplementedException(); + } + + public IServiceProperty FindServiceProperty(string name) + { + throw new NotImplementedException(); + } + + public IEnumerable GetDeclaredServiceProperties() + { + throw new NotImplementedException(); + } + + public IEnumerable GetForeignKeyProperties() + { + throw new NotImplementedException(); + } + + public IEnumerable GetValueGeneratingProperties() + { + throw new NotImplementedException(); + } + + IEnumerable IReadOnlyEntityType.GetProperties() + { + return GetProperties(); + } + + IReadOnlyServiceProperty IReadOnlyEntityType.FindServiceProperty(string name) + { + return FindServiceProperty(name); + } + + IEnumerable IReadOnlyEntityType.GetDeclaredServiceProperties() + { + return GetDeclaredServiceProperties(); + } + + public IEnumerable GetDerivedServiceProperties() + { + throw new NotImplementedException(); + } + + public IEnumerable GetServiceProperties() + { + throw new NotImplementedException(); + } + + IEnumerable IReadOnlyEntityType.GetServiceProperties() + { + return GetServiceProperties(); + } + + public IAnnotation FindRuntimeAnnotation(string name) + { + throw new NotImplementedException(); + } + + public IEnumerable GetRuntimeAnnotations() + { + throw new NotImplementedException(); + } + + public IAnnotation AddRuntimeAnnotation(string name, object? value) + { + throw new NotImplementedException(); + } + + public IAnnotation SetRuntimeAnnotation(string name, object? value) + { + throw new NotImplementedException(); + } + + public IAnnotation RemoveRuntimeAnnotation(string name) + { + throw new NotImplementedException(); + } + + public TValue GetOrAddRuntimeAnnotationValue(string name, Func valueFactory, TArg? factoryArgument) + { + throw new NotImplementedException(); } } diff --git a/src/JsonApiDotNetCore.MongoDb/Repositories/MongoModel.cs b/src/JsonApiDotNetCore.MongoDb/Repositories/MongoModel.cs index 7271798..1aac207 100644 --- a/src/JsonApiDotNetCore.MongoDb/Repositories/MongoModel.cs +++ b/src/JsonApiDotNetCore.MongoDb/Repositories/MongoModel.cs @@ -1,49 +1,147 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using System.Reflection; using JsonApiDotNetCore.Configuration; +using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; -namespace JsonApiDotNetCore.MongoDb.Repositories +namespace JsonApiDotNetCore.MongoDb.Repositories; + +internal sealed class MongoModel : IModel { - internal sealed class MongoModel : IModel + private readonly IResourceGraph _resourceGraph; + + public object this[string name] => throw new NotImplementedException(); + + public MongoModel(IResourceGraph resourceGraph) + { + ArgumentGuard.NotNull(resourceGraph, nameof(resourceGraph)); + + _resourceGraph = resourceGraph; + } + + public IEnumerable GetEntityTypes() + { + IReadOnlySet resourceTypes = _resourceGraph.GetResourceTypes(); + return resourceTypes.Select(resourceType => new MongoEntityType(resourceType, this)).ToArray(); + } + + public IAnnotation FindAnnotation(string name) + { + throw new NotImplementedException(); + } + + public IEnumerable GetAnnotations() + { + throw new NotImplementedException(); + } + + public ChangeTrackingStrategy GetChangeTrackingStrategy() + { + throw new NotImplementedException(); + } + + public PropertyAccessMode GetPropertyAccessMode() { - private readonly IResourceContextProvider _resourceContextProvider; + throw new NotImplementedException(); + } - public object this[string name] => throw new NotImplementedException(); + public bool IsShared(Type type) + { + throw new NotImplementedException(); + } - public MongoModel(IResourceContextProvider resourceContextProvider) - { - ArgumentGuard.NotNull(resourceContextProvider, nameof(resourceContextProvider)); + IEnumerable IReadOnlyModel.GetEntityTypes() + { + return GetEntityTypes(); + } - _resourceContextProvider = resourceContextProvider; - } + public IEntityType FindEntityType(string name) + { + throw new NotImplementedException(); + } - public IEnumerable GetEntityTypes() - { - IReadOnlyCollection resourceContexts = _resourceContextProvider.GetResourceContexts(); - return resourceContexts.Select(resourceContext => new MongoEntityType(resourceContext, this)).ToArray(); - } + public IEntityType FindEntityType(string name, string definingNavigationName, IEntityType definingEntityType) + { + throw new NotImplementedException(); + } - public IAnnotation FindAnnotation(string name) - { - throw new NotImplementedException(); - } + IReadOnlyEntityType IReadOnlyModel.FindEntityType(string name) + { + return FindEntityType(name); + } - public IEnumerable GetAnnotations() - { - throw new NotImplementedException(); - } + public IReadOnlyEntityType FindEntityType(string name, string definingNavigationName, IReadOnlyEntityType definingEntityType) + { + throw new NotImplementedException(); + } - public IEntityType FindEntityType(string name) - { - throw new NotImplementedException(); - } + public IEntityType FindEntityType(Type type) + { + throw new NotImplementedException(); + } - public IEntityType FindEntityType(string name, string definingNavigationName, IEntityType definingEntityType) - { - throw new NotImplementedException(); - } + IReadOnlyEntityType IReadOnlyModel.FindEntityType(Type type) + { + return FindEntityType(type); + } + + public IReadOnlyEntityType FindEntityType(Type type, string definingNavigationName, IReadOnlyEntityType definingEntityType) + { + throw new NotImplementedException(); + } + + public IEnumerable FindEntityTypes(Type type) + { + throw new NotImplementedException(); + } + + public bool IsIndexerMethod(MethodInfo methodInfo) + { + throw new NotImplementedException(); + } + + public IEnumerable GetTypeMappingConfigurations() + { + throw new NotImplementedException(); + } + + public ITypeMappingConfiguration FindTypeMappingConfiguration(Type scalarType) + { + throw new NotImplementedException(); + } + + IEnumerable IReadOnlyModel.FindEntityTypes(Type type) + { + return FindEntityTypes(type); + } + + public IAnnotation FindRuntimeAnnotation(string name) + { + throw new NotImplementedException(); + } + + public IEnumerable GetRuntimeAnnotations() + { + throw new NotImplementedException(); + } + + public IAnnotation AddRuntimeAnnotation(string name, object? value) + { + throw new NotImplementedException(); + } + + public IAnnotation SetRuntimeAnnotation(string name, object? value) + { + throw new NotImplementedException(); + } + + public IAnnotation RemoveRuntimeAnnotation(string name) + { + throw new NotImplementedException(); + } + + public TValue GetOrAddRuntimeAnnotationValue(string name, Func valueFactory, TArg? factoryArgument) + { + throw new NotImplementedException(); } } diff --git a/src/JsonApiDotNetCore.MongoDb/Repositories/MongoProperty.cs b/src/JsonApiDotNetCore.MongoDb/Repositories/MongoProperty.cs index e50fb24..b876890 100644 --- a/src/JsonApiDotNetCore.MongoDb/Repositories/MongoProperty.cs +++ b/src/JsonApiDotNetCore.MongoDb/Repositories/MongoProperty.cs @@ -1,42 +1,212 @@ -using System; -using System.Collections.Generic; using System.Reflection; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Microsoft.EntityFrameworkCore.Update; +using Microsoft.EntityFrameworkCore.ValueGeneration; -namespace JsonApiDotNetCore.MongoDb.Repositories +namespace JsonApiDotNetCore.MongoDb.Repositories; + +internal sealed class MongoProperty : IProperty { - internal sealed class MongoProperty : IProperty - { - public IEntityType DeclaringEntityType { get; } - public PropertyInfo PropertyInfo { get; } - - public string Name => throw new NotImplementedException(); - public Type ClrType => throw new NotImplementedException(); - public FieldInfo FieldInfo => throw new NotImplementedException(); - public ITypeBase DeclaringType => throw new NotImplementedException(); - public bool IsNullable => throw new NotImplementedException(); - public ValueGenerated ValueGenerated => throw new NotImplementedException(); - public bool IsConcurrencyToken => throw new NotImplementedException(); - public object this[string name] => throw new NotImplementedException(); - - public MongoProperty(PropertyInfo propertyInfo, MongoEntityType owner) - { - ArgumentGuard.NotNull(owner, nameof(owner)); - ArgumentGuard.NotNull(propertyInfo, nameof(propertyInfo)); - - DeclaringEntityType = owner; - PropertyInfo = propertyInfo; - } - - public IAnnotation FindAnnotation(string name) - { - throw new NotImplementedException(); - } - - public IEnumerable GetAnnotations() - { - throw new NotImplementedException(); - } + IReadOnlyEntityType IReadOnlyProperty.DeclaringEntityType => DeclaringEntityType; + + public IEntityType DeclaringEntityType { get; } + public PropertyInfo? PropertyInfo { get; } + + public string Name => throw new NotImplementedException(); + public IReadOnlyTypeBase DeclaringType => throw new NotImplementedException(); + public Type ClrType => throw new NotImplementedException(); + public FieldInfo FieldInfo => throw new NotImplementedException(); + public bool IsNullable => throw new NotImplementedException(); + public ValueGenerated ValueGenerated => throw new NotImplementedException(); + public bool IsConcurrencyToken => throw new NotImplementedException(); + public object this[string name] => throw new NotImplementedException(); + + public MongoProperty(PropertyInfo propertyInfo, MongoEntityType owner) + { + ArgumentGuard.NotNull(owner, nameof(owner)); + ArgumentGuard.NotNull(propertyInfo, nameof(propertyInfo)); + + DeclaringEntityType = owner; + PropertyInfo = propertyInfo; + } + + public IAnnotation FindAnnotation(string name) + { + throw new NotImplementedException(); + } + + public IEnumerable GetAnnotations() + { + throw new NotImplementedException(); + } + + public PropertyAccessMode GetPropertyAccessMode() + { + throw new NotImplementedException(); + } + + public IClrPropertyGetter GetGetter() + { + throw new NotImplementedException(); + } + + public IComparer GetCurrentValueComparer() + { + throw new NotImplementedException(); + } + + public CoreTypeMapping FindTypeMapping() + { + throw new NotImplementedException(); + } + + public int? GetMaxLength() + { + throw new NotImplementedException(); + } + + public int? GetPrecision() + { + throw new NotImplementedException(); + } + + public int? GetScale() + { + throw new NotImplementedException(); + } + + public bool? IsUnicode() + { + throw new NotImplementedException(); + } + + public PropertySaveBehavior GetBeforeSaveBehavior() + { + throw new NotImplementedException(); + } + + public PropertySaveBehavior GetAfterSaveBehavior() + { + throw new NotImplementedException(); + } + + public Func GetValueGeneratorFactory() + { + throw new NotImplementedException(); + } + + public ValueConverter GetValueConverter() + { + throw new NotImplementedException(); + } + + public Type GetProviderClrType() + { + throw new NotImplementedException(); + } + + ValueComparer IProperty.GetValueComparer() + { + throw new NotImplementedException(); + } + + ValueComparer IProperty.GetKeyValueComparer() + { + throw new NotImplementedException(); + } + + public IEnumerable GetContainingKeys() + { + throw new NotImplementedException(); + } + + ValueComparer IReadOnlyProperty.GetValueComparer() + { + throw new NotImplementedException(); + } + + ValueComparer IReadOnlyProperty.GetKeyValueComparer() + { + throw new NotImplementedException(); + } + + public bool IsForeignKey() + { + throw new NotImplementedException(); + } + + public IEnumerable GetContainingForeignKeys() + { + throw new NotImplementedException(); + } + + public IEnumerable GetContainingIndexes() + { + throw new NotImplementedException(); + } + + IEnumerable IReadOnlyProperty.GetContainingForeignKeys() + { + return GetContainingForeignKeys(); + } + + public bool IsIndex() + { + throw new NotImplementedException(); + } + + IEnumerable IReadOnlyProperty.GetContainingIndexes() + { + return GetContainingIndexes(); + } + + public IReadOnlyKey FindContainingPrimaryKey() + { + throw new NotImplementedException(); + } + + public bool IsKey() + { + throw new NotImplementedException(); + } + + IEnumerable IReadOnlyProperty.GetContainingKeys() + { + return GetContainingKeys(); + } + + public IAnnotation FindRuntimeAnnotation(string name) + { + throw new NotImplementedException(); + } + + public IEnumerable GetRuntimeAnnotations() + { + throw new NotImplementedException(); + } + + public IAnnotation AddRuntimeAnnotation(string name, object? value) + { + throw new NotImplementedException(); + } + + public IAnnotation SetRuntimeAnnotation(string name, object? value) + { + throw new NotImplementedException(); + } + + public IAnnotation RemoveRuntimeAnnotation(string name) + { + throw new NotImplementedException(); + } + + public TValue GetOrAddRuntimeAnnotationValue(string name, Func valueFactory, TArg? factoryArgument) + { + throw new NotImplementedException(); } } diff --git a/src/JsonApiDotNetCore.MongoDb/Repositories/MongoQueryExpressionValidator.cs b/src/JsonApiDotNetCore.MongoDb/Repositories/MongoQueryExpressionValidator.cs index 5c3df17..a255259 100644 --- a/src/JsonApiDotNetCore.MongoDb/Repositories/MongoQueryExpressionValidator.cs +++ b/src/JsonApiDotNetCore.MongoDb/Repositories/MongoQueryExpressionValidator.cs @@ -1,59 +1,54 @@ -using System.Linq; using JsonApiDotNetCore.MongoDb.Errors; using JsonApiDotNetCore.Queries; using JsonApiDotNetCore.Queries.Expressions; using JsonApiDotNetCore.Resources.Annotations; -namespace JsonApiDotNetCore.MongoDb.Repositories +namespace JsonApiDotNetCore.MongoDb.Repositories; + +internal sealed class MongoQueryExpressionValidator : QueryExpressionRewriter { - internal sealed class MongoQueryExpressionValidator : QueryExpressionRewriter + public void Validate(QueryLayer layer) { - public void Validate(QueryLayer layer) - { - ArgumentGuard.NotNull(layer, nameof(layer)); + ArgumentGuard.NotNull(layer, nameof(layer)); - bool hasIncludes = layer.Include?.Elements.Any() == true; - bool hasSparseRelationshipSets = layer.Projection?.Any(pair => pair.Key is RelationshipAttribute) == true; + bool hasIncludes = layer.Include?.Elements.Any() == true; + bool hasSparseRelationshipSets = layer.Projection?.Any(pair => pair.Key is RelationshipAttribute) == true; - if (hasIncludes || hasSparseRelationshipSets) - { - throw new UnsupportedRelationshipException(); - } - - ValidateExpression(layer.Filter); - ValidateExpression(layer.Sort); - ValidateExpression(layer.Pagination); + if (hasIncludes || hasSparseRelationshipSets) + { + throw new UnsupportedRelationshipException(); } - private void ValidateExpression(QueryExpression expression) + ValidateExpression(layer.Filter); + ValidateExpression(layer.Sort); + ValidateExpression(layer.Pagination); + } + + private void ValidateExpression(QueryExpression? expression) + { + if (expression != null) { - if (expression != null) - { - Visit(expression, null); - } + Visit(expression, null); } + } - public override QueryExpression VisitResourceFieldChain(ResourceFieldChainExpression expression, object argument) + public override QueryExpression? VisitResourceFieldChain(ResourceFieldChainExpression expression, object? argument) + { + if (expression.Fields.Count > 1 || expression.Fields.First() is RelationshipAttribute) { - if (expression != null) - { - if (expression.Fields.Count > 1 || expression.Fields.First() is RelationshipAttribute) - { - throw new UnsupportedRelationshipException(); - } - } - - return base.VisitResourceFieldChain(expression, argument); + throw new UnsupportedRelationshipException(); } - public override QueryExpression VisitComparison(ComparisonExpression expression, object argument) - { - if (expression?.Left is ResourceFieldChainExpression && expression.Right is ResourceFieldChainExpression) - { - throw new AttributeComparisonInFilterNotSupportedException(); - } + return base.VisitResourceFieldChain(expression, argument); + } - return base.VisitComparison(expression, argument); + public override QueryExpression? VisitComparison(ComparisonExpression expression, object? argument) + { + if (expression.Left is ResourceFieldChainExpression && expression.Right is ResourceFieldChainExpression) + { + throw new AttributeComparisonInFilterNotSupportedException(); } + + return base.VisitComparison(expression, argument); } } diff --git a/src/JsonApiDotNetCore.MongoDb/Repositories/MongoRepository.cs b/src/JsonApiDotNetCore.MongoDb/Repositories/MongoRepository.cs index cd3bf6d..07a6030 100644 --- a/src/JsonApiDotNetCore.MongoDb/Repositories/MongoRepository.cs +++ b/src/JsonApiDotNetCore.MongoDb/Repositories/MongoRepository.cs @@ -1,12 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; -using System.Threading; -using System.Threading.Tasks; using JetBrains.Annotations; using JsonApiDotNetCore.Configuration; using JsonApiDotNetCore.Errors; +using JsonApiDotNetCore.Middleware; using JsonApiDotNetCore.MongoDb.Errors; using JsonApiDotNetCore.MongoDb.Queries.Internal.QueryableBuilding; using JsonApiDotNetCore.Queries; @@ -18,287 +14,291 @@ using MongoDB.Driver; using MongoDB.Driver.Linq; -namespace JsonApiDotNetCore.MongoDb.Repositories +namespace JsonApiDotNetCore.MongoDb.Repositories; + +/// +/// Implements the foundational Repository layer in the JsonApiDotNetCore architecture that uses MongoDB. +/// +[PublicAPI] +public class MongoRepository : IResourceRepository, IRepositorySupportsTransaction + where TResource : class, IIdentifiable { - /// - /// Implements the foundational Repository layer in the JsonApiDotNetCore architecture that uses MongoDB. - /// - [PublicAPI] - public class MongoRepository : IResourceRepository, IRepositorySupportsTransaction - where TResource : class, IIdentifiable - { - private readonly IMongoDataAccess _mongoDataAccess; - private readonly ITargetedFields _targetedFields; - private readonly IResourceContextProvider _resourceContextProvider; - private readonly IResourceFactory _resourceFactory; - private readonly IEnumerable _constraintProviders; + private readonly IMongoDataAccess _mongoDataAccess; + private readonly ITargetedFields _targetedFields; + private readonly IResourceGraph _resourceGraph; + private readonly IResourceFactory _resourceFactory; + private readonly IEnumerable _constraintProviders; + private readonly IResourceDefinitionAccessor _resourceDefinitionAccessor; - protected virtual IMongoCollection Collection => _mongoDataAccess.MongoDatabase.GetCollection(typeof(TResource).Name); + protected virtual IMongoCollection Collection => _mongoDataAccess.MongoDatabase.GetCollection(typeof(TResource).Name); - /// - public virtual string TransactionId => _mongoDataAccess.TransactionId; + /// + public virtual string? TransactionId => _mongoDataAccess.TransactionId; - public MongoRepository(IMongoDataAccess mongoDataAccess, ITargetedFields targetedFields, IResourceContextProvider resourceContextProvider, - IResourceFactory resourceFactory, IEnumerable constraintProviders) + public MongoRepository(IMongoDataAccess mongoDataAccess, ITargetedFields targetedFields, IResourceGraph resourceGraph, IResourceFactory resourceFactory, + IEnumerable constraintProviders, IResourceDefinitionAccessor resourceDefinitionAccessor) + { + ArgumentGuard.NotNull(mongoDataAccess, nameof(mongoDataAccess)); + ArgumentGuard.NotNull(targetedFields, nameof(targetedFields)); + ArgumentGuard.NotNull(resourceGraph, nameof(resourceGraph)); + ArgumentGuard.NotNull(resourceFactory, nameof(resourceFactory)); + ArgumentGuard.NotNull(constraintProviders, nameof(constraintProviders)); + ArgumentGuard.NotNull(resourceDefinitionAccessor, nameof(resourceDefinitionAccessor)); + + _mongoDataAccess = mongoDataAccess; + _targetedFields = targetedFields; + _resourceGraph = resourceGraph; + _resourceFactory = resourceFactory; + _constraintProviders = constraintProviders; + _resourceDefinitionAccessor = resourceDefinitionAccessor; + + if (typeof(TId) != typeof(string)) { - ArgumentGuard.NotNull(mongoDataAccess, nameof(mongoDataAccess)); - ArgumentGuard.NotNull(targetedFields, nameof(targetedFields)); - ArgumentGuard.NotNull(resourceContextProvider, nameof(resourceContextProvider)); - ArgumentGuard.NotNull(resourceFactory, nameof(resourceFactory)); - ArgumentGuard.NotNull(constraintProviders, nameof(constraintProviders)); - - _mongoDataAccess = mongoDataAccess; - _targetedFields = targetedFields; - _resourceContextProvider = resourceContextProvider; - _resourceFactory = resourceFactory; - _constraintProviders = constraintProviders; - - if (typeof(TId) != typeof(string)) - { - throw new InvalidConfigurationException("MongoDB can only be used for resources with an 'Id' property of type 'string'."); - } + throw new InvalidConfigurationException("MongoDB can only be used for resources with an 'Id' property of type 'string'."); } + } - /// - public virtual async Task> GetAsync(QueryLayer layer, CancellationToken cancellationToken) - { - ArgumentGuard.NotNull(layer, nameof(layer)); + /// + public virtual async Task> GetAsync(QueryLayer queryLayer, CancellationToken cancellationToken) + { + ArgumentGuard.NotNull(queryLayer, nameof(queryLayer)); - IMongoQueryable query = ApplyQueryLayer(layer); - List resources = await query.ToListAsync(cancellationToken); - return resources.AsReadOnly(); - } + IMongoQueryable query = ApplyQueryLayer(queryLayer); + return await query.ToListAsync(cancellationToken); + } - /// - public virtual Task CountAsync(FilterExpression topFilter, CancellationToken cancellationToken) + /// + public virtual Task CountAsync(FilterExpression? topFilter, CancellationToken cancellationToken) + { + ResourceType resourceType = _resourceGraph.GetResourceType(); + + var layer = new QueryLayer(resourceType) { - ResourceContext resourceContext = _resourceContextProvider.GetResourceContext(); + Filter = topFilter + }; - var layer = new QueryLayer(resourceContext) - { - Filter = topFilter - }; + IMongoQueryable query = ApplyQueryLayer(layer); + return query.CountAsync(cancellationToken); + } - IMongoQueryable query = ApplyQueryLayer(layer); - return query.CountAsync(cancellationToken); - } + protected virtual IMongoQueryable ApplyQueryLayer(QueryLayer queryLayer) + { + ArgumentGuard.NotNull(queryLayer, nameof(queryLayer)); - protected virtual IMongoQueryable ApplyQueryLayer(QueryLayer layer) - { - ArgumentGuard.NotNull(layer, nameof(layer)); + var queryExpressionValidator = new MongoQueryExpressionValidator(); + queryExpressionValidator.Validate(queryLayer); - var queryExpressionValidator = new MongoQueryExpressionValidator(); - queryExpressionValidator.Validate(layer); + AssertNoRelationshipsInSparseFieldSets(); - AssertNoRelationshipsInSparseFieldSets(); + IQueryable source = GetAll(); - IQueryable source = GetAll(); + // @formatter:wrap_chained_method_calls chop_always + // @formatter:keep_existing_linebreaks true - // @formatter:wrap_chained_method_calls chop_always - // @formatter:keep_existing_linebreaks true + QueryableHandlerExpression[] queryableHandlers = _constraintProviders + .SelectMany(provider => provider.GetConstraints()) + .Where(expressionInScope => expressionInScope.Scope == null) + .Select(expressionInScope => expressionInScope.Expression) + .OfType() + .ToArray(); - QueryableHandlerExpression[] queryableHandlers = _constraintProviders - .SelectMany(provider => provider.GetConstraints()) - .Where(expressionInScope => expressionInScope.Scope == null) - .Select(expressionInScope => expressionInScope.Expression) - .OfType() - .ToArray(); + // @formatter:keep_existing_linebreaks restore + // @formatter:wrap_chained_method_calls restore - // @formatter:keep_existing_linebreaks restore - // @formatter:wrap_chained_method_calls restore + foreach (QueryableHandlerExpression queryableHandler in queryableHandlers) + { + source = queryableHandler.Apply(source); + } - foreach (QueryableHandlerExpression queryableHandler in queryableHandlers) - { - source = queryableHandler.Apply(source); - } + var nameFactory = new LambdaParameterNameFactory(); - var nameFactory = new LambdaParameterNameFactory(); + var builder = new MongoQueryableBuilder(source.Expression, source.ElementType, typeof(Queryable), nameFactory, _resourceFactory, + new MongoModel(_resourceGraph)); - var builder = new MongoQueryableBuilder(source.Expression, source.ElementType, typeof(Queryable), nameFactory, _resourceFactory, - _resourceContextProvider, new MongoModel(_resourceContextProvider)); + Expression expression = builder.ApplyQuery(queryLayer); + return (IMongoQueryable)source.Provider.CreateQuery(expression); + } - Expression expression = builder.ApplyQuery(layer); - return (IMongoQueryable)source.Provider.CreateQuery(expression); - } + protected virtual IQueryable GetAll() + { + return _mongoDataAccess.ActiveSession != null ? Collection.AsQueryable(_mongoDataAccess.ActiveSession) : Collection.AsQueryable(); + } - protected virtual IQueryable GetAll() + private void AssertNoRelationshipsInSparseFieldSets() + { + ResourceType resourceType = _resourceGraph.GetResourceType(); + + // @formatter:wrap_chained_method_calls chop_always + // @formatter:keep_existing_linebreaks true + + bool hasRelationshipSelectors = _constraintProviders + .SelectMany(provider => provider.GetConstraints()) + .Select(expressionInScope => expressionInScope.Expression) + .OfType() + .Any(fieldTable => + fieldTable.Table.Keys.Any(targetResourceType => !resourceType.Equals(targetResourceType)) || + fieldTable.Table.Values.Any(fieldSet => fieldSet.Fields.Any(field => field is RelationshipAttribute))); + + // @formatter:keep_existing_linebreaks restore + // @formatter:wrap_chained_method_calls restore + + if (hasRelationshipSelectors) { - return _mongoDataAccess.ActiveSession != null ? Collection.AsQueryable(_mongoDataAccess.ActiveSession) : Collection.AsQueryable(); + throw new UnsupportedRelationshipException(); } + } - private void AssertNoRelationshipsInSparseFieldSets() - { - ResourceContext resourceContext = _resourceContextProvider.GetResourceContext(); + /// + public virtual Task GetForCreateAsync(TId id, CancellationToken cancellationToken) + { + var resource = _resourceFactory.CreateInstance(); + resource.Id = id; - // @formatter:wrap_chained_method_calls chop_always - // @formatter:keep_existing_linebreaks true + return Task.FromResult(resource); + } - bool hasRelationshipSelectors = _constraintProviders - .SelectMany(provider => provider.GetConstraints()) - .Select(expressionInScope => expressionInScope.Expression) - .OfType() - .Any(fieldTable => - fieldTable.Table.Keys.Any(targetResourceContext => targetResourceContext != resourceContext) || - fieldTable.Table.Values.Any(fieldSet => fieldSet.Fields.Any(field => field is RelationshipAttribute))); + /// + public virtual async Task CreateAsync(TResource resourceFromRequest, TResource resourceForDatabase, CancellationToken cancellationToken) + { + ArgumentGuard.NotNull(resourceFromRequest, nameof(resourceFromRequest)); + ArgumentGuard.NotNull(resourceForDatabase, nameof(resourceForDatabase)); - // @formatter:keep_existing_linebreaks restore - // @formatter:wrap_chained_method_calls restore + AssertNoRelationshipsAreTargeted(); - if (hasRelationshipSelectors) - { - throw new UnsupportedRelationshipException(); - } + foreach (AttrAttribute attribute in _targetedFields.Attributes) + { + attribute.SetValue(resourceForDatabase, attribute.GetValue(resourceFromRequest)); } - /// - public virtual Task GetForCreateAsync(TId id, CancellationToken cancellationToken) + await _resourceDefinitionAccessor.OnWritingAsync(resourceForDatabase, WriteOperationKind.CreateResource, cancellationToken); + + await SaveChangesAsync(async () => { - var resource = _resourceFactory.CreateInstance(); - resource.Id = id; + await (_mongoDataAccess.ActiveSession != null + ? Collection.InsertOneAsync(_mongoDataAccess.ActiveSession, resourceForDatabase, cancellationToken: cancellationToken) + : Collection.InsertOneAsync(resourceForDatabase, cancellationToken: cancellationToken)); + }, cancellationToken); - return Task.FromResult(resource); - } + await _resourceDefinitionAccessor.OnWriteSucceededAsync(resourceForDatabase, WriteOperationKind.CreateResource, cancellationToken); + } - /// - public virtual async Task CreateAsync(TResource resourceFromRequest, TResource resourceForDatabase, CancellationToken cancellationToken) + private void AssertNoRelationshipsAreTargeted() + { + if (_targetedFields.Relationships.Any()) { - ArgumentGuard.NotNull(resourceFromRequest, nameof(resourceFromRequest)); - ArgumentGuard.NotNull(resourceForDatabase, nameof(resourceForDatabase)); + throw new UnsupportedRelationshipException(); + } + } - AssertNoRelationshipsAreTargeted(); + /// + public virtual async Task GetForUpdateAsync(QueryLayer queryLayer, CancellationToken cancellationToken) + { + ArgumentGuard.NotNull(queryLayer, nameof(queryLayer)); - foreach (AttrAttribute attribute in _targetedFields.Attributes) - { - attribute.SetValue(resourceForDatabase, attribute.GetValue(resourceFromRequest)); - } + IReadOnlyCollection resources = await GetAsync(queryLayer, cancellationToken); + return resources.FirstOrDefault(); + } - await SaveChangesAsync(async () => - { - await (_mongoDataAccess.ActiveSession != null - ? Collection.InsertOneAsync(_mongoDataAccess.ActiveSession, resourceForDatabase, cancellationToken: cancellationToken) - : Collection.InsertOneAsync(resourceForDatabase, cancellationToken: cancellationToken)); - }, cancellationToken); - } + /// + public virtual async Task UpdateAsync(TResource resourceFromRequest, TResource resourceFromDatabase, CancellationToken cancellationToken) + { + ArgumentGuard.NotNull(resourceFromRequest, nameof(resourceFromRequest)); + ArgumentGuard.NotNull(resourceFromDatabase, nameof(resourceFromDatabase)); - private void AssertNoRelationshipsAreTargeted() - { - if (_targetedFields.Relationships.Any()) - { - throw new UnsupportedRelationshipException(); - } - } + AssertNoRelationshipsAreTargeted(); - /// - public virtual async Task GetForUpdateAsync(QueryLayer queryLayer, CancellationToken cancellationToken) + foreach (AttrAttribute attr in _targetedFields.Attributes) { - IReadOnlyCollection resources = await GetAsync(queryLayer, cancellationToken); - return resources.FirstOrDefault(); + attr.SetValue(resourceFromDatabase, attr.GetValue(resourceFromRequest)); } - /// - public virtual async Task UpdateAsync(TResource resourceFromRequest, TResource resourceFromDatabase, CancellationToken cancellationToken) - { - ArgumentGuard.NotNull(resourceFromRequest, nameof(resourceFromRequest)); - ArgumentGuard.NotNull(resourceFromDatabase, nameof(resourceFromDatabase)); - - AssertNoRelationshipsAreTargeted(); + await _resourceDefinitionAccessor.OnWritingAsync(resourceFromDatabase, WriteOperationKind.UpdateResource, cancellationToken); - foreach (AttrAttribute attr in _targetedFields.Attributes) - { - attr.SetValue(resourceFromDatabase, attr.GetValue(resourceFromRequest)); - } + FilterDefinition filter = Builders.Filter.Eq(resource => resource.Id, resourceFromDatabase.Id); - FilterDefinition filter = Builders.Filter.Eq(resource => resource.Id, resourceFromDatabase.Id); + await SaveChangesAsync(async () => + { + await (_mongoDataAccess.ActiveSession != null + ? Collection.ReplaceOneAsync(_mongoDataAccess.ActiveSession, filter, resourceFromDatabase, cancellationToken: cancellationToken) + : Collection.ReplaceOneAsync(filter, resourceFromDatabase, cancellationToken: cancellationToken)); + }, cancellationToken); - await SaveChangesAsync(async () => - { - await (_mongoDataAccess.ActiveSession != null - ? Collection.ReplaceOneAsync(_mongoDataAccess.ActiveSession, filter, resourceFromDatabase, cancellationToken: cancellationToken) - : Collection.ReplaceOneAsync(filter, resourceFromDatabase, cancellationToken: cancellationToken)); - }, cancellationToken); - } + await _resourceDefinitionAccessor.OnWriteSucceededAsync(resourceFromDatabase, WriteOperationKind.UpdateResource, cancellationToken); + } - /// - public virtual async Task DeleteAsync(TId id, CancellationToken cancellationToken) - { - FilterDefinition filter = Builders.Filter.Eq(resource => resource.Id, id); + /// + public virtual async Task DeleteAsync(TId id, CancellationToken cancellationToken) + { + var placeholderResource = _resourceFactory.CreateInstance(); + placeholderResource.Id = id; - DeleteResult result = await SaveChangesAsync( - async () => _mongoDataAccess.ActiveSession != null - ? await Collection.DeleteOneAsync(_mongoDataAccess.ActiveSession, filter, cancellationToken: cancellationToken) - : await Collection.DeleteOneAsync(filter, cancellationToken), cancellationToken); + await _resourceDefinitionAccessor.OnWritingAsync(placeholderResource, WriteOperationKind.DeleteResource, cancellationToken); - if (!result.IsAcknowledged) - { - throw new DataStoreUpdateException( - new Exception($"Failed to delete document with id '{id}', because the operation was not acknowledged by MongoDB.")); - } + FilterDefinition filter = Builders.Filter.Eq(resource => resource.Id, id); - if (result.DeletedCount == 0) - { - throw new DataStoreUpdateException(new Exception($"Failed to delete document with id '{id}', because it does not exist.")); - } - } + DeleteResult result = await SaveChangesAsync( + async () => _mongoDataAccess.ActiveSession != null + ? await Collection.DeleteOneAsync(_mongoDataAccess.ActiveSession, filter, cancellationToken: cancellationToken) + : await Collection.DeleteOneAsync(filter, cancellationToken), cancellationToken); - /// - public virtual Task SetRelationshipAsync(TResource primaryResource, object secondaryResourceIds, CancellationToken cancellationToken) + if (!result.IsAcknowledged) { - throw new UnsupportedRelationshipException(); + throw new DataStoreUpdateException( + new Exception($"Failed to delete document with id '{id}', because the operation was not acknowledged by MongoDB.")); } - /// - public virtual Task AddToToManyRelationshipAsync(TId primaryId, ISet secondaryResourceIds, CancellationToken cancellationToken) + if (result.DeletedCount == 0) { - throw new UnsupportedRelationshipException(); + throw new DataStoreUpdateException(new Exception($"Failed to delete document with id '{id}', because it does not exist.")); } - /// - public virtual Task RemoveFromToManyRelationshipAsync(TResource primaryResource, ISet secondaryResourceIds, - CancellationToken cancellationToken) + await _resourceDefinitionAccessor.OnWriteSucceededAsync(placeholderResource, WriteOperationKind.DeleteResource, cancellationToken); + } + + /// + public virtual Task SetRelationshipAsync(TResource leftResource, object? rightValue, CancellationToken cancellationToken) + { + throw new UnsupportedRelationshipException(); + } + + /// + public virtual Task AddToToManyRelationshipAsync(TId leftId, ISet rightResourceIds, CancellationToken cancellationToken) + { + throw new UnsupportedRelationshipException(); + } + + /// + public virtual Task RemoveFromToManyRelationshipAsync(TResource leftResource, ISet rightResourceIds, CancellationToken cancellationToken) + { + throw new UnsupportedRelationshipException(); + } + + protected virtual async Task SaveChangesAsync(Func asyncSaveAction, CancellationToken cancellationToken) + { + _ = await SaveChangesAsync(async () => { - throw new UnsupportedRelationshipException(); - } + await asyncSaveAction(); + return null; + }, cancellationToken); + } - protected virtual async Task SaveChangesAsync(Func asyncSaveAction, CancellationToken cancellationToken) + protected virtual async Task SaveChangesAsync(Func> asyncSaveAction, CancellationToken cancellationToken) + { + try { - _ = await SaveChangesAsync(async () => - { - await asyncSaveAction(); - return null; - }, cancellationToken); + return await asyncSaveAction(); } - - protected virtual async Task SaveChangesAsync(Func> asyncSaveAction, CancellationToken cancellationToken) + catch (MongoException exception) { - try + if (_mongoDataAccess.ActiveSession != null) { - return await asyncSaveAction(); + // The ResourceService calling us needs to run additional SQL queries after an aborted transaction, + // to determine error cause. This fails when a failed transaction is still in progress. + await _mongoDataAccess.ActiveSession.AbortTransactionAsync(cancellationToken); + _mongoDataAccess.ActiveSession = null; } - catch (MongoException exception) - { - if (_mongoDataAccess.ActiveSession != null) - { - // The ResourceService calling us needs to run additional SQL queries after an aborted transaction, - // to determine error cause. This fails when a failed transaction is still in progress. - await _mongoDataAccess.ActiveSession.AbortTransactionAsync(cancellationToken); - _mongoDataAccess.ActiveSession = null; - } - - throw new DataStoreUpdateException(exception); - } - } - } - /// - /// Do not use. This type exists solely to produce a proper error message when trying to use MongoDB with a non-string Id. - /// - public sealed class MongoRepository : MongoRepository, IResourceRepository - where TResource : class, IIdentifiable - { - public MongoRepository(IMongoDataAccess mongoDataAccess, ITargetedFields targetedFields, IResourceContextProvider resourceContextProvider, - IResourceFactory resourceFactory, IEnumerable constraintProviders) - : base(mongoDataAccess, targetedFields, resourceContextProvider, resourceFactory, constraintProviders) - { + throw new DataStoreUpdateException(exception); } } } diff --git a/src/JsonApiDotNetCore.MongoDb/Resources/MongoIdentifiable.cs b/src/JsonApiDotNetCore.MongoDb/Resources/MongoIdentifiable.cs index ded90d5..b8b4104 100644 --- a/src/JsonApiDotNetCore.MongoDb/Resources/MongoIdentifiable.cs +++ b/src/JsonApiDotNetCore.MongoDb/Resources/MongoIdentifiable.cs @@ -2,28 +2,27 @@ using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; -namespace JsonApiDotNetCore.MongoDb.Resources -{ - /// - /// A convenient basic implementation of for use with MongoDB models. - /// - public abstract class MongoIdentifiable : IIdentifiable - { - /// - [BsonId] - [BsonRepresentation(BsonType.ObjectId)] - public virtual string Id { get; set; } +namespace JsonApiDotNetCore.MongoDb.Resources; - /// - [BsonIgnore] - public string StringId - { - get => Id; - set => Id = value; - } +/// +/// A convenient basic implementation of for use with MongoDB models. +/// +public abstract class MongoIdentifiable : IIdentifiable +{ + /// + [BsonId] + [BsonRepresentation(BsonType.ObjectId)] + public virtual string? Id { get; set; } - /// - [BsonIgnore] - public string LocalId { get; set; } + /// + [BsonIgnore] + public string? StringId + { + get => Id; + set => Id = value; } + + /// + [BsonIgnore] + public string? LocalId { get; set; } } diff --git a/src/JsonApiDotNetCore.MongoDb/Serialization/Building/IgnoreRelationshipsResponseResourceObjectBuilder.cs b/src/JsonApiDotNetCore.MongoDb/Serialization/Building/IgnoreRelationshipsResponseResourceObjectBuilder.cs deleted file mode 100644 index 62cbc5c..0000000 --- a/src/JsonApiDotNetCore.MongoDb/Serialization/Building/IgnoreRelationshipsResponseResourceObjectBuilder.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Collections.Generic; -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.MongoDb.Resources; -using JsonApiDotNetCore.Queries; -using JsonApiDotNetCore.Queries.Internal; -using JsonApiDotNetCore.Resources; -using JsonApiDotNetCore.Resources.Annotations; -using JsonApiDotNetCore.Serialization.Building; -using JsonApiDotNetCore.Serialization.Objects; - -namespace JsonApiDotNetCore.MongoDb.Serialization.Building -{ - /// - public sealed class IgnoreRelationshipsResponseResourceObjectBuilder : ResponseResourceObjectBuilder - { - public IgnoreRelationshipsResponseResourceObjectBuilder(ILinkBuilder linkBuilder, IIncludedResourceObjectBuilder includedBuilder, - IEnumerable constraintProviders, IResourceContextProvider resourceContextProvider, - IResourceDefinitionAccessor resourceDefinitionAccessor, IResourceObjectBuilderSettingsProvider settingsProvider, - IEvaluatedIncludeCache evaluatedIncludeCache) - : base(linkBuilder, includedBuilder, constraintProviders, resourceContextProvider, resourceDefinitionAccessor, settingsProvider, - evaluatedIncludeCache) - { - } - - /// - protected override RelationshipEntry GetRelationshipData(RelationshipAttribute relationship, IIdentifiable resource) - { - if (resource is MongoIdentifiable) - { - return null; - } - - return base.GetRelationshipData(relationship, resource); - } - } -} diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/AtomicOperationsFixture.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/AtomicOperationsFixture.cs deleted file mode 100644 index 30b3b11..0000000 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/AtomicOperationsFixture.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using JetBrains.Annotations; -using JsonApiDotNetCoreMongoDbExampleTests.TestBuildingBlocks; - -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations -{ - [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] - public sealed class AtomicOperationsFixture : IDisposable - { - internal IntegrationTestContext TestContext { get; } - - public AtomicOperationsFixture() - { - TestContext = new IntegrationTestContext - { - StartMongoDbInSingleNodeReplicaSetMode = true - }; - } - - public void Dispose() - { - TestContext.Dispose(); - } - } -} diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/AtomicOperationsTestCollection.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/AtomicOperationsTestCollection.cs deleted file mode 100644 index 355dab3..0000000 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/AtomicOperationsTestCollection.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Xunit; - -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations -{ - [CollectionDefinition("AtomicOperationsFixture")] - public sealed class AtomicOperationsTestCollection : ICollectionFixture - { - // Starting MongoDB in Single Node Replica Set mode is required to enable transactions. - // Starting in this mode requires about 10 seconds, which is normally repeated for each test class. - // So to improve test runtime performance, we reuse a single MongoDB instance for all atomic:operations tests. - } -} diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceTests.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceTests.cs deleted file mode 100644 index 3d90ce9..0000000 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceTests.cs +++ /dev/null @@ -1,229 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using FluentAssertions; -using JsonApiDotNetCore.Serialization.Objects; -using JsonApiDotNetCoreMongoDbExampleTests.TestBuildingBlocks; -using MongoDB.Driver; -using MongoDB.Driver.Linq; -using Xunit; - -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Creating -{ - [Collection("AtomicOperationsFixture")] - public sealed class AtomicCreateResourceTests - { - private readonly IntegrationTestContext _testContext; - private readonly OperationsFakers _fakers = new OperationsFakers(); - - public AtomicCreateResourceTests(AtomicOperationsFixture fixture) - { - _testContext = fixture.TestContext; - - fixture.TestContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); - } - - [Fact] - public async Task Can_create_resource() - { - // Arrange - string newArtistName = _fakers.Performer.Generate().ArtistName; - DateTimeOffset newBornAt = _fakers.Performer.Generate().BornAt; - - await _testContext.RunOnDatabaseAsync(async db => - { - await db.EnsureEmptyCollectionAsync(); - }); - - var requestBody = new - { - atomic__operations = new[] - { - new - { - op = "add", - data = new - { - type = "performers", - attributes = new - { - artistName = newArtistName, - bornAt = newBornAt - } - } - } - } - }; - - const string route = "/operations"; - - // Act - (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = - await _testContext.ExecutePostAtomicAsync(route, requestBody); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); - - responseDocument.Results.Should().HaveCount(1); - responseDocument.Results[0].SingleData.Should().NotBeNull(); - responseDocument.Results[0].SingleData.Type.Should().Be("performers"); - responseDocument.Results[0].SingleData.Attributes["artistName"].Should().Be(newArtistName); - responseDocument.Results[0].SingleData.Attributes["bornAt"].Should().BeCloseTo(newBornAt); - responseDocument.Results[0].SingleData.Relationships.Should().BeNull(); - - string newPerformerId = responseDocument.Results[0].SingleData.Id; - - await _testContext.RunOnDatabaseAsync(async db => - { - Performer performerInDatabase = await db.GetCollection().AsQueryable().FirstWithIdAsync(newPerformerId); - - performerInDatabase.ArtistName.Should().Be(newArtistName); - performerInDatabase.BornAt.Should().BeCloseTo(newBornAt); - }); - } - - [Fact] - public async Task Can_create_resources() - { - // Arrange - const int elementCount = 5; - - List newTracks = _fakers.MusicTrack.Generate(elementCount); - - await _testContext.RunOnDatabaseAsync(async db => - { - await db.EnsureEmptyCollectionAsync(); - }); - - var operationElements = new List(elementCount); - - for (int index = 0; index < elementCount; index++) - { - operationElements.Add(new - { - op = "add", - data = new - { - type = "musicTracks", - attributes = new - { - title = newTracks[index].Title, - lengthInSeconds = newTracks[index].LengthInSeconds, - genre = newTracks[index].Genre, - releasedAt = newTracks[index].ReleasedAt - } - } - }); - } - - var requestBody = new - { - atomic__operations = operationElements - }; - - const string route = "/operations"; - - // Act - (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = - await _testContext.ExecutePostAtomicAsync(route, requestBody); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); - - responseDocument.Results.Should().HaveCount(elementCount); - - for (int index = 0; index < elementCount; index++) - { - ResourceObject singleData = responseDocument.Results[index].SingleData; - - singleData.Should().NotBeNull(); - singleData.Type.Should().Be("musicTracks"); - singleData.Attributes["title"].Should().Be(newTracks[index].Title); - singleData.Attributes["lengthInSeconds"].As().Should().BeApproximately(newTracks[index].LengthInSeconds); - singleData.Attributes["genre"].Should().Be(newTracks[index].Genre); - singleData.Attributes["releasedAt"].Should().BeCloseTo(newTracks[index].ReleasedAt); - singleData.Relationships.Should().BeNull(); - } - - string[] newTrackIds = responseDocument.Results.Select(result => result.SingleData.Id).ToArray(); - - await _testContext.RunOnDatabaseAsync(async db => - { - List tracksInDatabase = await db.GetCollection().AsQueryable().Where(musicTrack => newTrackIds.Contains(musicTrack.Id)) - .ToListAsync(); - - tracksInDatabase.Should().HaveCount(elementCount); - - for (int index = 0; index < elementCount; index++) - { - MusicTrack trackInDatabase = tracksInDatabase.Single(musicTrack => musicTrack.Id == newTrackIds[index]); - - trackInDatabase.Title.Should().Be(newTracks[index].Title); - trackInDatabase.LengthInSeconds.Should().BeApproximately(newTracks[index].LengthInSeconds); - trackInDatabase.Genre.Should().Be(newTracks[index].Genre); - trackInDatabase.ReleasedAt.Should().BeCloseTo(newTracks[index].ReleasedAt); - } - }); - } - - [Fact] - public async Task Can_create_resource_without_attributes_or_relationships() - { - // Arrange - await _testContext.RunOnDatabaseAsync(async db => - { - await db.EnsureEmptyCollectionAsync(); - }); - - var requestBody = new - { - atomic__operations = new[] - { - new - { - op = "add", - data = new - { - type = "performers", - attributes = new - { - }, - relationship = new - { - } - } - } - } - }; - - const string route = "/operations"; - - // Act - (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = - await _testContext.ExecutePostAtomicAsync(route, requestBody); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); - - responseDocument.Results.Should().HaveCount(1); - responseDocument.Results[0].SingleData.Should().NotBeNull(); - responseDocument.Results[0].SingleData.Type.Should().Be("performers"); - responseDocument.Results[0].SingleData.Attributes["artistName"].Should().BeNull(); - responseDocument.Results[0].SingleData.Attributes["bornAt"].Should().BeCloseTo(default(DateTimeOffset)); - responseDocument.Results[0].SingleData.Relationships.Should().BeNull(); - - string newPerformerId = responseDocument.Results[0].SingleData.Id; - - await _testContext.RunOnDatabaseAsync(async db => - { - Performer performerInDatabase = await db.GetCollection().AsQueryable().FirstWithIdAsync(newPerformerId); - - performerInDatabase.ArtistName.Should().BeNull(); - performerInDatabase.BornAt.Should().Be(default); - }); - } - } -} diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithClientGeneratedIdTests.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithClientGeneratedIdTests.cs deleted file mode 100644 index 6ef4930..0000000 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithClientGeneratedIdTests.cs +++ /dev/null @@ -1,189 +0,0 @@ -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using FluentAssertions; -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Serialization.Objects; -using JsonApiDotNetCoreMongoDbExampleTests.TestBuildingBlocks; -using Microsoft.Extensions.DependencyInjection; -using MongoDB.Driver; -using Xunit; - -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Creating -{ - [Collection("AtomicOperationsFixture")] - public sealed class AtomicCreateResourceWithClientGeneratedIdTests - { - private readonly IntegrationTestContext _testContext; - private readonly OperationsFakers _fakers = new OperationsFakers(); - - public AtomicCreateResourceWithClientGeneratedIdTests(AtomicOperationsFixture fixture) - { - _testContext = fixture.TestContext; - - fixture.TestContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); - - var options = (JsonApiOptions)fixture.TestContext.Factory.Services.GetRequiredService(); - options.AllowClientGeneratedIds = true; - } - - [Fact] - public async Task Can_create_resource_with_client_generated_string_ID_having_side_effects() - { - // Arrange - TextLanguage newLanguage = _fakers.TextLanguage.Generate(); - newLanguage.Id = "507f191e810c19729de860ea"; - - await _testContext.RunOnDatabaseAsync(async db => - { - await db.EnsureEmptyCollectionAsync(); - }); - - var requestBody = new - { - atomic__operations = new[] - { - new - { - op = "add", - data = new - { - type = "textLanguages", - id = newLanguage.StringId, - attributes = new - { - isoCode = newLanguage.IsoCode - } - } - } - } - }; - - const string route = "/operations"; - - // Act - (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = - await _testContext.ExecutePostAtomicAsync(route, requestBody); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); - - responseDocument.Results.Should().HaveCount(1); - responseDocument.Results[0].SingleData.Should().NotBeNull(); - responseDocument.Results[0].SingleData.Type.Should().Be("textLanguages"); - responseDocument.Results[0].SingleData.Attributes["isoCode"].Should().Be(newLanguage.IsoCode); - responseDocument.Results[0].SingleData.Attributes.Should().NotContainKey("concurrencyToken"); - responseDocument.Results[0].SingleData.Relationships.Should().BeNull(); - - await _testContext.RunOnDatabaseAsync(async db => - { - TextLanguage languageInDatabase = await db.GetCollection().AsQueryable().FirstWithIdAsync(newLanguage.Id); - - languageInDatabase.IsoCode.Should().Be(newLanguage.IsoCode); - }); - } - - [Fact] - public async Task Can_create_resource_with_client_generated_string_ID_having_no_side_effects() - { - // Arrange - MusicTrack newTrack = _fakers.MusicTrack.Generate(); - newTrack.Id = "5ffcc0d1d69a27c92b8c62dd"; - - await _testContext.RunOnDatabaseAsync(async db => - { - await db.EnsureEmptyCollectionAsync(); - }); - - var requestBody = new - { - atomic__operations = new[] - { - new - { - op = "add", - data = new - { - type = "musicTracks", - id = newTrack.StringId, - attributes = new - { - title = newTrack.Title, - lengthInSeconds = newTrack.LengthInSeconds - } - } - } - } - }; - - const string route = "/operations"; - - // Act - (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); - - responseDocument.Should().BeEmpty(); - - await _testContext.RunOnDatabaseAsync(async db => - { - MusicTrack trackInDatabase = await db.GetCollection().AsQueryable().FirstWithIdAsync(newTrack.Id); - - trackInDatabase.Title.Should().Be(newTrack.Title); - trackInDatabase.LengthInSeconds.Should().BeApproximately(newTrack.LengthInSeconds); - }); - } - - [Fact] - public async Task Cannot_create_resource_for_existing_client_generated_ID() - { - // Arrange - TextLanguage existingLanguage = _fakers.TextLanguage.Generate(); - - TextLanguage languageToCreate = _fakers.TextLanguage.Generate(); - - await _testContext.RunOnDatabaseAsync(async db => - { - await db.GetCollection().InsertOneAsync(existingLanguage); - languageToCreate.Id = existingLanguage.Id; - }); - - var requestBody = new - { - atomic__operations = new[] - { - new - { - op = "add", - data = new - { - type = "textLanguages", - id = languageToCreate.StringId, - attributes = new - { - isoCode = languageToCreate.IsoCode - } - } - } - } - }; - - const string route = "/operations"; - - // Act - (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.Conflict); - - responseDocument.Errors.Should().HaveCount(1); - - Error error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.Conflict); - error.Title.Should().Be("Another resource with the specified ID already exists."); - error.Detail.Should().Be($"Another resource of type 'textLanguages' with ID '{languageToCreate.StringId}' already exists."); - error.Source.Pointer.Should().Be("/atomic:operations[0]"); - } - } -} diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithToManyRelationshipTests.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithToManyRelationshipTests.cs deleted file mode 100644 index 177497d..0000000 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithToManyRelationshipTests.cs +++ /dev/null @@ -1,92 +0,0 @@ -using System.Collections.Generic; -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using FluentAssertions; -using JsonApiDotNetCore.Serialization.Objects; -using JsonApiDotNetCoreMongoDbExampleTests.TestBuildingBlocks; -using Xunit; - -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Creating -{ - [Collection("AtomicOperationsFixture")] - public sealed class AtomicCreateResourceWithToManyRelationshipTests - { - private readonly IntegrationTestContext _testContext; - private readonly OperationsFakers _fakers = new OperationsFakers(); - - public AtomicCreateResourceWithToManyRelationshipTests(AtomicOperationsFixture fixture) - { - _testContext = fixture.TestContext; - - fixture.TestContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); - } - - [Fact] - public async Task Cannot_create_HasMany_relationship() - { - // Arrange - List existingPerformers = _fakers.Performer.Generate(2); - string newTitle = _fakers.MusicTrack.Generate().Title; - - await _testContext.RunOnDatabaseAsync(async db => - { - await db.GetCollection().InsertManyAsync(existingPerformers); - }); - - var requestBody = new - { - atomic__operations = new[] - { - new - { - op = "add", - data = new - { - type = "musicTracks", - attributes = new - { - title = newTitle - }, - relationships = new - { - performers = new - { - data = new[] - { - new - { - type = "performers", - id = existingPerformers[0].StringId - }, - new - { - type = "performers", - id = existingPerformers[1].StringId - } - } - } - } - } - } - } - }; - - const string route = "/operations"; - - // Act - (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); - - responseDocument.Errors.Should().HaveCount(1); - - Error error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.BadRequest); - error.Title.Should().Be("Relationships are not supported when using MongoDB."); - error.Detail.Should().BeNull(); - error.Source.Pointer.Should().Be("/atomic:operations[0]"); - } - } -} diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithToOneRelationshipTests.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithToOneRelationshipTests.cs deleted file mode 100644 index 1c28d6f..0000000 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Creating/AtomicCreateResourceWithToOneRelationshipTests.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using FluentAssertions; -using JsonApiDotNetCore.Serialization.Objects; -using JsonApiDotNetCoreMongoDbExampleTests.TestBuildingBlocks; -using Xunit; - -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Creating -{ - [Collection("AtomicOperationsFixture")] - public sealed class AtomicCreateResourceWithToOneRelationshipTests - { - private readonly IntegrationTestContext _testContext; - private readonly OperationsFakers _fakers = new OperationsFakers(); - - public AtomicCreateResourceWithToOneRelationshipTests(AtomicOperationsFixture fixture) - { - _testContext = fixture.TestContext; - - fixture.TestContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); - } - - [Fact] - public async Task Cannot_create_relationship() - { - // Arrange - MusicTrack existingTrack = _fakers.MusicTrack.Generate(); - - await _testContext.RunOnDatabaseAsync(async db => - { - await db.GetCollection().InsertOneAsync(existingTrack); - }); - - var requestBody = new - { - atomic__operations = new[] - { - new - { - op = "add", - data = new - { - type = "lyrics", - relationships = new - { - track = new - { - data = new - { - type = "musicTracks", - id = existingTrack.StringId - } - } - } - } - } - } - }; - - const string route = "/operations"; - - // Act - (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); - - responseDocument.Errors.Should().HaveCount(1); - - Error error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.BadRequest); - error.Title.Should().Be("Relationships are not supported when using MongoDB."); - error.Detail.Should().BeNull(); - error.Source.Pointer.Should().Be("/atomic:operations[0]"); - } - } -} diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Deleting/AtomicDeleteResourceTests.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Deleting/AtomicDeleteResourceTests.cs deleted file mode 100644 index 9865896..0000000 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Deleting/AtomicDeleteResourceTests.cs +++ /dev/null @@ -1,165 +0,0 @@ -using System.Collections.Generic; -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using FluentAssertions; -using JsonApiDotNetCore.Serialization.Objects; -using JsonApiDotNetCoreMongoDbExampleTests.TestBuildingBlocks; -using MongoDB.Driver; -using Xunit; - -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Deleting -{ - [Collection("AtomicOperationsFixture")] - public sealed class AtomicDeleteResourceTests - { - private readonly IntegrationTestContext _testContext; - private readonly OperationsFakers _fakers = new OperationsFakers(); - - public AtomicDeleteResourceTests(AtomicOperationsFixture fixture) - { - _testContext = fixture.TestContext; - - fixture.TestContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); - } - - [Fact] - public async Task Can_delete_existing_resource() - { - // Arrange - Performer existingPerformer = _fakers.Performer.Generate(); - - await _testContext.RunOnDatabaseAsync(async db => - { - await db.GetCollection().InsertOneAsync(existingPerformer); - }); - - var requestBody = new - { - atomic__operations = new[] - { - new - { - op = "remove", - @ref = new - { - type = "performers", - id = existingPerformer.StringId - } - } - } - }; - - const string route = "/operations"; - - // Act - (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); - - responseDocument.Should().BeEmpty(); - - await _testContext.RunOnDatabaseAsync(async db => - { - Performer performerInDatabase = await db.GetCollection().AsQueryable().FirstWithIdOrDefaultAsync(existingPerformer.Id); - - performerInDatabase.Should().BeNull(); - }); - } - - [Fact] - public async Task Can_delete_existing_resources() - { - // Arrange - const int elementCount = 5; - - List existingTracks = _fakers.MusicTrack.Generate(elementCount); - - await _testContext.RunOnDatabaseAsync(async db => - { - await db.ClearCollectionAsync(); - await db.GetCollection().InsertManyAsync(existingTracks); - }); - - var operationElements = new List(elementCount); - - for (int index = 0; index < elementCount; index++) - { - operationElements.Add(new - { - op = "remove", - @ref = new - { - type = "musicTracks", - id = existingTracks[index].StringId - } - }); - } - - var requestBody = new - { - atomic__operations = operationElements - }; - - const string route = "/operations"; - - // Act - (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); - - responseDocument.Should().BeEmpty(); - - await _testContext.RunOnDatabaseAsync(async db => - { - List tracksInDatabase = await db.GetCollection().AsQueryable().ToListAsync(); - - tracksInDatabase.Should().BeEmpty(); - }); - } - - [Fact] - public async Task Cannot_delete_resource_for_unknown_ID() - { - // Arrange - await _testContext.RunOnDatabaseAsync(async db => - { - await db.EnsureEmptyCollectionAsync(); - }); - - var requestBody = new - { - atomic__operations = new[] - { - new - { - op = "remove", - @ref = new - { - type = "performers", - id = "ffffffffffffffffffffffff" - } - } - } - }; - - const string route = "/operations"; - - // Act - (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.NotFound); - - responseDocument.Errors.Should().HaveCount(1); - - Error error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.NotFound); - error.Title.Should().Be("The requested resource does not exist."); - error.Detail.Should().Be("Resource of type 'performers' with ID 'ffffffffffffffffffffffff' does not exist."); - error.Source.Pointer.Should().Be("/atomic:operations[0]"); - } - } -} diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/LocalIds/AtomicLocalIdTests.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/LocalIds/AtomicLocalIdTests.cs deleted file mode 100644 index 884ae8d..0000000 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/LocalIds/AtomicLocalIdTests.cs +++ /dev/null @@ -1,172 +0,0 @@ -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using FluentAssertions; -using JsonApiDotNetCore.Serialization.Objects; -using JsonApiDotNetCoreMongoDbExampleTests.TestBuildingBlocks; -using MongoDB.Driver; -using Xunit; - -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.LocalIds -{ - [Collection("AtomicOperationsFixture")] - public sealed class AtomicLocalIdTests - { - private readonly IntegrationTestContext _testContext; - private readonly OperationsFakers _fakers = new OperationsFakers(); - - public AtomicLocalIdTests(AtomicOperationsFixture fixture) - { - _testContext = fixture.TestContext; - - fixture.TestContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); - } - - [Fact] - public async Task Can_update_resource_using_local_ID() - { - // Arrange - string newTrackTitle = _fakers.MusicTrack.Generate().Title; - string newTrackGenre = _fakers.MusicTrack.Generate().Genre; - - await _testContext.RunOnDatabaseAsync(async db => - { - await db.EnsureEmptyCollectionAsync(); - }); - - const string trackLocalId = "track-1"; - - var requestBody = new - { - atomic__operations = new object[] - { - new - { - op = "add", - data = new - { - type = "musicTracks", - lid = trackLocalId, - attributes = new - { - title = newTrackTitle - } - } - }, - new - { - op = "update", - data = new - { - type = "musicTracks", - lid = trackLocalId, - attributes = new - { - genre = newTrackGenre - } - } - } - } - }; - - const string route = "/operations"; - - // Act - (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = - await _testContext.ExecutePostAtomicAsync(route, requestBody); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); - - responseDocument.Results.Should().HaveCount(2); - - responseDocument.Results[0].SingleData.Should().NotBeNull(); - responseDocument.Results[0].SingleData.Type.Should().Be("musicTracks"); - responseDocument.Results[0].SingleData.Lid.Should().BeNull(); - responseDocument.Results[0].SingleData.Attributes["title"].Should().Be(newTrackTitle); - responseDocument.Results[0].SingleData.Attributes["genre"].Should().BeNull(); - - responseDocument.Results[1].Data.Should().BeNull(); - - string newTrackId = responseDocument.Results[0].SingleData.Id; - - await _testContext.RunOnDatabaseAsync(async db => - { - MusicTrack trackInDatabase = await db.GetCollection().AsQueryable().FirstWithIdAsync(newTrackId); - - trackInDatabase.Title.Should().Be(newTrackTitle); - trackInDatabase.Genre.Should().Be(newTrackGenre); - }); - } - - [Fact] - public async Task Can_delete_resource_using_local_ID() - { - // Arrange - string newTrackTitle = _fakers.MusicTrack.Generate().Title; - - await _testContext.RunOnDatabaseAsync(async db => - { - await db.EnsureEmptyCollectionAsync(); - }); - - const string trackLocalId = "track-1"; - - var requestBody = new - { - atomic__operations = new object[] - { - new - { - op = "add", - data = new - { - type = "musicTracks", - lid = trackLocalId, - attributes = new - { - title = newTrackTitle - } - } - }, - new - { - op = "remove", - @ref = new - { - type = "musicTracks", - lid = trackLocalId - } - } - } - }; - - const string route = "/operations"; - - // Act - (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = - await _testContext.ExecutePostAtomicAsync(route, requestBody); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); - - responseDocument.Results.Should().HaveCount(2); - - responseDocument.Results[0].SingleData.Should().NotBeNull(); - responseDocument.Results[0].SingleData.Type.Should().Be("musicTracks"); - responseDocument.Results[0].SingleData.Lid.Should().BeNull(); - responseDocument.Results[0].SingleData.Attributes["title"].Should().Be(newTrackTitle); - - responseDocument.Results[1].Data.Should().BeNull(); - - string newTrackId = responseDocument.Results[0].SingleData.Id; - - await _testContext.RunOnDatabaseAsync(async db => - { - MusicTrack trackInDatabase = await db.GetCollection().AsQueryable().FirstWithIdOrDefaultAsync(newTrackId); - - trackInDatabase.Should().BeNull(); - }); - } - } -} diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Lyric.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Lyric.cs deleted file mode 100644 index c9a23f4..0000000 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Lyric.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System; -using JetBrains.Annotations; -using JsonApiDotNetCore.MongoDb.Resources; -using JsonApiDotNetCore.Resources.Annotations; -using MongoDB.Bson.Serialization.Attributes; - -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations -{ - [UsedImplicitly(ImplicitUseTargetFlags.Members)] - public sealed class Lyric : MongoIdentifiable - { - [Attr] - public string Format { get; set; } - - [Attr] - public string Text { get; set; } - - [Attr(Capabilities = AttrCapabilities.None)] - public DateTimeOffset CreatedAt { get; set; } - - [HasOne] - [BsonIgnore] - public TextLanguage Language { get; set; } - - [HasOne] - [BsonIgnore] - public MusicTrack Track { get; set; } - } -} diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Meta/AtomicResourceMetaTests.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Meta/AtomicResourceMetaTests.cs deleted file mode 100644 index 48c3a2d..0000000 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Meta/AtomicResourceMetaTests.cs +++ /dev/null @@ -1,132 +0,0 @@ -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using FluentAssertions; -using FluentAssertions.Extensions; -using JsonApiDotNetCore.Serialization.Objects; -using JsonApiDotNetCoreMongoDbExampleTests.TestBuildingBlocks; -using Xunit; - -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Meta -{ - [Collection("AtomicOperationsFixture")] - public sealed class AtomicResourceMetaTests - { - private readonly IntegrationTestContext _testContext; - private readonly OperationsFakers _fakers = new OperationsFakers(); - - public AtomicResourceMetaTests(AtomicOperationsFixture fixture) - { - _testContext = fixture.TestContext; - - fixture.TestContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); - } - - [Fact] - public async Task Returns_resource_meta_in_create_resource_with_side_effects() - { - // Arrange - string newTitle1 = _fakers.MusicTrack.Generate().Title; - string newTitle2 = _fakers.MusicTrack.Generate().Title; - - await _testContext.RunOnDatabaseAsync(async db => - { - await db.EnsureEmptyCollectionAsync(); - }); - - var requestBody = new - { - atomic__operations = new[] - { - new - { - op = "add", - data = new - { - type = "musicTracks", - attributes = new - { - title = newTitle1, - releasedAt = 1.January(2018) - } - } - }, - new - { - op = "add", - data = new - { - type = "musicTracks", - attributes = new - { - title = newTitle2, - releasedAt = 23.August(1994) - } - } - } - } - }; - - const string route = "/operations"; - - // Act - (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = - await _testContext.ExecutePostAtomicAsync(route, requestBody); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); - - responseDocument.Results.Should().HaveCount(2); - - responseDocument.Results[0].SingleData.Meta.Should().HaveCount(1); - responseDocument.Results[0].SingleData.Meta["Copyright"].Should().Be("(C) 2018. All rights reserved."); - - responseDocument.Results[1].SingleData.Meta.Should().HaveCount(1); - responseDocument.Results[1].SingleData.Meta["Copyright"].Should().Be("(C) 1994. All rights reserved."); - } - - [Fact] - public async Task Returns_resource_meta_in_update_resource_with_side_effects() - { - // Arrange - TextLanguage existingLanguage = _fakers.TextLanguage.Generate(); - - await _testContext.RunOnDatabaseAsync(async db => - { - await db.GetCollection().InsertOneAsync(existingLanguage); - }); - - var requestBody = new - { - atomic__operations = new[] - { - new - { - op = "update", - data = new - { - type = "textLanguages", - id = existingLanguage.StringId, - attributes = new - { - } - } - } - } - }; - - const string route = "/operations"; - - // Act - (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = - await _testContext.ExecutePostAtomicAsync(route, requestBody); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); - - responseDocument.Results.Should().HaveCount(1); - responseDocument.Results[0].SingleData.Meta.Should().HaveCount(1); - responseDocument.Results[0].SingleData.Meta["Notice"].Should().Be(TextLanguageMetaDefinition.NoticeText); - } - } -} diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Meta/MusicTrackMetaDefinition.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Meta/MusicTrackMetaDefinition.cs deleted file mode 100644 index 13dfb4d..0000000 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Meta/MusicTrackMetaDefinition.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Collections.Generic; -using JetBrains.Annotations; -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Resources; - -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Meta -{ - [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] - public sealed class MusicTrackMetaDefinition : JsonApiResourceDefinition - { - public MusicTrackMetaDefinition(IResourceGraph resourceGraph) - : base(resourceGraph) - { - } - - public override IDictionary GetMeta(MusicTrack resource) - { - return new Dictionary - { - ["Copyright"] = $"(C) {resource.ReleasedAt.Year}. All rights reserved." - }; - } - } -} diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Meta/TextLanguageMetaDefinition.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Meta/TextLanguageMetaDefinition.cs deleted file mode 100644 index 566ad3f..0000000 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Meta/TextLanguageMetaDefinition.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Collections.Generic; -using JetBrains.Annotations; -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Resources; - -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Meta -{ - [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] - public sealed class TextLanguageMetaDefinition : JsonApiResourceDefinition - { - internal const string NoticeText = "See https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes for ISO 639-1 language codes."; - - public TextLanguageMetaDefinition(IResourceGraph resourceGraph) - : base(resourceGraph) - { - } - - public override IDictionary GetMeta(TextLanguage resource) - { - return new Dictionary - { - ["Notice"] = NoticeText - }; - } - } -} diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Mixed/MaximumOperationsPerRequestTests.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Mixed/MaximumOperationsPerRequestTests.cs deleted file mode 100644 index b0bc94c..0000000 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Mixed/MaximumOperationsPerRequestTests.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System.Collections.Generic; -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Serialization.Objects; -using JsonApiDotNetCoreMongoDbExampleTests.TestBuildingBlocks; -using Microsoft.Extensions.DependencyInjection; -using Xunit; - -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Mixed -{ - [Collection("AtomicOperationsFixture")] - public sealed class MaximumOperationsPerRequestTests - { - private readonly IntegrationTestContext _testContext; - - public MaximumOperationsPerRequestTests(AtomicOperationsFixture fixture) - { - _testContext = fixture.TestContext; - - fixture.TestContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); - } - - [Fact] - public async Task Can_process_high_number_of_operations_when_unconstrained() - { - // Arrange - var options = (JsonApiOptions)_testContext.Factory.Services.GetRequiredService(); - options.MaximumOperationsPerRequest = null; - - await _testContext.RunOnDatabaseAsync(async db => - { - await db.EnsureEmptyCollectionAsync(); - }); - - const int elementCount = 100; - - var operationElements = new List(elementCount); - - for (int index = 0; index < elementCount; index++) - { - operationElements.Add(new - { - op = "add", - data = new - { - type = "performers", - attributes = new - { - } - } - }); - } - - var requestBody = new - { - atomic__operations = operationElements - }; - - const string route = "/operations"; - - // Act - (HttpResponseMessage httpResponse, _) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); - } - } -} diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/MusicTrack.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/MusicTrack.cs deleted file mode 100644 index 25a1b7f..0000000 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/MusicTrack.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using JetBrains.Annotations; -using JsonApiDotNetCore.MongoDb.Resources; -using JsonApiDotNetCore.Resources.Annotations; -using MongoDB.Bson.Serialization.Attributes; - -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations -{ - [UsedImplicitly(ImplicitUseTargetFlags.Members)] - public sealed class MusicTrack : MongoIdentifiable - { - [RegularExpression(@"(?im)^[{(]?[0-9A-F]{8}[-]?(?:[0-9A-F]{4}[-]?){3}[0-9A-F]{12}[)}]?$")] - public override string Id { get; set; } - - [Attr] - [Required] - public string Title { get; set; } - - [Attr] - [Range(1, 24 * 60)] - public decimal? LengthInSeconds { get; set; } - - [Attr] - public string Genre { get; set; } - - [Attr] - public DateTimeOffset ReleasedAt { get; set; } - - [HasOne] - [BsonIgnore] - public Lyric Lyric { get; set; } - - [HasOne] - [BsonIgnore] - public RecordCompany OwnedBy { get; set; } - - [HasMany] - [BsonIgnore] - public IList Performers { get; set; } - } -} diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/MusicTracksController.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/MusicTracksController.cs deleted file mode 100644 index feb52e8..0000000 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/MusicTracksController.cs +++ /dev/null @@ -1,15 +0,0 @@ -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Services; -using Microsoft.Extensions.Logging; - -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations -{ - public sealed class MusicTracksController : JsonApiController - { - public MusicTracksController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) - : base(options, loggerFactory, resourceService) - { - } - } -} diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/OperationsFakers.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/OperationsFakers.cs deleted file mode 100644 index c750587..0000000 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/OperationsFakers.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using Bogus; -using JsonApiDotNetCoreMongoDbExampleTests.TestBuildingBlocks; - -// @formatter:wrap_chained_method_calls chop_always -// @formatter:keep_existing_linebreaks true - -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations -{ - internal sealed class OperationsFakers : FakerContainer - { - private static readonly Lazy> LazyLanguageIsoCodes = - new Lazy>(() => CultureInfo - .GetCultures(CultureTypes.NeutralCultures) - .Where(culture => !string.IsNullOrEmpty(culture.Name)) - .Select(culture => culture.Name) - .ToArray()); - - private readonly Lazy> _lazyPlaylistFaker = new Lazy>(() => - new Faker() - .UseSeed(GetFakerSeed()) - .RuleFor(playlist => playlist.Name, faker => faker.Lorem.Sentence())); - - private readonly Lazy> _lazyMusicTrackFaker = new Lazy>(() => - new Faker() - .UseSeed(GetFakerSeed()) - .RuleFor(musicTrack => musicTrack.Title, faker => faker.Lorem.Word()) - .RuleFor(musicTrack => musicTrack.LengthInSeconds, faker => faker.Random.Decimal(3 * 60, 5 * 60)) - .RuleFor(musicTrack => musicTrack.Genre, faker => faker.Lorem.Word()) - .RuleFor(musicTrack => musicTrack.ReleasedAt, faker => faker.Date.PastOffset())); - - private readonly Lazy> _lazyLyricFaker = new Lazy>(() => - new Faker() - .UseSeed(GetFakerSeed()) - .RuleFor(lyric => lyric.Text, faker => faker.Lorem.Text()) - .RuleFor(lyric => lyric.Format, "LRC")); - - private readonly Lazy> _lazyTextLanguageFaker = new Lazy>(() => - new Faker() - .UseSeed(GetFakerSeed()) - .RuleFor(textLanguage => textLanguage.IsoCode, faker => faker.PickRandom(LazyLanguageIsoCodes.Value))); - - private readonly Lazy> _lazyPerformerFaker = new Lazy>(() => - new Faker() - .UseSeed(GetFakerSeed()) - .RuleFor(performer => performer.ArtistName, faker => faker.Name.FullName()) - .RuleFor(performer => performer.BornAt, faker => faker.Date.PastOffset())); - - private readonly Lazy> _lazyRecordCompanyFaker = new Lazy>(() => - new Faker() - .UseSeed(GetFakerSeed()) - .RuleFor(recordCompany => recordCompany.Name, faker => faker.Company.CompanyName()) - .RuleFor(recordCompany => recordCompany.CountryOfResidence, faker => faker.Address.Country())); - - public Faker Playlist => _lazyPlaylistFaker.Value; - public Faker MusicTrack => _lazyMusicTrackFaker.Value; - public Faker Lyric => _lazyLyricFaker.Value; - public Faker TextLanguage => _lazyTextLanguageFaker.Value; - public Faker Performer => _lazyPerformerFaker.Value; - public Faker RecordCompany => _lazyRecordCompanyFaker.Value; - } -} diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Performer.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Performer.cs deleted file mode 100644 index 5d15d9c..0000000 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Performer.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using JetBrains.Annotations; -using JsonApiDotNetCore.MongoDb.Resources; -using JsonApiDotNetCore.Resources.Annotations; - -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations -{ - [UsedImplicitly(ImplicitUseTargetFlags.Members)] - public sealed class Performer : MongoIdentifiable - { - [Attr] - public string ArtistName { get; set; } - - [Attr] - public DateTimeOffset BornAt { get; set; } - } -} diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Playlist.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Playlist.cs deleted file mode 100644 index e76652d..0000000 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Playlist.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using JetBrains.Annotations; -using JsonApiDotNetCore.MongoDb.Resources; -using JsonApiDotNetCore.Resources.Annotations; -using MongoDB.Bson.Serialization.Attributes; - -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations -{ - [UsedImplicitly(ImplicitUseTargetFlags.Members)] - public sealed class Playlist : MongoIdentifiable - { - [Attr] - [Required] - public string Name { get; set; } - - [Attr] - [BsonIgnore] - public bool IsArchived => false; - - [HasManyThrough(nameof(PlaylistMusicTracks))] - [BsonIgnore] - public IList Tracks { get; set; } - - [BsonIgnore] - public IList PlaylistMusicTracks { get; set; } - } -} diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/PlaylistMusicTrack.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/PlaylistMusicTrack.cs deleted file mode 100644 index 8f6ef90..0000000 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/PlaylistMusicTrack.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using JetBrains.Annotations; - -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations -{ - [UsedImplicitly(ImplicitUseTargetFlags.Members)] - public sealed class PlaylistMusicTrack - { - public long PlaylistId { get; set; } - public Playlist Playlist { get; set; } - - public Guid MusicTrackId { get; set; } - public MusicTrack MusicTrack { get; set; } - } -} diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/RecordCompany.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/RecordCompany.cs deleted file mode 100644 index 0295252..0000000 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/RecordCompany.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Collections.Generic; -using JetBrains.Annotations; -using JsonApiDotNetCore.MongoDb.Resources; -using JsonApiDotNetCore.Resources.Annotations; -using MongoDB.Bson.Serialization.Attributes; - -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations -{ - [UsedImplicitly(ImplicitUseTargetFlags.Members)] - public sealed class RecordCompany : MongoIdentifiable - { - [Attr] - public string Name { get; set; } - - [Attr] - public string CountryOfResidence { get; set; } - - [HasMany] - [BsonIgnore] - public IList Tracks { get; set; } - - [HasOne] - [BsonIgnore] - public RecordCompany Parent { get; set; } - } -} diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/TextLanguage.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/TextLanguage.cs deleted file mode 100644 index 84d1288..0000000 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/TextLanguage.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; -using JetBrains.Annotations; -using JsonApiDotNetCore.MongoDb.Resources; -using JsonApiDotNetCore.Resources.Annotations; -using MongoDB.Bson.Serialization.Attributes; - -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations -{ - [UsedImplicitly(ImplicitUseTargetFlags.Members)] - public sealed class TextLanguage : MongoIdentifiable - { - [Attr] - public string IsoCode { get; set; } - - [Attr(Capabilities = AttrCapabilities.None)] - [BsonIgnore] - public Guid ConcurrencyToken - { - get => Guid.NewGuid(); - set => _ = value; - } - - [HasMany] - [BsonIgnore] - public ICollection Lyrics { get; set; } - } -} diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Transactions/AtomicRollbackTests.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Transactions/AtomicRollbackTests.cs deleted file mode 100644 index 34b8954..0000000 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Transactions/AtomicRollbackTests.cs +++ /dev/null @@ -1,220 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using FluentAssertions; -using JsonApiDotNetCore.Serialization.Objects; -using JsonApiDotNetCoreMongoDbExampleTests.TestBuildingBlocks; -using MongoDB.Driver; -using Xunit; - -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Transactions -{ - [Collection("AtomicOperationsFixture")] - public sealed class AtomicRollbackTests - { - private readonly IntegrationTestContext _testContext; - private readonly OperationsFakers _fakers = new OperationsFakers(); - - public AtomicRollbackTests(AtomicOperationsFixture fixture) - { - _testContext = fixture.TestContext; - - fixture.TestContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); - } - - [Fact] - public async Task Can_rollback_created_resource_on_error() - { - // Arrange - string newArtistName = _fakers.Performer.Generate().ArtistName; - DateTimeOffset newBornAt = _fakers.Performer.Generate().BornAt; - - await _testContext.RunOnDatabaseAsync(async db => - { - await db.EnsureEmptyCollectionAsync(); - }); - - var requestBody = new - { - atomic__operations = new object[] - { - new - { - op = "add", - id = "507f191e810c19729de860ea", - data = new - { - type = "performers", - attributes = new - { - artistName = newArtistName, - bornAt = newBornAt - } - } - }, - new - { - op = "remove", - @ref = new - { - type = "performers", - id = "ffffffffffffffffffffffff" - } - } - } - }; - - const string route = "/operations"; - - // Act - (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.NotFound); - - responseDocument.Errors.Should().HaveCount(1); - - Error error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.NotFound); - error.Title.Should().Be("The requested resource does not exist."); - error.Detail.Should().Be("Resource of type 'performers' with ID 'ffffffffffffffffffffffff' does not exist."); - error.Source.Pointer.Should().Be("/atomic:operations[1]"); - - await _testContext.RunOnDatabaseAsync(async db => - { - List performersInDatabase = await db.GetCollection().AsQueryable().ToListAsync(); - - performersInDatabase.Should().BeEmpty(); - }); - } - - [Fact] - public async Task Can_rollback_updated_resource_on_error() - { - // Arrange - Performer existingPerformer = _fakers.Performer.Generate(); - - string newArtistName = _fakers.Performer.Generate().ArtistName; - - await _testContext.RunOnDatabaseAsync(async db => - { - await db.GetCollection().InsertOneAsync(existingPerformer); - }); - - var requestBody = new - { - atomic__operations = new object[] - { - new - { - op = "update", - data = new - { - type = "performers", - id = existingPerformer.StringId, - attributes = new - { - artistName = newArtistName - } - } - }, - new - { - op = "remove", - @ref = new - { - type = "performers", - id = "ffffffffffffffffffffffff" - } - } - } - }; - - const string route = "/operations"; - - // Act - (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.NotFound); - - responseDocument.Errors.Should().HaveCount(1); - - Error error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.NotFound); - error.Title.Should().Be("The requested resource does not exist."); - error.Detail.Should().Be("Resource of type 'performers' with ID 'ffffffffffffffffffffffff' does not exist."); - error.Source.Pointer.Should().Be("/atomic:operations[1]"); - - await _testContext.RunOnDatabaseAsync(async db => - { - Performer performerInDatabase = await db.GetCollection().AsQueryable().FirstWithIdAsync(existingPerformer.Id); - - performerInDatabase.ArtistName.Should().Be(existingPerformer.ArtistName); - }); - } - - [Fact] - public async Task Can_rollback_deleted_resource_on_error() - { - // Arrange - Performer existingPerformer = _fakers.Performer.Generate(); - - await _testContext.RunOnDatabaseAsync(async db => - { - await db.ClearCollectionAsync(); - await db.GetCollection().InsertOneAsync(existingPerformer); - }); - - var requestBody = new - { - atomic__operations = new object[] - { - new - { - op = "remove", - @ref = new - { - type = "performers", - id = existingPerformer.StringId - } - }, - new - { - op = "remove", - @ref = new - { - type = "performers", - id = "ffffffffffffffffffffffff" - } - } - } - }; - - const string route = "/operations"; - - // Act - (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.NotFound); - - responseDocument.Errors.Should().HaveCount(1); - - Error error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.NotFound); - error.Title.Should().Be("The requested resource does not exist."); - error.Detail.Should().Be("Resource of type 'performers' with ID 'ffffffffffffffffffffffff' does not exist."); - error.Source.Pointer.Should().Be("/atomic:operations[1]"); - - await _testContext.RunOnDatabaseAsync(async db => - { - List performersInDatabase = await db.GetCollection().AsQueryable().ToListAsync(); - - performersInDatabase.Should().HaveCount(1); - }); - } - } -} diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Transactions/AtomicTransactionConsistencyTests.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Transactions/AtomicTransactionConsistencyTests.cs deleted file mode 100644 index f0b2c8c..0000000 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Transactions/AtomicTransactionConsistencyTests.cs +++ /dev/null @@ -1,149 +0,0 @@ -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using FluentAssertions; -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Serialization.Objects; -using JsonApiDotNetCoreMongoDbExampleTests.TestBuildingBlocks; -using Xunit; - -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Transactions -{ - public sealed class AtomicTransactionConsistencyTests : IClassFixture> - { - private readonly IntegrationTestContext _testContext; - - public AtomicTransactionConsistencyTests(IntegrationTestContext testContext) - { - _testContext = testContext; - - testContext.StartMongoDbInSingleNodeReplicaSetMode = true; - - testContext.ConfigureServicesAfterStartup(services => - { - services.AddControllersFromExampleProject(); - - services.AddResourceRepository(); - services.AddResourceRepository(); - services.AddResourceRepository(); - }); - } - - [Fact] - public async Task Cannot_use_non_transactional_repository() - { - // Arrange - var requestBody = new - { - atomic__operations = new object[] - { - new - { - op = "add", - data = new - { - type = "performers", - attributes = new - { - } - } - } - } - }; - - const string route = "/operations"; - - // Act - (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); - - responseDocument.Errors.Should().HaveCount(1); - - Error error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); - error.Title.Should().Be("Unsupported resource type in atomic:operations request."); - error.Detail.Should().Be("Operations on resources of type 'performers' cannot be used because transaction support is unavailable."); - error.Source.Pointer.Should().Be("/atomic:operations[0]"); - } - - [Fact] - public async Task Cannot_use_transactional_repository_without_active_transaction() - { - // Arrange - var requestBody = new - { - atomic__operations = new object[] - { - new - { - op = "add", - data = new - { - type = "musicTracks", - attributes = new - { - } - } - } - } - }; - - const string route = "/operations"; - - // Act - (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); - - responseDocument.Errors.Should().HaveCount(1); - - Error error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); - error.Title.Should().Be("Unsupported combination of resource types in atomic:operations request."); - error.Detail.Should().Be("All operations need to participate in a single shared transaction, which is not the case for this request."); - error.Source.Pointer.Should().Be("/atomic:operations[0]"); - } - - [Fact] - public async Task Cannot_use_distributed_transaction() - { - // Arrange - var requestBody = new - { - atomic__operations = new object[] - { - new - { - op = "add", - data = new - { - type = "lyrics", - attributes = new - { - } - } - } - } - }; - - const string route = "/operations"; - - // Act - (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.UnprocessableEntity); - - responseDocument.Errors.Should().HaveCount(1); - - Error error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.UnprocessableEntity); - error.Title.Should().Be("Unsupported combination of resource types in atomic:operations request."); - error.Detail.Should().Be("All operations need to participate in a single shared transaction, which is not the case for this request."); - error.Source.Pointer.Should().Be("/atomic:operations[0]"); - } - } -} diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Transactions/LyricRepository.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Transactions/LyricRepository.cs deleted file mode 100644 index 94c22e0..0000000 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Transactions/LyricRepository.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using JetBrains.Annotations; -using JsonApiDotNetCore.AtomicOperations; -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.MongoDb.AtomicOperations; -using JsonApiDotNetCore.MongoDb.Repositories; -using JsonApiDotNetCore.Queries; -using JsonApiDotNetCore.Resources; - -#pragma warning disable AV1008 // Class should not be static - -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Transactions -{ - internal static class ContainerTypeToHideLyricRepositoryFromAutoDiscovery - { - [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] - public sealed class LyricRepository : MongoRepository, IAsyncDisposable - { - private readonly IOperationsTransaction _transaction; - - public override string TransactionId => _transaction.TransactionId; - - public LyricRepository(IMongoDataAccess mongoDataAccess, ITargetedFields targetedFields, IResourceContextProvider resourceContextProvider, - IResourceFactory resourceFactory, IEnumerable constraintProviders) - : base(mongoDataAccess, targetedFields, resourceContextProvider, resourceFactory, constraintProviders) - { - IMongoDataAccess otherDataAccess = new MongoDataAccess(mongoDataAccess.MongoDatabase); - - var factory = new MongoTransactionFactory(otherDataAccess); - _transaction = factory.BeginTransactionAsync(CancellationToken.None).Result; - } - - public async ValueTask DisposeAsync() - { - await _transaction.DisposeAsync(); - } - } - } -} diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Transactions/MusicTrackRepository.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Transactions/MusicTrackRepository.cs deleted file mode 100644 index 5d8a105..0000000 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Transactions/MusicTrackRepository.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System.Collections.Generic; -using JetBrains.Annotations; -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.MongoDb.Repositories; -using JsonApiDotNetCore.Queries; -using JsonApiDotNetCore.Resources; - -#pragma warning disable AV1008 // Class should not be static - -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Transactions -{ - internal static class ContainerTypeToHideMusicTrackRepositoryFromAutoDiscovery - { - [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] - public sealed class MusicTrackRepository : MongoRepository - { - public override string TransactionId => null; - - public MusicTrackRepository(IMongoDataAccess mongoDataAccess, ITargetedFields targetedFields, IResourceContextProvider resourceContextProvider, - IResourceFactory resourceFactory, IEnumerable constraintProviders) - : base(mongoDataAccess, targetedFields, resourceContextProvider, resourceFactory, constraintProviders) - { - } - } - } -} diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Transactions/PerformerRepository.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Transactions/PerformerRepository.cs deleted file mode 100644 index 1987b12..0000000 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Transactions/PerformerRepository.cs +++ /dev/null @@ -1,72 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using JetBrains.Annotations; -using JsonApiDotNetCore.Queries; -using JsonApiDotNetCore.Queries.Expressions; -using JsonApiDotNetCore.Repositories; -using JsonApiDotNetCore.Resources; - -#pragma warning disable AV1008 // Class should not be static - -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Transactions -{ - internal static class ContainerTypeToHidePerformerRepositoryFromAutoDiscovery - { - [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] - public sealed class PerformerRepository : IResourceRepository - { - public Task> GetAsync(QueryLayer layer, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - public Task CountAsync(FilterExpression topFilter, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - public Task GetForCreateAsync(string id, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - public Task CreateAsync(Performer resourceFromRequest, Performer resourceForDatabase, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - public Task GetForUpdateAsync(QueryLayer queryLayer, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - public Task UpdateAsync(Performer resourceFromRequest, Performer resourceFromDatabase, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - public Task DeleteAsync(string id, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - public Task SetRelationshipAsync(Performer primaryResource, object secondaryResourceIds, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - public Task AddToToManyRelationshipAsync(string primaryId, ISet secondaryResourceIds, CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - - public Task RemoveFromToManyRelationshipAsync(Performer primaryResource, ISet secondaryResourceIds, - CancellationToken cancellationToken) - { - throw new NotImplementedException(); - } - } - } -} diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicAddToToManyRelationshipTests.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicAddToToManyRelationshipTests.cs deleted file mode 100644 index aa52ed3..0000000 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicAddToToManyRelationshipTests.cs +++ /dev/null @@ -1,136 +0,0 @@ -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using FluentAssertions; -using JsonApiDotNetCore.Serialization.Objects; -using JsonApiDotNetCoreMongoDbExampleTests.TestBuildingBlocks; -using Xunit; - -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Updating.Relationships -{ - [Collection("AtomicOperationsFixture")] - public sealed class AtomicAddToToManyRelationshipTests - { - private readonly IntegrationTestContext _testContext; - private readonly OperationsFakers _fakers = new OperationsFakers(); - - public AtomicAddToToManyRelationshipTests(AtomicOperationsFixture fixture) - { - _testContext = fixture.TestContext; - - fixture.TestContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); - } - - [Fact] - public async Task Cannot_add_to_HasMany_relationship() - { - // Arrange - MusicTrack existingTrack = _fakers.MusicTrack.Generate(); - - Performer existingPerformer = _fakers.Performer.Generate(); - - await _testContext.RunOnDatabaseAsync(async db => - { - await db.GetCollection().InsertOneAsync(existingTrack); - await db.GetCollection().InsertOneAsync(existingPerformer); - }); - - var requestBody = new - { - atomic__operations = new[] - { - new - { - op = "add", - @ref = new - { - type = "musicTracks", - id = existingTrack.StringId, - relationship = "performers" - }, - data = new[] - { - new - { - type = "performers", - id = existingPerformer.StringId - } - } - } - } - }; - - const string route = "/operations"; - - // Act - (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); - - responseDocument.Errors.Should().HaveCount(1); - - Error error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.BadRequest); - error.Title.Should().Be("Relationships are not supported when using MongoDB."); - error.Detail.Should().BeNull(); - error.Source.Pointer.Should().Be("/atomic:operations[0]"); - } - - [Fact] - public async Task Cannot_add_to_HasManyThrough_relationship() - { - // Arrange - Playlist existingPlaylist = _fakers.Playlist.Generate(); - - MusicTrack existingTrack = _fakers.MusicTrack.Generate(); - - await _testContext.RunOnDatabaseAsync(async db => - { - await db.GetCollection().InsertOneAsync(existingPlaylist); - await db.GetCollection().InsertOneAsync(existingTrack); - }); - - var requestBody = new - { - atomic__operations = new[] - { - new - { - op = "add", - @ref = new - { - type = "playlists", - id = existingPlaylist.StringId, - relationship = "tracks" - }, - data = new[] - { - new - { - type = "musicTracks", - id = existingTrack.StringId - } - } - } - } - }; - - const string route = "/operations"; - - // Act - (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); - - responseDocument.Errors.Should().HaveCount(1); - - Error error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.BadRequest); - error.Title.Should().Be("Relationships are not supported when using MongoDB."); - error.Detail.Should().BeNull(); - error.Source.Pointer.Should().Be("/atomic:operations[0]"); - } - } -} diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicRemoveFromToManyRelationshipTests.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicRemoveFromToManyRelationshipTests.cs deleted file mode 100644 index 6bf79ad..0000000 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicRemoveFromToManyRelationshipTests.cs +++ /dev/null @@ -1,138 +0,0 @@ -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using FluentAssertions; -using JsonApiDotNetCore.Serialization.Objects; -using JsonApiDotNetCoreMongoDbExampleTests.TestBuildingBlocks; -using Xunit; - -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Updating.Relationships -{ - [Collection("AtomicOperationsFixture")] - public sealed class AtomicRemoveFromToManyRelationshipTests - { - private readonly IntegrationTestContext _testContext; - private readonly OperationsFakers _fakers = new OperationsFakers(); - - public AtomicRemoveFromToManyRelationshipTests(AtomicOperationsFixture fixture) - { - _testContext = fixture.TestContext; - - fixture.TestContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); - } - - [Fact] - public async Task Cannot_remove_from_HasMany_relationship() - { - // Arrange - MusicTrack existingTrack = _fakers.MusicTrack.Generate(); - existingTrack.Performers = _fakers.Performer.Generate(1); - - await _testContext.RunOnDatabaseAsync(async db => - { - await db.GetCollection().InsertOneAsync(existingTrack.Performers[0]); - await db.GetCollection().InsertOneAsync(existingTrack); - }); - - var requestBody = new - { - atomic__operations = new[] - { - new - { - op = "remove", - @ref = new - { - type = "musicTracks", - id = existingTrack.StringId, - relationship = "performers" - }, - data = new[] - { - new - { - type = "performers", - id = existingTrack.Performers[0].StringId - } - } - } - } - }; - - const string route = "/operations"; - - // Act - (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); - - responseDocument.Errors.Should().HaveCount(1); - - Error error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.BadRequest); - error.Title.Should().Be("Relationships are not supported when using MongoDB."); - error.Detail.Should().BeNull(); - error.Source.Pointer.Should().Be("/atomic:operations[0]"); - } - - [Fact] - public async Task Cannot_remove_from_HasManyThrough_relationship() - { - // Arrange - var existingPlaylistMusicTrack = new PlaylistMusicTrack - { - Playlist = _fakers.Playlist.Generate(), - MusicTrack = _fakers.MusicTrack.Generate() - }; - - await _testContext.RunOnDatabaseAsync(async db => - { - await db.GetCollection().InsertOneAsync(existingPlaylistMusicTrack.Playlist); - await db.GetCollection().InsertOneAsync(existingPlaylistMusicTrack.MusicTrack); - await db.GetCollection().InsertOneAsync(existingPlaylistMusicTrack); - }); - - var requestBody = new - { - atomic__operations = new[] - { - new - { - op = "remove", - @ref = new - { - type = "playlists", - id = existingPlaylistMusicTrack.Playlist.StringId, - relationship = "tracks" - }, - data = new[] - { - new - { - type = "musicTracks", - id = existingPlaylistMusicTrack.MusicTrack.StringId - } - } - } - } - }; - - const string route = "/operations"; - - // Act - (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); - - responseDocument.Errors.Should().HaveCount(1); - - Error error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.BadRequest); - error.Title.Should().Be("Relationships are not supported when using MongoDB."); - error.Detail.Should().BeNull(); - error.Source.Pointer.Should().Be("/atomic:operations[0]"); - } - } -} diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicReplaceToManyRelationshipTests.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicReplaceToManyRelationshipTests.cs deleted file mode 100644 index 69874bc..0000000 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicReplaceToManyRelationshipTests.cs +++ /dev/null @@ -1,136 +0,0 @@ -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using FluentAssertions; -using JsonApiDotNetCore.Serialization.Objects; -using JsonApiDotNetCoreMongoDbExampleTests.TestBuildingBlocks; -using Xunit; - -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Updating.Relationships -{ - [Collection("AtomicOperationsFixture")] - public sealed class AtomicReplaceToManyRelationshipTests - { - private readonly IntegrationTestContext _testContext; - private readonly OperationsFakers _fakers = new OperationsFakers(); - - public AtomicReplaceToManyRelationshipTests(AtomicOperationsFixture fixture) - { - _testContext = fixture.TestContext; - - fixture.TestContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); - } - - [Fact] - public async Task Cannot_replace_HasMany_relationship() - { - // Arrange - MusicTrack existingTrack = _fakers.MusicTrack.Generate(); - - Performer existingPerformer = _fakers.Performer.Generate(); - - await _testContext.RunOnDatabaseAsync(async db => - { - await db.GetCollection().InsertOneAsync(existingTrack); - await db.GetCollection().InsertOneAsync(existingPerformer); - }); - - var requestBody = new - { - atomic__operations = new[] - { - new - { - op = "update", - @ref = new - { - type = "musicTracks", - id = existingTrack.StringId, - relationship = "performers" - }, - data = new[] - { - new - { - type = "performers", - id = existingPerformer.StringId - } - } - } - } - }; - - const string route = "/operations"; - - // Act - (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); - - responseDocument.Errors.Should().HaveCount(1); - - Error error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.BadRequest); - error.Title.Should().Be("Relationships are not supported when using MongoDB."); - error.Detail.Should().BeNull(); - error.Source.Pointer.Should().Be("/atomic:operations[0]"); - } - - [Fact] - public async Task Cannot_replace_HasManyThrough_relationship() - { - // Arrange - Playlist existingPlaylist = _fakers.Playlist.Generate(); - - MusicTrack existingTrack = _fakers.MusicTrack.Generate(); - - await _testContext.RunOnDatabaseAsync(async db => - { - await db.GetCollection().InsertOneAsync(existingPlaylist); - await db.GetCollection().InsertOneAsync(existingTrack); - }); - - var requestBody = new - { - atomic__operations = new[] - { - new - { - op = "update", - @ref = new - { - type = "playlists", - id = existingPlaylist.StringId, - relationship = "tracks" - }, - data = new[] - { - new - { - type = "musicTracks", - id = existingTrack.StringId - } - } - } - } - }; - - const string route = "/operations"; - - // Act - (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); - - responseDocument.Errors.Should().HaveCount(1); - - Error error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.BadRequest); - error.Title.Should().Be("Relationships are not supported when using MongoDB."); - error.Detail.Should().BeNull(); - error.Source.Pointer.Should().Be("/atomic:operations[0]"); - } - } -} diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicUpdateToOneRelationshipTests.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicUpdateToOneRelationshipTests.cs deleted file mode 100644 index 3175e02..0000000 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Relationships/AtomicUpdateToOneRelationshipTests.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using FluentAssertions; -using JsonApiDotNetCore.Serialization.Objects; -using JsonApiDotNetCoreMongoDbExampleTests.TestBuildingBlocks; -using Xunit; - -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Updating.Relationships -{ - [Collection("AtomicOperationsFixture")] - public sealed class AtomicUpdateToOneRelationshipTests - { - private readonly IntegrationTestContext _testContext; - private readonly OperationsFakers _fakers = new OperationsFakers(); - - public AtomicUpdateToOneRelationshipTests(AtomicOperationsFixture fixture) - { - _testContext = fixture.TestContext; - - fixture.TestContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); - } - - [Fact] - public async Task Cannot_create_relationship() - { - // Arrange - MusicTrack existingTrack = _fakers.MusicTrack.Generate(); - RecordCompany existingCompany = _fakers.RecordCompany.Generate(); - - await _testContext.RunOnDatabaseAsync(async db => - { - await db.GetCollection().InsertOneAsync(existingTrack); - await db.GetCollection().InsertOneAsync(existingCompany); - }); - - var requestBody = new - { - atomic__operations = new[] - { - new - { - op = "update", - @ref = new - { - type = "musicTracks", - id = existingTrack.StringId, - relationship = "ownedBy" - }, - data = new - { - type = "recordCompanies", - id = existingCompany.StringId - } - } - } - }; - - const string route = "/operations"; - - // Act - (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); - - responseDocument.Errors.Should().HaveCount(1); - - Error error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.BadRequest); - error.Title.Should().Be("Relationships are not supported when using MongoDB."); - error.Detail.Should().BeNull(); - error.Source.Pointer.Should().Be("/atomic:operations[0]"); - } - } -} diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicReplaceToManyRelationshipTests.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicReplaceToManyRelationshipTests.cs deleted file mode 100644 index 2142aa0..0000000 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicReplaceToManyRelationshipTests.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using FluentAssertions; -using JsonApiDotNetCore.Serialization.Objects; -using JsonApiDotNetCoreMongoDbExampleTests.TestBuildingBlocks; -using Xunit; - -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Updating.Resources -{ - [Collection("AtomicOperationsFixture")] - public sealed class AtomicReplaceToManyRelationshipTests - { - private readonly IntegrationTestContext _testContext; - private readonly OperationsFakers _fakers = new OperationsFakers(); - - public AtomicReplaceToManyRelationshipTests(AtomicOperationsFixture fixture) - { - _testContext = fixture.TestContext; - - fixture.TestContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); - } - - [Fact] - public async Task Cannot_replace_HasMany_relationship() - { - // Arrange - MusicTrack existingTrack = _fakers.MusicTrack.Generate(); - existingTrack.Performers = _fakers.Performer.Generate(1); - - Performer existingPerformer = _fakers.Performer.Generate(); - - await _testContext.RunOnDatabaseAsync(async db => - { - await db.GetCollection().InsertManyAsync(existingTrack.Performers[0], existingPerformer); - await db.GetCollection().InsertOneAsync(existingTrack); - }); - - var requestBody = new - { - atomic__operations = new[] - { - new - { - op = "update", - data = new - { - type = "musicTracks", - id = existingTrack.StringId, - relationships = new - { - performers = new - { - data = new[] - { - new - { - type = "performers", - id = existingPerformer.StringId - } - } - } - } - } - } - } - }; - - const string route = "/operations"; - - // Act - (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); - - responseDocument.Errors.Should().HaveCount(1); - - Error error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.BadRequest); - error.Title.Should().Be("Relationships are not supported when using MongoDB."); - error.Detail.Should().BeNull(); - error.Source.Pointer.Should().Be("/atomic:operations[0]"); - } - } -} diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateResourceTests.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateResourceTests.cs deleted file mode 100644 index d029961..0000000 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateResourceTests.cs +++ /dev/null @@ -1,359 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using FluentAssertions; -using JsonApiDotNetCore.Serialization.Objects; -using JsonApiDotNetCoreMongoDbExampleTests.TestBuildingBlocks; -using MongoDB.Driver; -using Xunit; - -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Updating.Resources -{ - [Collection("AtomicOperationsFixture")] - public sealed class AtomicUpdateResourceTests - { - private readonly IntegrationTestContext _testContext; - private readonly OperationsFakers _fakers = new OperationsFakers(); - - public AtomicUpdateResourceTests(AtomicOperationsFixture fixture) - { - _testContext = fixture.TestContext; - - fixture.TestContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); - } - - [Fact] - public async Task Can_update_resources() - { - // Arrange - const int elementCount = 5; - - List existingTracks = _fakers.MusicTrack.Generate(elementCount); - string[] newTrackTitles = _fakers.MusicTrack.Generate(elementCount).Select(musicTrack => musicTrack.Title).ToArray(); - - await _testContext.RunOnDatabaseAsync(async db => - { - await db.ClearCollectionAsync(); - await db.GetCollection().InsertManyAsync(existingTracks); - }); - - var operationElements = new List(elementCount); - - for (int index = 0; index < elementCount; index++) - { - operationElements.Add(new - { - op = "update", - data = new - { - type = "musicTracks", - id = existingTracks[index].StringId, - attributes = new - { - title = newTrackTitles[index] - } - } - }); - } - - var requestBody = new - { - atomic__operations = operationElements - }; - - const string route = "/operations"; - - // Act - (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); - - responseDocument.Should().BeEmpty(); - - await _testContext.RunOnDatabaseAsync(async db => - { - List tracksInDatabase = await db.GetCollection().AsQueryable().ToListAsync(); - - tracksInDatabase.Should().HaveCount(elementCount); - - for (int index = 0; index < elementCount; index++) - { - MusicTrack trackInDatabase = tracksInDatabase.Single(musicTrack => musicTrack.Id == existingTracks[index].Id); - - trackInDatabase.Title.Should().Be(newTrackTitles[index]); - trackInDatabase.Genre.Should().Be(existingTracks[index].Genre); - } - }); - } - - [Fact] - public async Task Can_update_resource_without_attributes_or_relationships() - { - // Arrange - MusicTrack existingTrack = _fakers.MusicTrack.Generate(); - - await _testContext.RunOnDatabaseAsync(async db => - { - await db.GetCollection().InsertOneAsync(existingTrack); - }); - - var requestBody = new - { - atomic__operations = new[] - { - new - { - op = "update", - data = new - { - type = "musicTracks", - id = existingTrack.StringId, - attributes = new - { - }, - relationships = new - { - } - } - } - } - }; - - const string route = "/operations"; - - // Act - (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); - - responseDocument.Should().BeEmpty(); - - await _testContext.RunOnDatabaseAsync(async db => - { - MusicTrack trackInDatabase = await db.GetCollection().AsQueryable().FirstWithIdAsync(existingTrack.Id); - - trackInDatabase.Title.Should().Be(existingTrack.Title); - trackInDatabase.Genre.Should().Be(existingTrack.Genre); - }); - } - - [Fact] - public async Task Can_partially_update_resource_without_side_effects() - { - // Arrange - MusicTrack existingTrack = _fakers.MusicTrack.Generate(); - existingTrack.OwnedBy = _fakers.RecordCompany.Generate(); - - string newGenre = _fakers.MusicTrack.Generate().Genre; - - await _testContext.RunOnDatabaseAsync(async db => - { - await db.GetCollection().InsertOneAsync(existingTrack); - }); - - var requestBody = new - { - atomic__operations = new[] - { - new - { - op = "update", - data = new - { - type = "musicTracks", - id = existingTrack.StringId, - attributes = new - { - genre = newGenre - } - } - } - } - }; - - const string route = "/operations"; - - // Act - (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); - - responseDocument.Should().BeEmpty(); - - await _testContext.RunOnDatabaseAsync(async db => - { - MusicTrack trackInDatabase = await db.GetCollection().AsQueryable().FirstWithIdAsync(existingTrack.Id); - - trackInDatabase.Title.Should().Be(existingTrack.Title); - trackInDatabase.LengthInSeconds.Should().BeApproximately(existingTrack.LengthInSeconds); - trackInDatabase.Genre.Should().Be(newGenre); - trackInDatabase.ReleasedAt.Should().BeCloseTo(existingTrack.ReleasedAt); - }); - } - - [Fact] - public async Task Can_completely_update_resource_without_side_effects() - { - // Arrange - MusicTrack existingTrack = _fakers.MusicTrack.Generate(); - - string newTitle = _fakers.MusicTrack.Generate().Title; - decimal? newLengthInSeconds = _fakers.MusicTrack.Generate().LengthInSeconds; - string newGenre = _fakers.MusicTrack.Generate().Genre; - DateTimeOffset newReleasedAt = _fakers.MusicTrack.Generate().ReleasedAt; - - await _testContext.RunOnDatabaseAsync(async db => - { - await db.GetCollection().InsertOneAsync(existingTrack); - }); - - var requestBody = new - { - atomic__operations = new[] - { - new - { - op = "update", - data = new - { - type = "musicTracks", - id = existingTrack.StringId, - attributes = new - { - title = newTitle, - lengthInSeconds = newLengthInSeconds, - genre = newGenre, - releasedAt = newReleasedAt - } - } - } - } - }; - - const string route = "/operations"; - - // Act - (HttpResponseMessage httpResponse, string responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.NoContent); - - responseDocument.Should().BeEmpty(); - - await _testContext.RunOnDatabaseAsync(async db => - { - MusicTrack trackInDatabase = await db.GetCollection().AsQueryable().FirstWithIdAsync(existingTrack.Id); - - trackInDatabase.Title.Should().Be(newTitle); - trackInDatabase.LengthInSeconds.Should().BeApproximately(newLengthInSeconds); - trackInDatabase.Genre.Should().Be(newGenre); - trackInDatabase.ReleasedAt.Should().BeCloseTo(newReleasedAt); - }); - } - - [Fact] - public async Task Can_update_resource_with_side_effects() - { - // Arrange - TextLanguage existingLanguage = _fakers.TextLanguage.Generate(); - string newIsoCode = _fakers.TextLanguage.Generate().IsoCode; - - await _testContext.RunOnDatabaseAsync(async db => - { - await db.GetCollection().InsertOneAsync(existingLanguage); - }); - - var requestBody = new - { - atomic__operations = new[] - { - new - { - op = "update", - data = new - { - type = "textLanguages", - id = existingLanguage.StringId, - attributes = new - { - isoCode = newIsoCode - } - } - } - } - }; - - const string route = "/operations"; - - // Act - (HttpResponseMessage httpResponse, AtomicOperationsDocument responseDocument) = - await _testContext.ExecutePostAtomicAsync(route, requestBody); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); - - responseDocument.Results.Should().HaveCount(1); - responseDocument.Results[0].SingleData.Should().NotBeNull(); - responseDocument.Results[0].SingleData.Type.Should().Be("textLanguages"); - responseDocument.Results[0].SingleData.Attributes["isoCode"].Should().Be(newIsoCode); - responseDocument.Results[0].SingleData.Attributes.Should().NotContainKey("concurrencyToken"); - responseDocument.Results[0].SingleData.Relationships.Should().BeNull(); - - await _testContext.RunOnDatabaseAsync(async db => - { - TextLanguage languageInDatabase = await db.GetCollection().AsQueryable().FirstWithIdAsync(existingLanguage.Id); - - languageInDatabase.IsoCode.Should().Be(newIsoCode); - }); - } - - [Fact] - public async Task Cannot_update_resource_for_unknown_ID() - { - // Arrange - var requestBody = new - { - atomic__operations = new[] - { - new - { - op = "update", - data = new - { - type = "performers", - id = "ffffffffffffffffffffffff", - attributes = new - { - }, - relationships = new - { - } - } - } - } - }; - - const string route = "/operations"; - - // Act - (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.NotFound); - - responseDocument.Errors.Should().HaveCount(1); - - Error error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.NotFound); - error.Title.Should().Be("The requested resource does not exist."); - error.Detail.Should().Be("Resource of type 'performers' with ID 'ffffffffffffffffffffffff' does not exist."); - error.Source.Pointer.Should().Be("/atomic:operations[0]"); - } - } -} diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateToOneRelationshipTests.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateToOneRelationshipTests.cs deleted file mode 100644 index 5c55006..0000000 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/AtomicOperations/Updating/Resources/AtomicUpdateToOneRelationshipTests.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using FluentAssertions; -using JsonApiDotNetCore.Serialization.Objects; -using JsonApiDotNetCoreMongoDbExampleTests.TestBuildingBlocks; -using Xunit; - -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.AtomicOperations.Updating.Resources -{ - [Collection("AtomicOperationsFixture")] - public sealed class AtomicUpdateToOneRelationshipTests - { - private readonly IntegrationTestContext _testContext; - private readonly OperationsFakers _fakers = new OperationsFakers(); - - public AtomicUpdateToOneRelationshipTests(AtomicOperationsFixture fixture) - { - _testContext = fixture.TestContext; - - fixture.TestContext.ConfigureServicesAfterStartup(services => services.AddControllersFromExampleProject()); - } - - [Fact] - public async Task Cannot_create_relationship() - { - // Arrange - Lyric existingLyric = _fakers.Lyric.Generate(); - MusicTrack existingTrack = _fakers.MusicTrack.Generate(); - - await _testContext.RunOnDatabaseAsync(async db => - { - await db.GetCollection().InsertOneAsync(existingLyric); - await db.GetCollection().InsertOneAsync(existingTrack); - }); - - var requestBody = new - { - atomic__operations = new[] - { - new - { - op = "update", - data = new - { - type = "lyrics", - id = existingLyric.StringId, - relationships = new - { - track = new - { - data = new - { - type = "musicTracks", - id = existingTrack.StringId - } - } - } - } - } - } - }; - - const string route = "/operations"; - - // Act - (HttpResponseMessage httpResponse, ErrorDocument responseDocument) = await _testContext.ExecutePostAtomicAsync(route, requestBody); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.BadRequest); - - responseDocument.Errors.Should().HaveCount(1); - - Error error = responseDocument.Errors[0]; - error.StatusCode.Should().Be(HttpStatusCode.BadRequest); - error.Title.Should().Be("Relationships are not supported when using MongoDB."); - error.Detail.Should().BeNull(); - error.Source.Pointer.Should().Be("/atomic:operations[0]"); - } - } -} diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/Meta/ResourceMetaTests.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/Meta/ResourceMetaTests.cs deleted file mode 100644 index e6cb1ff..0000000 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/Meta/ResourceMetaTests.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System.Collections.Generic; -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using FluentAssertions; -using JsonApiDotNetCore.Serialization.Objects; -using JsonApiDotNetCoreMongoDbExampleTests.TestBuildingBlocks; -using Xunit; - -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.Meta -{ - public sealed class ResourceMetaTests : IClassFixture> - { - private readonly IntegrationTestContext _testContext; - private readonly SupportFakers _fakers = new SupportFakers(); - - public ResourceMetaTests(IntegrationTestContext testContext) - { - _testContext = testContext; - } - - [Fact] - public async Task Returns_resource_meta_from_ResourceDefinition() - { - // Arrange - List tickets = _fakers.SupportTicket.Generate(3); - tickets[0].Description = "Critical: " + tickets[0].Description; - tickets[2].Description = "Critical: " + tickets[2].Description; - - await _testContext.RunOnDatabaseAsync(async db => - { - await db.ClearCollectionAsync(); - await db.GetCollection().InsertManyAsync(tickets); - }); - - const string route = "/supportTickets"; - - // Act - (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); - - responseDocument.ManyData.Should().HaveCount(3); - responseDocument.ManyData[0].Meta.Should().ContainKey("hasHighPriority"); - responseDocument.ManyData[1].Meta.Should().BeNull(); - responseDocument.ManyData[2].Meta.Should().ContainKey("hasHighPriority"); - } - } -} diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/Meta/SupportFakers.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/Meta/SupportFakers.cs deleted file mode 100644 index ca6f6f5..0000000 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/Meta/SupportFakers.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using Bogus; -using JsonApiDotNetCoreMongoDbExampleTests.TestBuildingBlocks; - -// @formatter:wrap_chained_method_calls chop_always -// @formatter:keep_existing_linebreaks true - -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.Meta -{ - internal sealed class SupportFakers : FakerContainer - { - private readonly Lazy> _lazySupportTicketFaker = new Lazy>(() => - new Faker() - .UseSeed(GetFakerSeed()) - .RuleFor(supportTicket => supportTicket.Description, faker => faker.Lorem.Paragraph())); - - public Faker SupportTicket => _lazySupportTicketFaker.Value; - } -} diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/Meta/SupportTicket.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/Meta/SupportTicket.cs deleted file mode 100644 index aaee7e8..0000000 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/Meta/SupportTicket.cs +++ /dev/null @@ -1,13 +0,0 @@ -using JetBrains.Annotations; -using JsonApiDotNetCore.MongoDb.Resources; -using JsonApiDotNetCore.Resources.Annotations; - -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.Meta -{ - [UsedImplicitly(ImplicitUseTargetFlags.Members)] - public sealed class SupportTicket : MongoIdentifiable - { - [Attr] - public string Description { get; set; } - } -} diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/Meta/SupportTicketDefinition.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/Meta/SupportTicketDefinition.cs deleted file mode 100644 index 3f18eff..0000000 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/Meta/SupportTicketDefinition.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Collections.Generic; -using JetBrains.Annotations; -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Resources; - -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.Meta -{ - [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] - public sealed class SupportTicketDefinition : JsonApiResourceDefinition - { - public SupportTicketDefinition(IResourceGraph resourceGraph) - : base(resourceGraph) - { - } - - public override IDictionary GetMeta(SupportTicket resource) - { - if (resource.Description != null && resource.Description.StartsWith("Critical:", StringComparison.Ordinal)) - { - return new Dictionary - { - ["hasHighPriority"] = true - }; - } - - return base.GetMeta(resource); - } - } -} diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/Meta/SupportTicketsController.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/Meta/SupportTicketsController.cs deleted file mode 100644 index b5674d2..0000000 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/Meta/SupportTicketsController.cs +++ /dev/null @@ -1,15 +0,0 @@ -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Controllers; -using JsonApiDotNetCore.Services; -using Microsoft.Extensions.Logging; - -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.Meta -{ - public sealed class SupportTicketsController : JsonApiController - { - public SupportTicketsController(IJsonApiOptions options, ILoggerFactory loggerFactory, IResourceService resourceService) - : base(options, loggerFactory, resourceService) - { - } - } -} diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/Meta/TopLevelCountTests.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/Meta/TopLevelCountTests.cs deleted file mode 100644 index 7aa39df..0000000 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/Meta/TopLevelCountTests.cs +++ /dev/null @@ -1,143 +0,0 @@ -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using FluentAssertions; -using JsonApiDotNetCore.Configuration; -using JsonApiDotNetCore.Resources; -using JsonApiDotNetCore.Serialization.Objects; -using JsonApiDotNetCoreMongoDbExampleTests.TestBuildingBlocks; -using Microsoft.Extensions.DependencyInjection; -using Xunit; - -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.Meta -{ - public sealed class TopLevelCountTests : IClassFixture> - { - private readonly IntegrationTestContext _testContext; - private readonly SupportFakers _fakers = new SupportFakers(); - - public TopLevelCountTests(IntegrationTestContext testContext) - { - _testContext = testContext; - - testContext.ConfigureServicesAfterStartup(services => - { - services.AddScoped(typeof(IResourceChangeTracker<>), typeof(NeverSameResourceChangeTracker<>)); - }); - - var options = (JsonApiOptions)testContext.Factory.Services.GetRequiredService(); - options.IncludeTotalResourceCount = true; - } - - [Fact] - public async Task Renders_resource_count_for_collection() - { - // Arrange - SupportTicket ticket = _fakers.SupportTicket.Generate(); - - await _testContext.RunOnDatabaseAsync(async db => - { - await db.ClearCollectionAsync(); - await db.GetCollection().InsertOneAsync(ticket); - }); - - const string route = "/supportTickets"; - - // Act - (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); - - responseDocument.Meta.Should().NotBeNull(); - responseDocument.Meta["totalResources"].Should().Be(1); - } - - [Fact] - public async Task Renders_resource_count_for_empty_collection() - { - // Arrange - await _testContext.RunOnDatabaseAsync(async db => - { - await db.ClearCollectionAsync(); - }); - - const string route = "/supportTickets"; - - // Act - (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); - - responseDocument.Meta.Should().NotBeNull(); - responseDocument.Meta["totalResources"].Should().Be(0); - } - - [Fact] - public async Task Hides_resource_count_in_create_resource_response() - { - // Arrange - string newDescription = _fakers.SupportTicket.Generate().Description; - - var requestBody = new - { - data = new - { - type = "supportTickets", - attributes = new - { - description = newDescription - } - } - }; - - const string route = "/supportTickets"; - - // Act - (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.Created); - - responseDocument.Meta.Should().BeNull(); - } - - [Fact] - public async Task Hides_resource_count_in_update_resource_response() - { - // Arrange - SupportTicket existingTicket = _fakers.SupportTicket.Generate(); - - string newDescription = _fakers.SupportTicket.Generate().Description; - - await _testContext.RunOnDatabaseAsync(async db => - { - await db.GetCollection().InsertOneAsync(existingTicket); - }); - - var requestBody = new - { - data = new - { - type = "supportTickets", - id = existingTicket.StringId, - attributes = new - { - description = newDescription - } - } - }; - - string route = "/supportTickets/" + existingTicket.StringId; - - // Act - (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); - - // Assert - httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); - - responseDocument.Meta.Should().BeNull(); - } - } -} diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/QueryStrings/AccountPreferences.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/QueryStrings/AccountPreferences.cs deleted file mode 100644 index 5f3de6e..0000000 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/QueryStrings/AccountPreferences.cs +++ /dev/null @@ -1,13 +0,0 @@ -using JetBrains.Annotations; -using JsonApiDotNetCore.MongoDb.Resources; -using JsonApiDotNetCore.Resources.Annotations; - -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.QueryStrings -{ - [UsedImplicitly(ImplicitUseTargetFlags.Members)] - public sealed class AccountPreferences : MongoIdentifiable - { - [Attr] - public bool UseDarkTheme { get; set; } - } -} diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/QueryStrings/Appointment.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/QueryStrings/Appointment.cs deleted file mode 100644 index a9fd483..0000000 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/QueryStrings/Appointment.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using JetBrains.Annotations; -using JsonApiDotNetCore.MongoDb.Resources; -using JsonApiDotNetCore.Resources.Annotations; - -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.QueryStrings -{ - [UsedImplicitly(ImplicitUseTargetFlags.Members)] - public sealed class Appointment : MongoIdentifiable - { - [Attr] - public string Title { get; set; } - - [Attr] - public DateTimeOffset StartTime { get; set; } - - [Attr] - public DateTimeOffset EndTime { get; set; } - } -} diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/QueryStrings/Blog.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/QueryStrings/Blog.cs deleted file mode 100644 index 7ef49d6..0000000 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/QueryStrings/Blog.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Collections.Generic; -using JetBrains.Annotations; -using JsonApiDotNetCore.MongoDb.Resources; -using JsonApiDotNetCore.Resources.Annotations; -using MongoDB.Bson.Serialization.Attributes; - -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.QueryStrings -{ - [UsedImplicitly(ImplicitUseTargetFlags.Members)] - public sealed class Blog : MongoIdentifiable - { - [Attr] - public string Title { get; set; } - - [Attr] - public string PlatformName { get; set; } - - [Attr(Capabilities = AttrCapabilities.All & ~(AttrCapabilities.AllowCreate | AttrCapabilities.AllowChange))] - public bool ShowAdvertisements => PlatformName.EndsWith("(using free account)", StringComparison.Ordinal); - - [HasMany] - [BsonIgnore] - public IList Posts { get; set; } - - [HasOne] - [BsonIgnore] - public WebAccount Owner { get; set; } - } -} diff --git a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/QueryStrings/BlogPost.cs b/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/QueryStrings/BlogPost.cs deleted file mode 100644 index 4678864..0000000 --- a/test/JsonApiDotNetCoreMongoDbExampleTests/IntegrationTests/QueryStrings/BlogPost.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.Collections.Generic; -using JetBrains.Annotations; -using JsonApiDotNetCore.MongoDb.Resources; -using JsonApiDotNetCore.Resources.Annotations; -using MongoDB.Bson.Serialization.Attributes; - -namespace JsonApiDotNetCoreMongoDbExampleTests.IntegrationTests.QueryStrings -{ - [UsedImplicitly(ImplicitUseTargetFlags.Members)] - public sealed class BlogPost : MongoIdentifiable - { - [Attr] - public string Caption { get; set; } - - [Attr] - public string Url { get; set; } - - [HasOne] - [BsonIgnore] - public WebAccount Author { get; set; } - - [HasOne] - [BsonIgnore] - public WebAccount Reviewer { get; set; } - - [HasManyThrough(nameof(BlogPostLabels))] - [BsonIgnore] - public ISet