diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/Blobs/BlobDbContext.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/Blobs/BlobDbContext.cs new file mode 100644 index 0000000000..cadbb08b56 --- /dev/null +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/Blobs/BlobDbContext.cs @@ -0,0 +1,15 @@ +using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore; + +namespace JsonApiDotNetCoreTests.IntegrationTests.Blobs; + +[UsedImplicitly(ImplicitUseTargetFlags.Members)] +public sealed class BlobDbContext : DbContext +{ + public DbSet ImageContainers => Set(); + + public BlobDbContext(DbContextOptions options) + : base(options) + { + } +} diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/Blobs/BlobFakers.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/Blobs/BlobFakers.cs new file mode 100644 index 0000000000..924b67dfa3 --- /dev/null +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/Blobs/BlobFakers.cs @@ -0,0 +1,19 @@ +using Bogus; +using TestBuildingBlocks; + +// @formatter:wrap_chained_method_calls chop_always +// @formatter:keep_existing_linebreaks true + +namespace JsonApiDotNetCoreTests.IntegrationTests.Blobs; + +internal sealed class BlobFakers : FakerContainer +{ + private readonly Lazy> _lazyImageContainerFaker = new(() => + new Faker() + .UseSeed(GetFakerSeed()) + .RuleFor(imageContainer => imageContainer.FileName, faker => faker.System.FileName()) + .RuleFor(imageContainer => imageContainer.Data, faker => faker.Random.Bytes(128)) + .RuleFor(imageContainer => imageContainer.Thumbnail, faker => faker.Random.Bytes(64))); + + public Faker ImageContainer => _lazyImageContainerFaker.Value; +} diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/Blobs/BlobTests.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/Blobs/BlobTests.cs new file mode 100644 index 0000000000..67ca34d83c --- /dev/null +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/Blobs/BlobTests.cs @@ -0,0 +1,256 @@ +using System.Net; +using FluentAssertions; +using JsonApiDotNetCore.Resources; +using JsonApiDotNetCore.Serialization.Objects; +using Microsoft.Extensions.DependencyInjection; +using TestBuildingBlocks; +using Xunit; + +namespace JsonApiDotNetCoreTests.IntegrationTests.Blobs; + +public sealed class BlobTests : IClassFixture, BlobDbContext>> +{ + private readonly IntegrationTestContext, BlobDbContext> _testContext; + private readonly BlobFakers _fakers = new(); + + public BlobTests(IntegrationTestContext, BlobDbContext> testContext) + { + _testContext = testContext; + + testContext.UseController(); + + testContext.ConfigureServicesAfterStartup(services => + { + services.AddScoped(typeof(IResourceChangeTracker<>), typeof(NeverSameResourceChangeTracker<>)); + }); + } + + [Fact] + public async Task Can_get_primary_resource_by_ID() + { + // Arrange + ImageContainer container = _fakers.ImageContainer.Generate(); + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + dbContext.ImageContainers.Add(container); + await dbContext.SaveChangesAsync(); + }); + + string route = $"/imageContainers/{container.StringId}"; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecuteGetAsync(route); + + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); + + responseDocument.Data.SingleValue.ShouldNotBeNull(); + responseDocument.Data.SingleValue.Type.Should().Be("imageContainers"); + responseDocument.Data.SingleValue.Id.Should().Be(container.StringId); + responseDocument.Data.SingleValue.Attributes.ShouldContainKey("fileName").With(value => value.Should().Be(container.FileName)); + responseDocument.Data.SingleValue.Attributes.ShouldContainKey("data").As().With(value => value.Should().Equal(container.Data)); + responseDocument.Data.SingleValue.Attributes.ShouldContainKey("thumbnail").As().With(value => value.Should().Equal(container.Thumbnail)); + responseDocument.Data.SingleValue.Relationships.Should().BeNull(); + } + + [Fact] + public async Task Can_create_resource() + { + // Arrange + ImageContainer newContainer = _fakers.ImageContainer.Generate(); + + var requestBody = new + { + data = new + { + type = "imageContainers", + attributes = new + { + fileName = newContainer.FileName, + data = Convert.ToBase64String(newContainer.Data), + thumbnail = Convert.ToBase64String(newContainer.Thumbnail!) + } + } + }; + + const string route = "/imageContainers"; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePostAsync(route, requestBody); + + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.Created); + + responseDocument.Data.SingleValue.ShouldNotBeNull(); + responseDocument.Data.SingleValue.Type.Should().Be("imageContainers"); + responseDocument.Data.SingleValue.Attributes.ShouldContainKey("fileName").With(value => value.Should().Be(newContainer.FileName)); + responseDocument.Data.SingleValue.Attributes.ShouldContainKey("data").As().With(value => value.Should().Equal(newContainer.Data)); + responseDocument.Data.SingleValue.Attributes.ShouldContainKey("thumbnail").As().With(value => value.Should().Equal(newContainer.Thumbnail)); + responseDocument.Data.SingleValue.Relationships.Should().BeNull(); + + long newContainerId = long.Parse(responseDocument.Data.SingleValue.Id.ShouldNotBeNull()); + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + ImageContainer containerInDatabase = await dbContext.ImageContainers.FirstWithIdAsync(newContainerId); + + containerInDatabase.FileName.Should().Be(newContainer.FileName); + containerInDatabase.Data.Should().Equal(newContainer.Data); + containerInDatabase.Thumbnail.Should().Equal(newContainer.Thumbnail); + }); + } + + [Fact] + public async Task Can_update_resource() + { + // Arrange + ImageContainer existingContainer = _fakers.ImageContainer.Generate(); + + byte[] newData = _fakers.ImageContainer.Generate().Data; + byte[] newThumbnail = _fakers.ImageContainer.Generate().Thumbnail!; + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + dbContext.ImageContainers.Add(existingContainer); + await dbContext.SaveChangesAsync(); + }); + + var requestBody = new + { + data = new + { + type = "imageContainers", + id = existingContainer.StringId, + attributes = new + { + data = Convert.ToBase64String(newData), + thumbnail = Convert.ToBase64String(newThumbnail) + } + } + }; + + string route = $"/imageContainers/{existingContainer.StringId}"; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); + + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); + + responseDocument.Data.SingleValue.ShouldNotBeNull(); + responseDocument.Data.SingleValue.Type.Should().Be("imageContainers"); + responseDocument.Data.SingleValue.Id.Should().Be(existingContainer.StringId); + responseDocument.Data.SingleValue.Attributes.ShouldContainKey("fileName").With(value => value.Should().Be(existingContainer.FileName)); + responseDocument.Data.SingleValue.Attributes.ShouldContainKey("data").As().With(value => value.Should().Equal(newData)); + responseDocument.Data.SingleValue.Attributes.ShouldContainKey("thumbnail").As().With(value => value.Should().Equal(newThumbnail)); + responseDocument.Data.SingleValue.Relationships.Should().BeNull(); + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + ImageContainer containerInDatabase = await dbContext.ImageContainers.FirstWithIdAsync(existingContainer.Id); + + containerInDatabase.FileName.Should().Be(existingContainer.FileName); + containerInDatabase.Data.Should().Equal(newData); + containerInDatabase.Thumbnail.Should().Equal(newThumbnail); + }); + } + + [Fact] + public async Task Can_update_resource_with_empty_blob() + { + // Arrange + ImageContainer existingContainer = _fakers.ImageContainer.Generate(); + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + dbContext.ImageContainers.Add(existingContainer); + await dbContext.SaveChangesAsync(); + }); + + var requestBody = new + { + data = new + { + type = "imageContainers", + id = existingContainer.StringId, + attributes = new + { + data = string.Empty + } + } + }; + + string route = $"/imageContainers/{existingContainer.StringId}"; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); + + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); + + responseDocument.Data.SingleValue.ShouldNotBeNull(); + responseDocument.Data.SingleValue.Type.Should().Be("imageContainers"); + responseDocument.Data.SingleValue.Id.Should().Be(existingContainer.StringId); + responseDocument.Data.SingleValue.Attributes.ShouldContainKey("fileName").With(value => value.Should().Be(existingContainer.FileName)); + responseDocument.Data.SingleValue.Attributes.ShouldContainKey("data").As().With(value => value.Should().BeEmpty()); + responseDocument.Data.SingleValue.Relationships.Should().BeNull(); + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + ImageContainer containerInDatabase = await dbContext.ImageContainers.FirstWithIdAsync(existingContainer.Id); + + containerInDatabase.FileName.Should().Be(existingContainer.FileName); + containerInDatabase.Data.Should().BeEmpty(); + }); + } + + [Fact] + public async Task Can_update_resource_with_null_blob() + { + // Arrange + ImageContainer existingContainer = _fakers.ImageContainer.Generate(); + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + dbContext.ImageContainers.Add(existingContainer); + await dbContext.SaveChangesAsync(); + }); + + var requestBody = new + { + data = new + { + type = "imageContainers", + id = existingContainer.StringId, + attributes = new + { + thumbnail = (object?)null + } + } + }; + + string route = $"/imageContainers/{existingContainer.StringId}"; + + // Act + (HttpResponseMessage httpResponse, Document responseDocument) = await _testContext.ExecutePatchAsync(route, requestBody); + + // Assert + httpResponse.Should().HaveStatusCode(HttpStatusCode.OK); + + responseDocument.Data.SingleValue.ShouldNotBeNull(); + responseDocument.Data.SingleValue.Type.Should().Be("imageContainers"); + responseDocument.Data.SingleValue.Id.Should().Be(existingContainer.StringId); + responseDocument.Data.SingleValue.Attributes.ShouldContainKey("fileName").With(value => value.Should().Be(existingContainer.FileName)); + responseDocument.Data.SingleValue.Attributes.ShouldContainKey("thumbnail").With(value => value.Should().BeNull()); + responseDocument.Data.SingleValue.Relationships.Should().BeNull(); + + await _testContext.RunOnDatabaseAsync(async dbContext => + { + ImageContainer containerInDatabase = await dbContext.ImageContainers.FirstWithIdAsync(existingContainer.Id); + + containerInDatabase.FileName.Should().Be(existingContainer.FileName); + containerInDatabase.Thumbnail.Should().BeNull(); + }); + } +} diff --git a/test/JsonApiDotNetCoreTests/IntegrationTests/Blobs/ImageContainer.cs b/test/JsonApiDotNetCoreTests/IntegrationTests/Blobs/ImageContainer.cs new file mode 100644 index 0000000000..4ad0e54d5a --- /dev/null +++ b/test/JsonApiDotNetCoreTests/IntegrationTests/Blobs/ImageContainer.cs @@ -0,0 +1,19 @@ +using JetBrains.Annotations; +using JsonApiDotNetCore.Resources; +using JsonApiDotNetCore.Resources.Annotations; + +namespace JsonApiDotNetCoreTests.IntegrationTests.Blobs; + +[UsedImplicitly(ImplicitUseTargetFlags.Members)] +[Resource(ControllerNamespace = "JsonApiDotNetCoreTests.IntegrationTests.Blobs")] +public sealed class ImageContainer : Identifiable +{ + [Attr] + public string FileName { get; set; } = null!; + + [Attr] + public byte[] Data { get; set; } = Array.Empty(); + + [Attr] + public byte[]? Thumbnail { get; set; } +}