diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 1dc9384515..750c0476bc 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -127,10 +127,13 @@ jobs:
dotnet build --no-restore --configuration Release /p:VersionSuffix=$env:PACKAGE_VERSION_SUFFIX
- name: Test
run: |
- dotnet test --no-build --configuration Release --collect:"XPlat Code Coverage" --logger "GitHubActions;summary.includeSkippedTests=true" -- RunConfiguration.CollectSourceInformation=true DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.DeterministicReport=true
+ dotnet test --no-build --configuration Release --collect:"XPlat Code Coverage" --logger "GitHubActions;summary.includeSkippedTests=true"
- name: Upload coverage to codecov.io
if: matrix.os == 'ubuntu-latest'
uses: codecov/codecov-action@v3
+ with:
+ fail_ci_if_error: true
+ verbose: true
- name: Generate packages
shell: pwsh
run: |
diff --git a/Build.ps1 b/Build.ps1
index 4854651d67..3abc926e6a 100644
--- a/Build.ps1
+++ b/Build.ps1
@@ -10,13 +10,16 @@ Write-Host "$(pwsh --version)"
Write-Host "Active .NET SDK: $(dotnet --version)"
Write-Host "Using version suffix: $versionSuffix"
+Remove-Item -Recurse -Force artifacts -ErrorAction SilentlyContinue
+Remove-Item -Recurse -Force * -Include coverage.cobertura.xml
+
dotnet tool restore
VerifySuccessExitCode
dotnet build --configuration Release /p:VersionSuffix=$versionSuffix
VerifySuccessExitCode
-dotnet test --no-build --configuration Release --collect:"XPlat Code Coverage" -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.DeterministicReport=true
+dotnet test --no-build --configuration Release --collect:"XPlat Code Coverage"
VerifySuccessExitCode
dotnet reportgenerator -reports:**\coverage.cobertura.xml -targetdir:artifacts\coverage -filefilters:-*.g.cs
diff --git a/Directory.Build.props b/Directory.Build.props
index 73e1b54135..90de08a271 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -53,6 +53,7 @@
false
false
$(MSBuildThisFileDirectory)CodingGuidelines.ruleset
+ $(MSBuildThisFileDirectory)tests.runsettings
5.4.1
diff --git a/JsonApiDotNetCore.sln b/JsonApiDotNetCore.sln
index 2f8e9f9127..e10df8567a 100644
--- a/JsonApiDotNetCore.sln
+++ b/JsonApiDotNetCore.sln
@@ -13,6 +13,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
CodingGuidelines.ruleset = CodingGuidelines.ruleset
CSharpGuidelinesAnalyzer.config = CSharpGuidelinesAnalyzer.config
Directory.Build.props = Directory.Build.props
+ tests.runsettings = tests.runsettings
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{026FBC6C-AF76-4568-9B87-EC73457899FD}"
diff --git a/test/NoEntityFrameworkTests/TagTests.cs b/test/NoEntityFrameworkTests/TagTests.cs
new file mode 100644
index 0000000000..83842b7725
--- /dev/null
+++ b/test/NoEntityFrameworkTests/TagTests.cs
@@ -0,0 +1,229 @@
+using System.Net;
+using System.Text.Json;
+using FluentAssertions;
+using JsonApiDotNetCore.Configuration;
+using JsonApiDotNetCore.Serialization.Objects;
+using Microsoft.Extensions.DependencyInjection;
+using NoEntityFrameworkExample.Models;
+using TestBuildingBlocks;
+using Xunit;
+
+namespace NoEntityFrameworkTests;
+
+public sealed class TagTests : IntegrationTest, IClassFixture>
+{
+ private readonly NoLoggingWebApplicationFactory _factory;
+
+ protected override JsonSerializerOptions SerializerOptions
+ {
+ get
+ {
+ var options = _factory.Services.GetRequiredService();
+ return options.SerializerOptions;
+ }
+ }
+
+ public TagTests(NoLoggingWebApplicationFactory factory)
+ {
+ _factory = factory;
+ }
+
+ [Fact]
+ public async Task Can_get_primary_resources()
+ {
+ // Arrange
+ const string route = "/api/tags";
+
+ // Act
+ (HttpResponseMessage httpResponse, Document responseDocument) = await ExecuteGetAsync(route);
+
+ // Assert
+ httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK);
+
+ responseDocument.Data.ManyValue.ShouldHaveCount(3);
+
+ responseDocument.Meta.Should().ContainTotal(3);
+ }
+
+ [Fact]
+ public async Task Can_filter_in_primary_resources()
+ {
+ // Arrange
+ const string route = "/api/tags?filter=equals(name,'Personal')";
+
+ // Act
+ (HttpResponseMessage httpResponse, Document responseDocument) = await ExecuteGetAsync(route);
+
+ // Assert
+ httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK);
+
+ responseDocument.Data.ManyValue.ShouldHaveCount(1);
+ responseDocument.Data.ManyValue[0].Attributes.ShouldContainKey("name").With(value => value.Should().Be("Personal"));
+
+ responseDocument.Meta.Should().ContainTotal(1);
+ }
+
+ [Fact]
+ public async Task Can_filter_in_related_resources()
+ {
+ // Arrange
+ const string route = "/api/tags?filter=has(todoItems,equals(description,'Check emails'))";
+
+ // Act
+ (HttpResponseMessage httpResponse, Document responseDocument) = await ExecuteGetAsync(route);
+
+ // Assert
+ httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK);
+
+ responseDocument.Data.ManyValue.ShouldHaveCount(1);
+ responseDocument.Data.ManyValue[0].Attributes.ShouldContainKey("name").With(value => value.Should().Be("Business"));
+
+ responseDocument.Meta.Should().ContainTotal(1);
+ }
+
+ [Fact]
+ public async Task Can_sort_on_attribute_in_primary_resources()
+ {
+ // Arrange
+ const string route = "/api/tags?sort=-id";
+
+ // Act
+ (HttpResponseMessage httpResponse, Document responseDocument) = await ExecuteGetAsync(route);
+
+ // Assert
+ httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK);
+
+ responseDocument.Data.ManyValue.ShouldHaveCount(3);
+ responseDocument.Data.ManyValue[0].Id.Should().Be("3");
+ responseDocument.Data.ManyValue[1].Id.Should().Be("2");
+ responseDocument.Data.ManyValue[2].Id.Should().Be("1");
+ }
+
+ [Fact]
+ public async Task Can_sort_on_count_in_primary_resources()
+ {
+ // Arrange
+ const string route = "/api/tags?sort=-count(todoItems),id";
+
+ // Act
+ (HttpResponseMessage httpResponse, Document responseDocument) = await ExecuteGetAsync(route);
+
+ // Assert
+ httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK);
+
+ responseDocument.Data.ManyValue.ShouldHaveCount(3);
+ responseDocument.Data.ManyValue[0].Id.Should().Be("1");
+ responseDocument.Data.ManyValue[1].Id.Should().Be("2");
+ responseDocument.Data.ManyValue[2].Id.Should().Be("3");
+ }
+
+ [Fact]
+ public async Task Can_paginate_in_primary_resources()
+ {
+ // Arrange
+ const string route = "/api/tags?page[size]=1&page[number]=2&sort=id";
+
+ // Act
+ (HttpResponseMessage httpResponse, Document responseDocument) = await ExecuteGetAsync(route);
+
+ // Assert
+ httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK);
+
+ responseDocument.Data.ManyValue.ShouldHaveCount(1);
+ responseDocument.Data.ManyValue[0].Attributes.ShouldContainKey("name").With(value => value.Should().Be("Family"));
+
+ responseDocument.Meta.Should().ContainTotal(3);
+ }
+
+ [Fact]
+ public async Task Can_select_fields_in_primary_resources()
+ {
+ // Arrange
+ const string route = "/api/tags?fields[tags]=todoItems";
+
+ // Act
+ (HttpResponseMessage httpResponse, Document responseDocument) = await ExecuteGetAsync(route);
+
+ // Assert
+ httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK);
+
+ responseDocument.Data.ManyValue.ShouldNotBeEmpty();
+ responseDocument.Data.ManyValue.Should().AllSatisfy(resource => resource.Attributes.Should().BeNull());
+ responseDocument.Data.ManyValue.Should().AllSatisfy(resource => resource.Relationships.ShouldOnlyContainKeys("todoItems"));
+ }
+
+ [Fact]
+ public async Task Can_include_in_primary_resources()
+ {
+ // Arrange
+ const string route = "/api/tags?include=todoItems.owner";
+
+ // Act
+ (HttpResponseMessage httpResponse, Document responseDocument) = await ExecuteGetAsync(route);
+
+ // Assert
+ httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK);
+
+ responseDocument.Data.ManyValue.Should().NotBeEmpty();
+ responseDocument.Included.Should().NotBeEmpty();
+ }
+
+ [Fact]
+ public async Task Can_get_primary_resource()
+ {
+ // Arrange
+ const string route = "/api/tags/1";
+
+ // Act
+ (HttpResponseMessage httpResponse, Document responseDocument) = await ExecuteGetAsync(route);
+
+ // Assert
+ httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK);
+
+ responseDocument.Data.SingleValue.ShouldNotBeNull();
+ responseDocument.Data.SingleValue.Id.Should().Be("1");
+ }
+
+ [Fact]
+ public async Task Can_get_secondary_resources()
+ {
+ // Arrange
+ const string route = "/api/tags/1/todoItems?sort=id";
+
+ // Act
+ (HttpResponseMessage httpResponse, Document responseDocument) = await ExecuteGetAsync(route);
+
+ // Assert
+ httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK);
+
+ responseDocument.Data.ManyValue.ShouldHaveCount(3);
+ responseDocument.Data.ManyValue[0].Attributes.ShouldContainKey("description").With(value => value.Should().Be("Make homework"));
+ responseDocument.Data.ManyValue[1].Attributes.ShouldContainKey("description").With(value => value.Should().Be("Book vacation"));
+ responseDocument.Data.ManyValue[2].Attributes.ShouldContainKey("description").With(value => value.Should().Be("Cook dinner"));
+
+ responseDocument.Meta.Should().ContainTotal(3);
+ }
+
+ [Fact]
+ public async Task Can_get_ToMany_relationship()
+ {
+ // Arrange
+ const string route = "/api/tags/2/relationships/todoItems";
+
+ // Act
+ (HttpResponseMessage httpResponse, Document responseDocument) = await ExecuteGetAsync(route);
+
+ // Assert
+ httpResponse.ShouldHaveStatusCode(HttpStatusCode.OK);
+
+ responseDocument.Data.ManyValue.ShouldHaveCount(1);
+ responseDocument.Data.ManyValue[0].Id.Should().Be("3");
+
+ responseDocument.Meta.Should().ContainTotal(1);
+ }
+
+ protected override HttpClient CreateClient()
+ {
+ return _factory.CreateClient();
+ }
+}
diff --git a/test/TestBuildingBlocks/AssemblyInfo.cs b/test/TestBuildingBlocks/AssemblyInfo.cs
index 82d12912a4..2af69d7b71 100644
--- a/test/TestBuildingBlocks/AssemblyInfo.cs
+++ b/test/TestBuildingBlocks/AssemblyInfo.cs
@@ -1,4 +1,4 @@
using System.Diagnostics.CodeAnalysis;
-// https://github.com/coverlet-coverage/coverlet/blob/master/Documentation/MSBuildIntegration.md#excluding-from-coverage
+// Justification: This assembly contains building blocks for writing tests. It does not contain code that ships.
[assembly: ExcludeFromCodeCoverage]
diff --git a/tests.runsettings b/tests.runsettings
new file mode 100644
index 0000000000..db83eb983e
--- /dev/null
+++ b/tests.runsettings
@@ -0,0 +1,16 @@
+
+
+
+ true
+
+
+
+
+
+ ObsoleteAttribute,GeneratedCodeAttribute
+ true
+
+
+
+
+