From ceb27c36a99ea953ff7f7cbbd03532216775a605 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Tue, 8 Nov 2022 22:12:14 +0100 Subject: [PATCH 01/15] Increment version to 5.1.1 (used for pre-release builds from ci) --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 99319a64e6..8e68ea532b 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -6,7 +6,7 @@ 6.0.* 4.3.* 2.14.1 - 5.1.0 + 5.1.1 $(MSBuildThisFileDirectory)CodingGuidelines.ruleset 9999 enable From 833312d377eacf137f9eceff87b0fc19c529e79a Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Wed, 9 Nov 2022 00:53:09 +0100 Subject: [PATCH 02/15] Run cibuild on Ubuntu 20.04 LTS instead of 18.04.4 LTS --- Build.ps1 | 2 +- appveyor.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Build.ps1 b/Build.ps1 index d8be7da5e6..ed1363f626 100644 --- a/Build.ps1 +++ b/Build.ps1 @@ -105,7 +105,7 @@ dotnet build -c Release CheckLastExitCode # https://youtrack.jetbrains.com/issue/RSRP-488628/Breaking-InspectCode-fails-with-Roslyn-Worker-process-exited-unexpectedly-after-update -if ($env:APPVEYOR_BUILD_WORKER_IMAGE -ne 'Ubuntu') { +if ($IsWindows) { RunInspectCode RunCleanupCode } diff --git a/appveyor.yml b/appveyor.yml index 61feec2ab8..79a41d39c2 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,5 +1,5 @@ image: - - Ubuntu + - Ubuntu2004 - Visual Studio 2022 version: '{build}' From 3d4be056030d1986858165ba06948f8ab3666304 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Fri, 11 Nov 2022 11:54:57 +0100 Subject: [PATCH 03/15] Use built-in HashSet<>.UnionWith() instead of our own .AddRange() --- src/JsonApiDotNetCore/CollectionExtensions.cs | 11 ----------- .../Queries/Internal/Parsing/IncludeParser.cs | 2 +- .../Queries/Internal/QueryLayerComposer.cs | 2 +- .../Repositories/EntityFrameworkCoreRepository.cs | 2 +- src/JsonApiDotNetCore/Resources/OperationContainer.cs | 2 +- src/JsonApiDotNetCore/Resources/TargetedFields.cs | 4 ++-- .../Request/Adapters/RelationshipDataAdapter.cs | 2 +- .../IntegrationTests/CustomRoutes/CustomRouteTests.cs | 4 ++-- .../RemoveFromToManyRelationshipTests.cs | 4 ++-- .../Relationships/UpdateToOneRelationshipTests.cs | 2 +- .../Resources/UpdateToOneRelationshipTests.cs | 2 +- 11 files changed, 13 insertions(+), 24 deletions(-) diff --git a/src/JsonApiDotNetCore/CollectionExtensions.cs b/src/JsonApiDotNetCore/CollectionExtensions.cs index 133231eb23..6a17fed7b9 100644 --- a/src/JsonApiDotNetCore/CollectionExtensions.cs +++ b/src/JsonApiDotNetCore/CollectionExtensions.cs @@ -79,15 +79,4 @@ public static IEnumerable WhereNotNull(this IEnumerable source) return source.Where(element => element is not null)!; #pragma warning restore AV1250 // Evaluate LINQ query before returning it } - - public static void AddRange(this ICollection source, IEnumerable itemsToAdd) - { - ArgumentGuard.NotNull(source); - ArgumentGuard.NotNull(itemsToAdd); - - foreach (T item in itemsToAdd) - { - source.Add(item); - } - } } diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/IncludeParser.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/IncludeParser.cs index 3c8be88e46..1250e36312 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/IncludeParser.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/IncludeParser.cs @@ -110,7 +110,7 @@ private ICollection LookupRelationshipName(string relationshipN if (relationships.Any()) { - relationshipsFound.AddRange(relationships); + relationshipsFound.UnionWith(relationships); RelationshipAttribute[] relationshipsToInclude = relationships.Where(relationship => !relationship.IsIncludeBlocked()).ToArray(); ICollection affectedChildren = parent.EnsureChildren(relationshipsToInclude); diff --git a/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs b/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs index 29e0935954..4661a5bdda 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/QueryLayerComposer.cs @@ -254,7 +254,7 @@ private static IImmutableSet ApplyIncludeElementUpdate IDictionary> updatesInChildren) { ImmutableHashSet.Builder newElementsBuilder = ImmutableHashSet.CreateBuilder(); - newElementsBuilder.AddRange(includeElements); + newElementsBuilder.UnionWith(includeElements); foreach ((IncludeElementExpression existingElement, IImmutableSet updatedChildren) in updatesInChildren) { diff --git a/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs b/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs index 653db6129a..7cdd114301 100644 --- a/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs +++ b/src/JsonApiDotNetCore/Repositories/EntityFrameworkCoreRepository.cs @@ -471,7 +471,7 @@ private IEnumerable GetRightValueToStoreForAddToToMany(TResource leftResource, H if (rightResourceIdsStored.Any()) { - rightResourceIdsStored.AddRange(rightResourceIdsToAdd); + rightResourceIdsStored.UnionWith(rightResourceIdsToAdd); return rightResourceIdsStored; } diff --git a/src/JsonApiDotNetCore/Resources/OperationContainer.cs b/src/JsonApiDotNetCore/Resources/OperationContainer.cs index a066943c82..88ea29ecdc 100644 --- a/src/JsonApiDotNetCore/Resources/OperationContainer.cs +++ b/src/JsonApiDotNetCore/Resources/OperationContainer.cs @@ -58,6 +58,6 @@ private void AddSecondaryResources(RelationshipAttribute relationship, HashSet rightResources = CollectionConverter.ExtractResources(rightValue); - secondaryResources.AddRange(rightResources); + secondaryResources.UnionWith(rightResources); } } diff --git a/src/JsonApiDotNetCore/Resources/TargetedFields.cs b/src/JsonApiDotNetCore/Resources/TargetedFields.cs index fe4701c61e..4d40fc240d 100644 --- a/src/JsonApiDotNetCore/Resources/TargetedFields.cs +++ b/src/JsonApiDotNetCore/Resources/TargetedFields.cs @@ -18,8 +18,8 @@ public void CopyFrom(ITargetedFields other) { Clear(); - Attributes.AddRange(other.Attributes); - Relationships.AddRange(other.Relationships); + Attributes.UnionWith(other.Attributes); + Relationships.UnionWith(other.Relationships); } public void Clear() diff --git a/src/JsonApiDotNetCore/Serialization/Request/Adapters/RelationshipDataAdapter.cs b/src/JsonApiDotNetCore/Serialization/Request/Adapters/RelationshipDataAdapter.cs index ac1e25746b..0e90f7df07 100644 --- a/src/JsonApiDotNetCore/Serialization/Request/Adapters/RelationshipDataAdapter.cs +++ b/src/JsonApiDotNetCore/Serialization/Request/Adapters/RelationshipDataAdapter.cs @@ -111,7 +111,7 @@ private IEnumerable ConvertToManyRelationshipData(SingleOrManyData(IdentifiableComparer.Instance); - resourceSet.AddRange(rightResources); + resourceSet.UnionWith(rightResources); return resourceSet; } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/CustomRoutes/CustomRouteTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/CustomRoutes/CustomRouteTests.cs index 572f2baeed..e89641470d 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/CustomRoutes/CustomRouteTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/CustomRoutes/CustomRouteTests.cs @@ -67,12 +67,12 @@ await _testContext.RunOnDatabaseAsync(async dbContext => public async Task Can_get_resources_at_custom_action_method() { // Arrange - List town = _fakers.Town.Generate(7); + List towns = _fakers.Town.Generate(7); await _testContext.RunOnDatabaseAsync(async dbContext => { await dbContext.ClearTableAsync(); - dbContext.Towns.AddRange(town); + dbContext.Towns.AddRange(towns); await dbContext.SaveChangesAsync(); }); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Relationships/RemoveFromToManyRelationshipTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Relationships/RemoveFromToManyRelationshipTests.cs index eef972dd1f..ed9e4e7936 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Relationships/RemoveFromToManyRelationshipTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Relationships/RemoveFromToManyRelationshipTests.cs @@ -1142,7 +1142,7 @@ private void RemoveFromSubscribers(WorkItem workItem, ISet rightR { if (!workItem.Subscribers.IsNullOrEmpty()) { - PreloadedSubscribers.AddRange(workItem.Subscribers); + PreloadedSubscribers.UnionWith(workItem.Subscribers); } foreach (long subscriberId in ExtraSubscribersIdsToRemove) @@ -1158,7 +1158,7 @@ private void RemoveFromTags(WorkItem workItem, ISet rightResource { if (!workItem.Tags.IsNullOrEmpty()) { - PreloadedTags.AddRange(workItem.Tags); + PreloadedTags.UnionWith(workItem.Tags); } foreach (int tagId in ExtraTagIdsToRemove) diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Relationships/UpdateToOneRelationshipTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Relationships/UpdateToOneRelationshipTests.cs index a492cc4826..b96fcd6fff 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Relationships/UpdateToOneRelationshipTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Relationships/UpdateToOneRelationshipTests.cs @@ -66,7 +66,7 @@ public async Task Can_clear_OneToOne_relationship() await _testContext.RunOnDatabaseAsync(async dbContext => { - dbContext.Groups.AddRange(existingGroup); + dbContext.Groups.Add(existingGroup); await dbContext.SaveChangesAsync(); }); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Resources/UpdateToOneRelationshipTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Resources/UpdateToOneRelationshipTests.cs index 81faaa8297..f96f4a9efa 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Resources/UpdateToOneRelationshipTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/Updating/Resources/UpdateToOneRelationshipTests.cs @@ -209,7 +209,7 @@ public async Task Can_clear_OneToOne_relationship() await _testContext.RunOnDatabaseAsync(async dbContext => { - dbContext.RgbColors.AddRange(existingColor); + dbContext.RgbColors.Add(existingColor); await dbContext.SaveChangesAsync(); }); From 1fff6cdf021563d70f7de763f50e389c5cdb0ded Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sat, 12 Nov 2022 09:10:15 +0100 Subject: [PATCH 04/15] Update to PostgreSQL v15 --- appveyor.yml | 6 +++--- run-docker-postgres.ps1 | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 79a41d39c2..196e7ff0d6 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,7 +4,7 @@ image: version: '{build}' -stack: postgresql 13.4 +stack: postgresql 15 environment: PGUSER: postgres @@ -34,7 +34,7 @@ for: only: - image: Visual Studio 2022 services: - - postgresql13 + - postgresql15 # REF: https://github.com/docascode/docfx-seed/blob/master/appveyor.yml before_build: - pwsh: | @@ -101,7 +101,7 @@ build_script: Write-Output "PostgreSQL version:" if ($IsWindows) { - . "${env:ProgramFiles}\PostgreSQL\13\bin\psql" --version + . "${env:ProgramFiles}\PostgreSQL\15\bin\psql" --version } else { psql --version diff --git a/run-docker-postgres.ps1 b/run-docker-postgres.ps1 index 89a325e3b5..153b93a846 100644 --- a/run-docker-postgres.ps1 +++ b/run-docker-postgres.ps1 @@ -9,4 +9,4 @@ docker run --rm --name jsonapi-dotnet-core-testing ` -e POSTGRES_USER=postgres ` -e POSTGRES_PASSWORD=postgres ` -p 5432:5432 ` - postgres:13.4 + postgres:15 From 9039dcd58202bfc1fe9544eeb813dc115d5939db Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sat, 12 Nov 2022 12:17:51 +0100 Subject: [PATCH 05/15] Update for changed nullability annotations in .NET 7 --- .../Data/AppDbContext.cs | 2 +- .../Services/WorkItemService.cs | 4 ++-- .../FilterQueryStringParameterReader.cs | 19 +++++++++++-------- .../IncludeQueryStringParameterReader.cs | 2 +- .../PaginationQueryStringParameterReader.cs | 2 +- .../SortQueryStringParameterReader.cs | 2 +- ...parseFieldSetQueryStringParameterReader.cs | 2 +- .../Serialization/Response/LinkBuilder.cs | 4 ++-- .../MusicTrackReleaseDefinition.cs | 2 +- .../Reading/MoonDefinition.cs | 2 +- 10 files changed, 22 insertions(+), 19 deletions(-) diff --git a/src/Examples/DatabasePerTenantExample/Data/AppDbContext.cs b/src/Examples/DatabasePerTenantExample/Data/AppDbContext.cs index c70fc8655f..ba73b8bf3a 100644 --- a/src/Examples/DatabasePerTenantExample/Data/AppDbContext.cs +++ b/src/Examples/DatabasePerTenantExample/Data/AppDbContext.cs @@ -36,7 +36,7 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) private string GetConnectionString() { string? tenantName = GetTenantName(); - string connectionString = _configuration[$"Data:{tenantName ?? "Default"}Connection"]; + string? connectionString = _configuration[$"Data:{tenantName ?? "Default"}Connection"]; if (connectionString == null) { diff --git a/src/Examples/NoEntityFrameworkExample/Services/WorkItemService.cs b/src/Examples/NoEntityFrameworkExample/Services/WorkItemService.cs index 6df109e5ba..34a40755cb 100644 --- a/src/Examples/NoEntityFrameworkExample/Services/WorkItemService.cs +++ b/src/Examples/NoEntityFrameworkExample/Services/WorkItemService.cs @@ -11,12 +11,12 @@ namespace NoEntityFrameworkExample.Services; [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] public sealed class WorkItemService : IResourceService { - private readonly string _connectionString; + private readonly string? _connectionString; public WorkItemService(IConfiguration configuration) { string postgresPassword = Environment.GetEnvironmentVariable("PGPASSWORD") ?? "postgres"; - _connectionString = configuration["Data:DefaultConnection"].Replace("###", postgresPassword); + _connectionString = configuration["Data:DefaultConnection"]?.Replace("###", postgresPassword); } public async Task> GetAsync(CancellationToken cancellationToken) diff --git a/src/JsonApiDotNetCore/QueryStrings/Internal/FilterQueryStringParameterReader.cs b/src/JsonApiDotNetCore/QueryStrings/Internal/FilterQueryStringParameterReader.cs index 60c7f6b75c..dace5b8ca4 100644 --- a/src/JsonApiDotNetCore/QueryStrings/Internal/FilterQueryStringParameterReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/Internal/FilterQueryStringParameterReader.cs @@ -77,18 +77,21 @@ public virtual void Read(string parameterName, StringValues parameterValue) } } - private IEnumerable ExtractParameterValue(string parameterValue) + private IEnumerable ExtractParameterValue(string? parameterValue) { - if (_options.EnableLegacyFilterNotation) + if (parameterValue != null) { - foreach (string condition in LegacyConverter.ExtractConditions(parameterValue)) + if (_options.EnableLegacyFilterNotation) { - yield return condition; + foreach (string condition in LegacyConverter.ExtractConditions(parameterValue)) + { + yield return condition; + } + } + else + { + yield return parameterValue; } - } - else - { - yield return parameterValue; } } diff --git a/src/JsonApiDotNetCore/QueryStrings/Internal/IncludeQueryStringParameterReader.cs b/src/JsonApiDotNetCore/QueryStrings/Internal/IncludeQueryStringParameterReader.cs index a4db6ebd4a..7db9a9a7d7 100644 --- a/src/JsonApiDotNetCore/QueryStrings/Internal/IncludeQueryStringParameterReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/Internal/IncludeQueryStringParameterReader.cs @@ -48,7 +48,7 @@ public virtual void Read(string parameterName, StringValues parameterValue) { try { - _includeExpression = GetInclude(parameterValue); + _includeExpression = GetInclude(parameterValue.ToString()); } catch (QueryParseException exception) { diff --git a/src/JsonApiDotNetCore/QueryStrings/Internal/PaginationQueryStringParameterReader.cs b/src/JsonApiDotNetCore/QueryStrings/Internal/PaginationQueryStringParameterReader.cs index 416b48f4de..3e4293c5e0 100644 --- a/src/JsonApiDotNetCore/QueryStrings/Internal/PaginationQueryStringParameterReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/Internal/PaginationQueryStringParameterReader.cs @@ -53,7 +53,7 @@ public virtual void Read(string parameterName, StringValues parameterValue) { try { - PaginationQueryStringValueExpression constraint = GetPageConstraint(parameterValue); + PaginationQueryStringValueExpression constraint = GetPageConstraint(parameterValue.ToString()); if (constraint.Elements.Any(element => element.Scope == null)) { diff --git a/src/JsonApiDotNetCore/QueryStrings/Internal/SortQueryStringParameterReader.cs b/src/JsonApiDotNetCore/QueryStrings/Internal/SortQueryStringParameterReader.cs index 060d0c9986..5e5842c960 100644 --- a/src/JsonApiDotNetCore/QueryStrings/Internal/SortQueryStringParameterReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/Internal/SortQueryStringParameterReader.cs @@ -62,7 +62,7 @@ public virtual void Read(string parameterName, StringValues parameterValue) try { ResourceFieldChainExpression? scope = GetScope(parameterName); - SortExpression sort = GetSort(parameterValue, scope); + SortExpression sort = GetSort(parameterValue.ToString(), scope); var expressionInScope = new ExpressionInScope(scope, sort); _constraints.Add(expressionInScope); diff --git a/src/JsonApiDotNetCore/QueryStrings/Internal/SparseFieldSetQueryStringParameterReader.cs b/src/JsonApiDotNetCore/QueryStrings/Internal/SparseFieldSetQueryStringParameterReader.cs index fb4f665873..09c3c0ede8 100644 --- a/src/JsonApiDotNetCore/QueryStrings/Internal/SparseFieldSetQueryStringParameterReader.cs +++ b/src/JsonApiDotNetCore/QueryStrings/Internal/SparseFieldSetQueryStringParameterReader.cs @@ -69,7 +69,7 @@ public virtual void Read(string parameterName, StringValues parameterValue) try { ResourceType targetResourceType = GetSparseFieldType(parameterName); - SparseFieldSetExpression sparseFieldSet = GetSparseFieldSet(parameterValue, targetResourceType); + SparseFieldSetExpression sparseFieldSet = GetSparseFieldSet(parameterValue.ToString(), targetResourceType); _sparseFieldTableBuilder[targetResourceType] = sparseFieldSet; } diff --git a/src/JsonApiDotNetCore/Serialization/Response/LinkBuilder.cs b/src/JsonApiDotNetCore/Serialization/Response/LinkBuilder.cs index ea0eb197df..fb86eb084c 100644 --- a/src/JsonApiDotNetCore/Serialization/Response/LinkBuilder.cs +++ b/src/JsonApiDotNetCore/Serialization/Response/LinkBuilder.cs @@ -140,13 +140,13 @@ private void SetPaginationInTopLevelLinks(ResourceType resourceType, TopLevelLin private string? CalculatePageSizeValue(PageSize? topPageSize, ResourceType resourceType) { - string pageSizeParameterValue = HttpContext.Request.Query[PageSizeParameterName]; + string? pageSizeParameterValue = HttpContext.Request.Query[PageSizeParameterName]; PageSize? newTopPageSize = Equals(topPageSize, _options.DefaultPageSize) ? null : topPageSize; return ChangeTopPageSize(pageSizeParameterValue, newTopPageSize, resourceType); } - private string? ChangeTopPageSize(string pageSizeParameterValue, PageSize? topPageSize, ResourceType resourceType) + private string? ChangeTopPageSize(string? pageSizeParameterValue, PageSize? topPageSize, ResourceType resourceType) { IImmutableList elements = ParsePageSizeExpression(pageSizeParameterValue, resourceType); int elementInTopScopeIndex = elements.FindIndex(expression => expression.Scope == null); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/QueryStrings/MusicTrackReleaseDefinition.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/QueryStrings/MusicTrackReleaseDefinition.cs index 41aa048ecb..65ab4a4344 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/QueryStrings/MusicTrackReleaseDefinition.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/QueryStrings/MusicTrackReleaseDefinition.cs @@ -32,7 +32,7 @@ private IQueryable FilterOnRecentlyReleased(IQueryable s { IQueryable tracks = source; - if (bool.Parse(parameterValue)) + if (bool.Parse(parameterValue.ToString())) { tracks = tracks.Where(musicTrack => musicTrack.ReleasedAt < _systemClock.UtcNow && musicTrack.ReleasedAt > _systemClock.UtcNow.AddMonths(-3)); } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/MoonDefinition.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/MoonDefinition.cs index bdd75a9aff..2b904434ad 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/MoonDefinition.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Reading/MoonDefinition.cs @@ -51,7 +51,7 @@ public override QueryStringParameterHandlers OnRegisterQueryableHandlersFo private static IQueryable FilterByRadius(IQueryable source, StringValues parameterValue) { - bool isFilterOnLargerThan = bool.Parse(parameterValue); + bool isFilterOnLargerThan = bool.Parse(parameterValue.ToString()); return isFilterOnLargerThan ? source.Where(moon => moon.SolarRadius > 1m) : source.Where(moon => moon.SolarRadius <= 1m); } } From 023cdcca1e1b9a34a44fdd4af2e7ded57af70e77 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sat, 12 Nov 2022 12:32:06 +0100 Subject: [PATCH 06/15] Package updates --- .config/dotnet-tools.json | 4 ++-- Directory.Build.props | 6 +++--- test/TestBuildingBlocks/TestBuildingBlocks.csproj | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index d914fc369d..7afec1e6ff 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "jetbrains.resharper.globaltools": { - "version": "2022.2.3", + "version": "2022.2.4", "commands": [ "jb" ] @@ -21,7 +21,7 @@ ] }, "dotnet-reportgenerator-globaltool": { - "version": "5.1.3", + "version": "5.1.11", "commands": [ "reportgenerator" ] diff --git a/Directory.Build.props b/Directory.Build.props index 8e68ea532b..1810b9cd46 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -16,7 +16,7 @@ - + @@ -33,8 +33,8 @@ - 3.1.2 + 3.2.0 4.18.2 - 17.3.1 + 17.4.0 diff --git a/test/TestBuildingBlocks/TestBuildingBlocks.csproj b/test/TestBuildingBlocks/TestBuildingBlocks.csproj index 5600104fda..ce8c54ef3b 100644 --- a/test/TestBuildingBlocks/TestBuildingBlocks.csproj +++ b/test/TestBuildingBlocks/TestBuildingBlocks.csproj @@ -10,7 +10,7 @@ - + From d1fb4d07b161ebcaf10da06465cb69c0b0aaf3d6 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sat, 12 Nov 2022 15:57:05 +0100 Subject: [PATCH 07/15] Turn off constraints for running tests in parallel --- test/AnnotationTests/AnnotationTests.csproj | 6 ------ test/DiscoveryTests/DiscoveryTests.csproj | 6 ------ test/DiscoveryTests/xunit.runner.json | 4 ---- test/JsonApiDotNetCoreTests/JsonApiDotNetCoreTests.csproj | 6 ------ test/JsonApiDotNetCoreTests/xunit.runner.json | 5 ----- test/MultiDbContextTests/MultiDbContextTests.csproj | 6 ------ test/MultiDbContextTests/xunit.runner.json | 5 ----- test/UnitTests/UnitTests.csproj | 6 ------ test/UnitTests/xunit.runner.json | 4 ---- 9 files changed, 48 deletions(-) delete mode 100644 test/DiscoveryTests/xunit.runner.json delete mode 100644 test/JsonApiDotNetCoreTests/xunit.runner.json delete mode 100644 test/MultiDbContextTests/xunit.runner.json delete mode 100644 test/UnitTests/xunit.runner.json diff --git a/test/AnnotationTests/AnnotationTests.csproj b/test/AnnotationTests/AnnotationTests.csproj index 51df20d735..7b221a9a42 100644 --- a/test/AnnotationTests/AnnotationTests.csproj +++ b/test/AnnotationTests/AnnotationTests.csproj @@ -4,12 +4,6 @@ latest - - - PreserveNewest - - - diff --git a/test/DiscoveryTests/DiscoveryTests.csproj b/test/DiscoveryTests/DiscoveryTests.csproj index 2f1048de3f..abbec3ed98 100644 --- a/test/DiscoveryTests/DiscoveryTests.csproj +++ b/test/DiscoveryTests/DiscoveryTests.csproj @@ -3,12 +3,6 @@ $(TargetFrameworkName) - - - PreserveNewest - - - diff --git a/test/DiscoveryTests/xunit.runner.json b/test/DiscoveryTests/xunit.runner.json deleted file mode 100644 index 9db029ba52..0000000000 --- a/test/DiscoveryTests/xunit.runner.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "parallelizeAssembly": false, - "parallelizeTestCollections": false -} diff --git a/test/JsonApiDotNetCoreTests/JsonApiDotNetCoreTests.csproj b/test/JsonApiDotNetCoreTests/JsonApiDotNetCoreTests.csproj index ddb7550e5e..22d50630ca 100644 --- a/test/JsonApiDotNetCoreTests/JsonApiDotNetCoreTests.csproj +++ b/test/JsonApiDotNetCoreTests/JsonApiDotNetCoreTests.csproj @@ -3,12 +3,6 @@ $(TargetFrameworkName) - - - PreserveNewest - - - $(TargetFrameworkName) - - - PreserveNewest - - - diff --git a/test/MultiDbContextTests/xunit.runner.json b/test/MultiDbContextTests/xunit.runner.json deleted file mode 100644 index 8f5f10571b..0000000000 --- a/test/MultiDbContextTests/xunit.runner.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "parallelizeAssembly": false, - "parallelizeTestCollections": false, - "maxParallelThreads": 1 -} diff --git a/test/UnitTests/UnitTests.csproj b/test/UnitTests/UnitTests.csproj index eb3383fbdf..3166fe27e1 100644 --- a/test/UnitTests/UnitTests.csproj +++ b/test/UnitTests/UnitTests.csproj @@ -3,12 +3,6 @@ $(TargetFrameworkName) - - - PreserveNewest - - - diff --git a/test/UnitTests/xunit.runner.json b/test/UnitTests/xunit.runner.json deleted file mode 100644 index 9db029ba52..0000000000 --- a/test/UnitTests/xunit.runner.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "parallelizeAssembly": false, - "parallelizeTestCollections": false -} From bfecdebeaa4c5353edfe7deb3bdf22ffc8a11bca Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sat, 12 Nov 2022 16:37:45 +0100 Subject: [PATCH 08/15] Add throttling on integration tests that are running in parallel --- test/TestBuildingBlocks/IntegrationTest.cs | 19 +++++++++++-- .../IntegrationTestContext.cs | 28 +++++++++++-------- 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/test/TestBuildingBlocks/IntegrationTest.cs b/test/TestBuildingBlocks/IntegrationTest.cs index 4fb2e5cd26..92f49879fc 100644 --- a/test/TestBuildingBlocks/IntegrationTest.cs +++ b/test/TestBuildingBlocks/IntegrationTest.cs @@ -2,14 +2,18 @@ using System.Text; using System.Text.Json; using JsonApiDotNetCore.Middleware; +using Xunit; namespace TestBuildingBlocks; /// -/// A base class for tests that conveniently enables to execute HTTP requests against JSON:API endpoints. +/// A base class for tests that conveniently enables to execute HTTP requests against JSON:API endpoints. It throttles tests that are running in parallel +/// to avoid exceeding the maximum active database connections. /// -public abstract class IntegrationTest +public abstract class IntegrationTest : IAsyncLifetime { + private static readonly SemaphoreSlim ThrottleSemaphore = new(64); + protected abstract JsonSerializerOptions SerializerOptions { get; } public async Task<(HttpResponseMessage httpResponse, TResponseDocument responseDocument)> ExecuteHeadAsync(string requestUrl, @@ -105,4 +109,15 @@ public abstract class IntegrationTest throw new FormatException($"Failed to deserialize response body to JSON:\n{responseText}", exception); } } + + public async Task InitializeAsync() + { + await ThrottleSemaphore.WaitAsync(); + } + + public virtual Task DisposeAsync() + { + _ = ThrottleSemaphore.Release(); + return Task.CompletedTask; + } } diff --git a/test/TestBuildingBlocks/IntegrationTestContext.cs b/test/TestBuildingBlocks/IntegrationTestContext.cs index bccf7d8bf3..7856ba67f9 100644 --- a/test/TestBuildingBlocks/IntegrationTestContext.cs +++ b/test/TestBuildingBlocks/IntegrationTestContext.cs @@ -24,7 +24,7 @@ namespace TestBuildingBlocks; /// The Entity Framework Core database context, which can be defined in the test project or API project. /// [UsedImplicitly(ImplicitUseKindFlags.InstantiatedNoFixedConstructorSignature)] -public class IntegrationTestContext : IntegrationTest, IDisposable +public class IntegrationTestContext : IntegrationTest where TStartup : class where TDbContext : TestableDbContext { @@ -103,16 +103,6 @@ private WebApplicationFactory CreateFactory() return factoryWithConfiguredContentRoot; } - public void Dispose() - { - if (_lazyFactory.IsValueCreated) - { - RunOnDatabaseAsync(async dbContext => await dbContext.Database.EnsureDeletedAsync()).Wait(); - - _lazyFactory.Value.Dispose(); - } - } - public void ConfigureLogging(Action loggingConfiguration) { _loggingConfiguration = loggingConfiguration; @@ -136,6 +126,22 @@ public async Task RunOnDatabaseAsync(Func asyncAction) await asyncAction(dbContext); } + public override async Task DisposeAsync() + { + try + { + if (_lazyFactory.IsValueCreated) + { + await RunOnDatabaseAsync(async dbContext => await dbContext.Database.EnsureDeletedAsync()); + await _lazyFactory.Value.DisposeAsync(); + } + } + finally + { + await base.DisposeAsync(); + } + } + private sealed class IntegrationTestWebApplicationFactory : WebApplicationFactory { private Action? _loggingConfiguration; From a349fb957acf6e4bdb97fe2c5915ea20fc63aa84 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sat, 12 Nov 2022 18:01:18 +0100 Subject: [PATCH 09/15] Fixed: temporary database created by test not deleted afterwards --- .../AtomicTransactionConsistencyTests.cs | 23 +++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Transactions/AtomicTransactionConsistencyTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Transactions/AtomicTransactionConsistencyTests.cs index 97f0e08ff5..46ef0c4784 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Transactions/AtomicTransactionConsistencyTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/Transactions/AtomicTransactionConsistencyTests.cs @@ -9,7 +9,8 @@ namespace JsonApiDotNetCoreTests.IntegrationTests.AtomicOperations.Transactions; -public sealed class AtomicTransactionConsistencyTests : IClassFixture, OperationsDbContext>> +public sealed class AtomicTransactionConsistencyTests + : IClassFixture, OperationsDbContext>>, IAsyncLifetime { private readonly IntegrationTestContext, OperationsDbContext> _testContext; private readonly OperationsFakers _fakers = new(); @@ -27,7 +28,7 @@ public AtomicTransactionConsistencyTests(IntegrationTestContext(); string postgresPassword = Environment.GetEnvironmentVariable("PGPASSWORD") ?? "postgres"; - string dbConnectionString = $"Host=localhost;Port=5432;Database=JsonApiTest-{Guid.NewGuid():N};User ID=postgres;Password={postgresPassword}"; + string dbConnectionString = $"Host=localhost;Port=5432;Database=JsonApiTest-Extra-{Guid.NewGuid():N};User ID=postgres;Password={postgresPassword}"; services.AddDbContext(options => options.UseNpgsql(dbConnectionString)); }); @@ -158,4 +159,22 @@ public async Task Cannot_use_distributed_transaction() error.Source.ShouldNotBeNull(); error.Source.Pointer.Should().Be("/atomic:operations[0]"); } + + public Task InitializeAsync() + { + return Task.CompletedTask; + } + + Task IAsyncLifetime.DisposeAsync() + { + return DeleteExtraDatabaseAsync(); + } + + private async Task DeleteExtraDatabaseAsync() + { + await using AsyncServiceScope scope = _testContext.Factory.Services.CreateAsyncScope(); + var dbContext = scope.ServiceProvider.GetRequiredService(); + + await dbContext.Database.EnsureDeletedAsync(); + } } From fd16904f636b0c201bbcf8f899dd32cc7b714539 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sat, 12 Nov 2022 18:09:42 +0100 Subject: [PATCH 10/15] Avoid potential database name clashes in tests --- .../Configuration/DependencyContainerRegistrationTests.cs | 2 +- test/UnitTests/Extensions/ServiceCollectionExtensionsTests.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/JsonApiDotNetCoreTests/UnitTests/Configuration/DependencyContainerRegistrationTests.cs b/test/JsonApiDotNetCoreTests/UnitTests/Configuration/DependencyContainerRegistrationTests.cs index d0bb39a187..8642461d50 100644 --- a/test/JsonApiDotNetCoreTests/UnitTests/Configuration/DependencyContainerRegistrationTests.cs +++ b/test/JsonApiDotNetCoreTests/UnitTests/Configuration/DependencyContainerRegistrationTests.cs @@ -74,7 +74,7 @@ private static IHostBuilder CreateValidatingHostBuilder() IHostBuilder hostBuilder = Host.CreateDefaultBuilder().ConfigureWebHostDefaults(webBuilder => { webBuilder.ConfigureServices(services => - services.AddDbContext(options => options.UseInMemoryDatabase("db"))); + services.AddDbContext(options => options.UseInMemoryDatabase(Guid.NewGuid().ToString()))); webBuilder.UseStartup>(); diff --git a/test/UnitTests/Extensions/ServiceCollectionExtensionsTests.cs b/test/UnitTests/Extensions/ServiceCollectionExtensionsTests.cs index a90c3851ee..0cb24d8025 100644 --- a/test/UnitTests/Extensions/ServiceCollectionExtensionsTests.cs +++ b/test/UnitTests/Extensions/ServiceCollectionExtensionsTests.cs @@ -25,7 +25,7 @@ public void RegisterResource_DeviatingDbContextPropertyName_RegistersCorrectly() // Arrange var services = new ServiceCollection(); services.AddLogging(); - services.AddDbContext(options => options.UseInMemoryDatabase("UnitTestDb")); + services.AddDbContext(options => options.UseInMemoryDatabase(Guid.NewGuid().ToString())); // Act services.AddJsonApi(); From 7cdf273076b83bd66658724fc7d006fba7c493f8 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Sat, 12 Nov 2022 19:32:33 +0100 Subject: [PATCH 11/15] Apply global convention for all integration tests: change DeleteBehavior.ClientSetNull to DeleteBehavior.SetNull This improves speed and reduces the number of errors logged by the PostgreSQL server when running tests, because we no longer need the "truncate table cascade" fallback in DbContextExtensions. --- .../AtomicOperations/OperationsDbContext.cs | 2 ++ .../CompositeKeys/CompositeDbContext.cs | 2 ++ .../EagerLoading/EagerLoadingDbContext.cs | 2 ++ .../ModelState/ModelStateDbContext.cs | 2 ++ .../IntegrationTests/Links/LinksDbContext.cs | 2 ++ .../MultiTenancy/MultiTenancyDbContext.cs | 2 ++ .../QueryStrings/Filtering/FilterDbContext.cs | 2 ++ .../QueryStrings/QueryStringDbContext.cs | 2 ++ .../ReadWrite/ReadWriteDbContext.cs | 2 ++ .../DefaultBehaviorDbContext.cs | 2 ++ .../Serialization/SerializationDbContext.cs | 2 ++ .../ResourceInheritanceReadTests.cs | 4 ++-- .../TablePerType/TablePerTypeDbContext.cs | 2 ++ .../SoftDeletion/SoftDeletionDbContext.cs | 2 ++ .../IntegrationTests/ZeroKeys/ZeroKeyDbContext.cs | 2 ++ test/TestBuildingBlocks/DbContextExtensions.cs | 13 +------------ test/TestBuildingBlocks/TestableDbContext.cs | 12 ++++++++++++ 17 files changed, 43 insertions(+), 14 deletions(-) diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/OperationsDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/OperationsDbContext.cs index 26dd815521..6fd7817ba7 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/OperationsDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/AtomicOperations/OperationsDbContext.cs @@ -31,5 +31,7 @@ protected override void OnModelCreating(ModelBuilder builder) builder.Entity() .HasMany(musicTrack => musicTrack.OccursIn) .WithMany(playlist => playlist.Tracks); + + base.OnModelCreating(builder); } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/CompositeKeys/CompositeDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/CompositeKeys/CompositeDbContext.cs index b745208cae..d4850ad428 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/CompositeKeys/CompositeDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/CompositeKeys/CompositeDbContext.cs @@ -39,5 +39,7 @@ protected override void OnModelCreating(ModelBuilder builder) builder.Entity() .HasMany(car => car.PreviousDealerships) .WithMany(dealership => dealership.SoldCars); + + base.OnModelCreating(builder); } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/EagerLoading/EagerLoadingDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/EagerLoading/EagerLoadingDbContext.cs index 030a1a447b..a31deab9a8 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/EagerLoading/EagerLoadingDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/EagerLoading/EagerLoadingDbContext.cs @@ -34,5 +34,7 @@ protected override void OnModelCreating(ModelBuilder builder) .HasOne(building => building.SecondaryDoor) .WithOne() .HasForeignKey("SecondaryDoorId"); + + base.OnModelCreating(builder); } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/InputValidation/ModelState/ModelStateDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/InputValidation/ModelState/ModelStateDbContext.cs index 73f3241f28..1bfdb1a28e 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/InputValidation/ModelState/ModelStateDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/InputValidation/ModelState/ModelStateDbContext.cs @@ -37,5 +37,7 @@ protected override void OnModelCreating(ModelBuilder builder) builder.Entity() .HasOne(systemDirectory => systemDirectory.AlsoSelf) .WithOne(); + + base.OnModelCreating(builder); } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/Links/LinksDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/Links/LinksDbContext.cs index ffd2333fbe..149b29b785 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/Links/LinksDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/Links/LinksDbContext.cs @@ -24,5 +24,7 @@ protected override void OnModelCreating(ModelBuilder builder) .HasOne(photo => photo.Location) .WithOne(location => location.Photo) .HasForeignKey("LocationId"); + + base.OnModelCreating(builder); } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/MultiTenancy/MultiTenancyDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/MultiTenancy/MultiTenancyDbContext.cs index 8e1fcd8350..69a6459303 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/MultiTenancy/MultiTenancyDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/MultiTenancy/MultiTenancyDbContext.cs @@ -31,5 +31,7 @@ protected override void OnModelCreating(ModelBuilder builder) builder.Entity() .HasQueryFilter(webProduct => webProduct.Shop.TenantId == _tenantProvider.TenantId); + + base.OnModelCreating(builder); } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/Filtering/FilterDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/Filtering/FilterDbContext.cs index 1b939ee9a1..35e7b4e51e 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/Filtering/FilterDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/Filtering/FilterDbContext.cs @@ -21,5 +21,7 @@ protected override void OnModelCreating(ModelBuilder builder) builder.Entity() .Property(resource => resource.SomeDateTimeInLocalZone) .HasColumnType("timestamp without time zone"); + + base.OnModelCreating(builder); } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/QueryStringDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/QueryStringDbContext.cs index 131bfe19fe..473a7428ba 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/QueryStringDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/QueryStringDbContext.cs @@ -37,5 +37,7 @@ protected override void OnModelCreating(ModelBuilder builder) .HasOne(man => man.Wife) .WithOne(woman => woman.Husband) .HasForeignKey(); + + base.OnModelCreating(builder); } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/ReadWriteDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/ReadWriteDbContext.cs index d25acf8a06..4f3d1080cf 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/ReadWriteDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ReadWrite/ReadWriteDbContext.cs @@ -49,5 +49,7 @@ protected override void OnModelCreating(ModelBuilder builder) left => left .HasOne(joinEntity => joinEntity.ToItem) .WithMany()); + + base.OnModelCreating(builder); } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/RequiredRelationships/DefaultBehaviorDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/RequiredRelationships/DefaultBehaviorDbContext.cs index 6ed4deaeff..5b0b839c63 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/RequiredRelationships/DefaultBehaviorDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/RequiredRelationships/DefaultBehaviorDbContext.cs @@ -33,5 +33,7 @@ protected override void OnModelCreating(ModelBuilder builder) .HasOne(order => order.Shipment) .WithOne(shipment => shipment.Order) .HasForeignKey("OrderId"); + + base.OnModelCreating(builder); } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Serialization/SerializationDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Serialization/SerializationDbContext.cs index 7f62a63b73..4bfb7aa709 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Serialization/SerializationDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceDefinitions/Serialization/SerializationDbContext.cs @@ -22,5 +22,7 @@ protected override void OnModelCreating(ModelBuilder builder) builder.Entity() .HasMany(scholarship => scholarship.Participants) .WithOne(student => student.Scholarship!); + + base.OnModelCreating(builder); } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/ResourceInheritanceReadTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/ResourceInheritanceReadTests.cs index 3a7b60e93d..ebca28dac9 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/ResourceInheritanceReadTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/ResourceInheritanceReadTests.cs @@ -2429,7 +2429,7 @@ public async Task Can_sort_on_derived_attribute_from_resource_definition_using_e await _testContext.RunOnDatabaseAsync(async dbContext => { - await dbContext.ClearTableAsync(); + await dbContext.ClearTableAsync(); dbContext.Wheels.AddRange(chromeWheel1, chromeWheel2, chromeWheel3, carbonWheel1, carbonWheel2); await dbContext.SaveChangesAsync(); }); @@ -2487,7 +2487,7 @@ public async Task Can_sort_on_derived_attribute_from_resource_definition_using_l await _testContext.RunOnDatabaseAsync(async dbContext => { - await dbContext.ClearTableAsync(); + await dbContext.ClearTableAsync(); dbContext.Wheels.AddRange(chromeWheel1, chromeWheel2, chromeWheel3, carbonWheel1, carbonWheel2); await dbContext.SaveChangesAsync(); }); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/TablePerType/TablePerTypeDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/TablePerType/TablePerTypeDbContext.cs index a1549e6332..7c50ee5573 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/TablePerType/TablePerTypeDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ResourceInheritance/TablePerType/TablePerTypeDbContext.cs @@ -32,5 +32,7 @@ protected override void OnModelCreating(ModelBuilder builder) builder.Entity().ToTable("GenericProperties"); builder.Entity().ToTable("StringProperties"); builder.Entity().ToTable("NumberProperties"); + + base.OnModelCreating(builder); } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/SoftDeletion/SoftDeletionDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/SoftDeletion/SoftDeletionDbContext.cs index 2a19ba74b4..3e98950be0 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/SoftDeletion/SoftDeletionDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/SoftDeletion/SoftDeletionDbContext.cs @@ -24,5 +24,7 @@ protected override void OnModelCreating(ModelBuilder builder) builder.Entity() .HasQueryFilter(department => department.SoftDeletedAt == null); + + base.OnModelCreating(builder); } } diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/ZeroKeys/ZeroKeyDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/ZeroKeys/ZeroKeyDbContext.cs index 301a7b6d7b..3e93768683 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/ZeroKeys/ZeroKeyDbContext.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/ZeroKeys/ZeroKeyDbContext.cs @@ -27,5 +27,7 @@ protected override void OnModelCreating(ModelBuilder builder) builder.Entity() .HasOne(player => player.ActiveGame) .WithMany(game => game.ActivePlayers); + + base.OnModelCreating(builder); } } diff --git a/test/TestBuildingBlocks/DbContextExtensions.cs b/test/TestBuildingBlocks/DbContextExtensions.cs index b570cbf655..5bb3f81a14 100644 --- a/test/TestBuildingBlocks/DbContextExtensions.cs +++ b/test/TestBuildingBlocks/DbContextExtensions.cs @@ -1,6 +1,5 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata; -using Npgsql; namespace TestBuildingBlocks; @@ -36,17 +35,7 @@ private static async Task ClearTablesAsync(this DbContext dbContext, params Type } string tableName = entityType.GetTableName()!; - - // PERF: We first try to clear the table, which is fast and usually succeeds, unless foreign key constraints are violated. - // In that case, we recursively delete all related data, which is slow. - try - { - await dbContext.Database.ExecuteSqlRawAsync($"delete from \"{tableName}\""); - } - catch (PostgresException) - { - await dbContext.Database.ExecuteSqlRawAsync($"truncate table \"{tableName}\" cascade"); - } + await dbContext.Database.ExecuteSqlRawAsync($"delete from \"{tableName}\""); } } } diff --git a/test/TestBuildingBlocks/TestableDbContext.cs b/test/TestBuildingBlocks/TestableDbContext.cs index d40db11c03..18ef090baa 100644 --- a/test/TestBuildingBlocks/TestableDbContext.cs +++ b/test/TestBuildingBlocks/TestableDbContext.cs @@ -1,6 +1,7 @@ using System.Diagnostics; using JsonApiDotNetCore; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.Extensions.Logging; namespace TestBuildingBlocks; @@ -17,4 +18,15 @@ protected override void OnConfiguring(DbContextOptionsBuilder builder) // Writes SQL statements to the Output Window when debugging. builder.LogTo(message => Debug.WriteLine(message), DbLoggerCategory.Database.Name.AsArray(), LogLevel.Information); } + + protected override void OnModelCreating(ModelBuilder builder) + { + foreach (IMutableForeignKey foreignKey in builder.Model.GetEntityTypes().SelectMany(entityType => entityType.GetForeignKeys())) + { + if (foreignKey.DeleteBehavior == DeleteBehavior.ClientSetNull) + { + foreignKey.DeleteBehavior = DeleteBehavior.SetNull; + } + } + } } From c7b86014c71c548264dd65634d4640be1e027d7d Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Mon, 14 Nov 2022 10:55:22 +0100 Subject: [PATCH 12/15] Start PostgreSQL service explicitly on Windows --- appveyor.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/appveyor.yml b/appveyor.yml index 196e7ff0d6..81ba53020c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -35,6 +35,9 @@ for: - image: Visual Studio 2022 services: - postgresql15 + install: + # Temporary workaround for https://help.appveyor.com/discussions/questions/60488-postgresql-version + - net start postgresql-x64-15 # REF: https://github.com/docascode/docfx-seed/blob/master/appveyor.yml before_build: - pwsh: | From 109d098fe0a6e73fe956b11e2b1cc7f49d48c2be Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Wed, 16 Nov 2022 10:27:24 +0100 Subject: [PATCH 13/15] Update to latest version of regitlint and switch to full cleanup when more than 5 batches are needed --- .config/dotnet-tools.json | 2 +- Build.ps1 | 2 +- cleanupcode.ps1 | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 7afec1e6ff..f8589a73a7 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -9,7 +9,7 @@ ] }, "regitlint": { - "version": "6.1.1", + "version": "6.2.1", "commands": [ "regitlint" ] diff --git a/Build.ps1 b/Build.ps1 index ed1363f626..9b076bc4d8 100644 --- a/Build.ps1 +++ b/Build.ps1 @@ -51,7 +51,7 @@ function RunCleanupCode { if ($baseCommitHash -ne $headCommitHash) { Write-Output "Running code cleanup on commit range $baseCommitHash..$headCommitHash in pull request." - dotnet regitlint -s JsonApiDotNetCore.sln --print-command --skip-tool-check --jb-profile="JADNC Full Cleanup" --jb --properties:Configuration=Release --jb --verbosity=WARN -f commits -a $headCommitHash -b $baseCommitHash --fail-on-diff --print-diff + dotnet regitlint -s JsonApiDotNetCore.sln --print-command --skip-tool-check --max-runs=5 --jb-profile="JADNC Full Cleanup" --jb --properties:Configuration=Release --jb --verbosity=WARN -f commits -a $headCommitHash -b $baseCommitHash --fail-on-diff --print-diff CheckLastExitCode } } diff --git a/cleanupcode.ps1 b/cleanupcode.ps1 index bab8b82af1..5740ab5a90 100644 --- a/cleanupcode.ps1 +++ b/cleanupcode.ps1 @@ -28,17 +28,17 @@ if ($revision) { if ($baseCommitHash -eq $headCommitHash) { Write-Output "Running code cleanup on staged/unstaged files." - dotnet regitlint -s JsonApiDotNetCore.sln --print-command --skip-tool-check --jb-profile="JADNC Full Cleanup" --jb --properties:Configuration=Release --jb --verbosity=WARN -f staged,modified + dotnet regitlint -s JsonApiDotNetCore.sln --print-command --skip-tool-check --max-runs=5 --jb-profile="JADNC Full Cleanup" --jb --properties:Configuration=Release --jb --verbosity=WARN -f staged,modified VerifySuccessExitCode } else { Write-Output "Running code cleanup on commit range $baseCommitHash..$headCommitHash, including staged/unstaged files." - dotnet regitlint -s JsonApiDotNetCore.sln --print-command --skip-tool-check --jb-profile="JADNC Full Cleanup" --jb --properties:Configuration=Release --jb --verbosity=WARN -f staged,modified,commits -a $headCommitHash -b $baseCommitHash + dotnet regitlint -s JsonApiDotNetCore.sln --print-command --skip-tool-check --max-runs=5 --jb-profile="JADNC Full Cleanup" --jb --properties:Configuration=Release --jb --verbosity=WARN -f staged,modified,commits -a $headCommitHash -b $baseCommitHash VerifySuccessExitCode } } else { Write-Output "Running code cleanup on all files." - dotnet regitlint -s JsonApiDotNetCore.sln --print-command --skip-tool-check --jb-profile="JADNC Full Cleanup" --jb --properties:Configuration=Release --jb --verbosity=WARN + dotnet regitlint -s JsonApiDotNetCore.sln --print-command --skip-tool-check --max-runs=5 --jb-profile="JADNC Full Cleanup" --jb --properties:Configuration=Release --jb --verbosity=WARN VerifySuccessExitCode } From 84d01c3067b95b6601868b6195838a2b21e94983 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Thu, 8 Dec 2022 13:15:53 +0100 Subject: [PATCH 14/15] Suppress typo warnings in faker-generated text --- .../Serialization/Response/ResponseModelAdapterTests.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/JsonApiDotNetCoreTests/UnitTests/Serialization/Response/ResponseModelAdapterTests.cs b/test/JsonApiDotNetCoreTests/UnitTests/Serialization/Response/ResponseModelAdapterTests.cs index d9459f7ec1..39279181dd 100644 --- a/test/JsonApiDotNetCoreTests/UnitTests/Serialization/Response/ResponseModelAdapterTests.cs +++ b/test/JsonApiDotNetCoreTests/UnitTests/Serialization/Response/ResponseModelAdapterTests.cs @@ -40,6 +40,7 @@ public void Resources_in_deeply_nested_circular_chain_are_written_in_relationshi // Assert string text = JsonSerializer.Serialize(document, new JsonSerializerOptions(options.SerializerWriteOptions)); + // ReSharper disable StringLiteralTypo text.Should().BeJson(@"{ ""data"": { ""type"": ""articles"", @@ -145,6 +146,7 @@ public void Resources_in_deeply_nested_circular_chain_are_written_in_relationshi } ] }"); + // ReSharper restore StringLiteralTypo } [Fact] @@ -177,6 +179,7 @@ public void Resources_in_deeply_nested_circular_chains_are_written_in_relationsh // Assert string text = JsonSerializer.Serialize(document, new JsonSerializerOptions(options.SerializerWriteOptions)); + // ReSharper disable StringLiteralTypo text.Should().BeJson(@"{ ""data"": [ { @@ -299,6 +302,7 @@ public void Resources_in_deeply_nested_circular_chains_are_written_in_relationsh } ] }"); + // ReSharper restore StringLiteralTypo } [Fact] @@ -335,6 +339,7 @@ public void Resources_in_overlapping_deeply_nested_circular_chains_are_written_i // Assert string text = JsonSerializer.Serialize(document, new JsonSerializerOptions(options.SerializerWriteOptions)); + // ReSharper disable StringLiteralTypo text.Should().BeJson(@"{ ""data"": { ""type"": ""articles"", @@ -514,6 +519,7 @@ public void Resources_in_overlapping_deeply_nested_circular_chains_are_written_i } ] }"); + // ReSharper restore StringLiteralTypo } [Fact] From faaba0bd878d3d00eefacdf64e0e0bb6f8ff0415 Mon Sep 17 00:00:00 2001 From: Bart Koelman <10324372+bkoelman@users.noreply.github.com> Date: Wed, 21 Dec 2022 11:00:41 +0100 Subject: [PATCH 15/15] Allow at least one constant instead of two in any() filter function --- docs/usage/reading/filtering.md | 2 +- src/JsonApiDotNetCore/Queries/Expressions/AnyExpression.cs | 7 +------ .../Queries/Internal/Parsing/FilterParser.cs | 5 ----- .../QueryStrings/Filtering/FilterOperatorTests.cs | 1 + .../UnitTests/QueryStringParameters/FilterParseTests.cs | 3 ++- 5 files changed, 5 insertions(+), 13 deletions(-) diff --git a/docs/usage/reading/filtering.md b/docs/usage/reading/filtering.md index a1c1215ccd..b99a14c3b2 100644 --- a/docs/usage/reading/filtering.md +++ b/docs/usage/reading/filtering.md @@ -196,7 +196,7 @@ matchTextExpression: ( 'contains' | 'startsWith' | 'endsWith' ) LPAREN fieldChain COMMA literalConstant RPAREN; anyExpression: - 'any' LPAREN fieldChain COMMA literalConstant ( COMMA literalConstant )+ RPAREN; + 'any' LPAREN fieldChain ( COMMA literalConstant )+ RPAREN; hasExpression: 'has' LPAREN fieldChain ( COMMA filterExpression )? RPAREN; diff --git a/src/JsonApiDotNetCore/Queries/Expressions/AnyExpression.cs b/src/JsonApiDotNetCore/Queries/Expressions/AnyExpression.cs index 2b855b1bdb..ecacc41c7c 100644 --- a/src/JsonApiDotNetCore/Queries/Expressions/AnyExpression.cs +++ b/src/JsonApiDotNetCore/Queries/Expressions/AnyExpression.cs @@ -17,12 +17,7 @@ public class AnyExpression : FilterExpression public AnyExpression(ResourceFieldChainExpression targetAttribute, IImmutableSet constants) { ArgumentGuard.NotNull(targetAttribute); - ArgumentGuard.NotNull(constants); - - if (constants.Count < 2) - { - throw new ArgumentException("At least two constants are required.", nameof(constants)); - } + ArgumentGuard.NotNullNorEmpty(constants); TargetAttribute = targetAttribute; Constants = constants; diff --git a/src/JsonApiDotNetCore/Queries/Internal/Parsing/FilterParser.cs b/src/JsonApiDotNetCore/Queries/Internal/Parsing/FilterParser.cs index c68e0f77f7..b768eb15b1 100644 --- a/src/JsonApiDotNetCore/Queries/Internal/Parsing/FilterParser.cs +++ b/src/JsonApiDotNetCore/Queries/Internal/Parsing/FilterParser.cs @@ -199,11 +199,6 @@ protected AnyExpression ParseAny() LiteralConstantExpression constant = ParseConstant(); constantsBuilder.Add(constant); - EatSingleCharacterToken(TokenKind.Comma); - - constant = ParseConstant(); - constantsBuilder.Add(constant); - while (TokenStack.TryPeek(out Token? nextToken) && nextToken.Kind == TokenKind.Comma) { EatSingleCharacterToken(TokenKind.Comma); diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/Filtering/FilterOperatorTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/Filtering/FilterOperatorTests.cs index 0abad96024..6d000f6433 100644 --- a/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/Filtering/FilterOperatorTests.cs +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/QueryStrings/Filtering/FilterOperatorTests.cs @@ -466,6 +466,7 @@ await _testContext.RunOnDatabaseAsync(async dbContext => } [Theory] + [InlineData("yes", "no", "'yes'")] [InlineData("two", "one two", "'one','two','three'")] [InlineData("two", "nine", "'one','two','three','four','five'")] public async Task Can_filter_in_set(string matchingText, string nonMatchingText, string filterText) diff --git a/test/JsonApiDotNetCoreTests/UnitTests/QueryStringParameters/FilterParseTests.cs b/test/JsonApiDotNetCoreTests/UnitTests/QueryStringParameters/FilterParseTests.cs index e471218ff6..562270a358 100644 --- a/test/JsonApiDotNetCoreTests/UnitTests/QueryStringParameters/FilterParseTests.cs +++ b/test/JsonApiDotNetCoreTests/UnitTests/QueryStringParameters/FilterParseTests.cs @@ -86,7 +86,7 @@ public void Reader_Is_Enabled(JsonApiQueryStringParameters parametersDisabled, b [InlineData("filter", "any(null,'a','b')", "Attribute 'null' does not exist on resource type 'blogs'.")] [InlineData("filter", "any('a','b','c')", "Field name expected.")] [InlineData("filter", "any(title,'b','c',)", "Value between quotes expected.")] - [InlineData("filter", "any(title,'b')", ", expected.")] + [InlineData("filter", "any(title)", ", expected.")] [InlineData("filter[posts]", "any(author,'a','b')", "Attribute 'author' does not exist on resource type 'blogPosts'.")] [InlineData("filter", "and(", "Filter function expected.")] [InlineData("filter", "or(equals(title,'some'),equals(title,'other')", ") expected.")] @@ -146,6 +146,7 @@ public void Reader_Read_Fails(string parameterName, string parameterValue, strin [InlineData("filter", "contains(title,'this')", null, "contains(title,'this')")] [InlineData("filter", "startsWith(title,'this')", null, "startsWith(title,'this')")] [InlineData("filter", "endsWith(title,'this')", null, "endsWith(title,'this')")] + [InlineData("filter", "any(title,'this')", null, "any(title,'this')")] [InlineData("filter", "any(title,'this','that','there')", null, "any(title,'that','there','this')")] [InlineData("filter", "and(contains(title,'sales'),contains(title,'marketing'),contains(title,'advertising'))", null, "and(contains(title,'sales'),contains(title,'marketing'),contains(title,'advertising'))")]