Skip to content

Commit

Permalink
Fixing image uploads. Closes #33
Browse files Browse the repository at this point in the history
  • Loading branch information
jezzsantos committed May 12, 2024
1 parent a09ce8e commit 3581cd0
Show file tree
Hide file tree
Showing 18 changed files with 181 additions and 85 deletions.
30 changes: 29 additions & 1 deletion src/Application.Resources.Shared/Images.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Net.Http.Headers;
using Application.Interfaces.Resources;
using Common.Extensions;

namespace Application.Resources.Shared;

Expand All @@ -19,13 +21,39 @@ public class FileUpload
{
public required Stream Content { get; set; }

public required string ContentType { get; set; }
public required FileUploadContentType ContentType { get; set; }

public string? Filename { get; set; }

public long Size { get; set; }
}

public class FileUploadContentType
{
public string? Charset { get; set; }

public string? MediaType { get; set; }

public static FileUploadContentType FromContentType(string contentType)
{
if (contentType.HasNoValue())
{
return new FileUploadContentType();
}

if (MediaTypeHeaderValue.TryParse(contentType, out var parsed))
{
return new FileUploadContentType
{
MediaType = parsed.MediaType,
Charset = parsed.CharSet
};
}

return new FileUploadContentType();
}
}

public class ImageDownload
{
public required string ContentType { get; set; }
Expand Down
12 changes: 9 additions & 3 deletions src/IdentityApplication/PasswordCredentialsApplication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -366,8 +366,7 @@ public async Task<Result<Error>> CompletePasswordResetAsync(ICallerContext calle

#if TESTINGONLY
public async Task<Result<PasswordCredentialConfirmation, Error>> GetPersonRegistrationConfirmationAsync(
ICallerContext caller, string userId,
CancellationToken cancellationToken)
ICallerContext caller, string userId, CancellationToken cancellationToken)
{
var retrieved = await _repository.FindCredentialsByUserIdAsync(userId.ToId(), cancellationToken);
if (retrieved.IsFailure)
Expand All @@ -379,8 +378,15 @@ public async Task<Result<PasswordCredentialConfirmation, Error>> GetPersonRegist
{
return Error.EntityNotFound();
}

var credential = retrieved.Value.Value;
var token = credential.VerificationKeep.Token;

if (!token.HasValue)
{
return Error.PreconditionViolation(Resources.PasswordCredentialsApplication_RegistrationAlreadyVerified);
}

return new PasswordCredentialConfirmation
{
Token = credential.VerificationKeep.Token,
Expand Down
9 changes: 9 additions & 0 deletions src/IdentityApplication/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions src/IdentityApplication/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@
<data name="PasswordCredentialsApplication_RegistrationNotVerified" xml:space="preserve">
<value>The user account has not been verified</value>
</data>
<data name="PasswordCredentialsApplication_RegistrationAlreadyVerified" xml:space="preserve">
<value>The user account has already been verified</value>
</data>
<data name="PasswordCredentialsApplication_InvalidUsername" xml:space="preserve">
<value>The username is not a valid email address</value>
</data>
Expand Down
2 changes: 1 addition & 1 deletion src/ImagesApplication.UnitTests/ImagesApplicationSpec.cs
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ public async Task WhenUploadImage_ThenReturns()
var upload = new FileUpload
{
Content = content,
ContentType = "image/jpeg",
ContentType = new FileUploadContentType { MediaType = "image/jpeg" },
Filename = "afilename",
Size = 99
};
Expand Down
8 changes: 4 additions & 4 deletions src/ImagesApplication/ImagesApplication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,9 @@ public async Task<Result<Image, Error>> UpdateImageAsync(ICallerContext caller,
}

public async Task<Result<Image, Error>> UploadImageAsync(ICallerContext caller, FileUpload upload,
string? description,
CancellationToken cancellationToken)
string? description, CancellationToken cancellationToken)
{
var created = ImageRoot.Create(_recorder, _idFactory, upload.ContentType);
var created = ImageRoot.Create(_recorder, _idFactory, upload.ContentType.MediaType!);
if (created.IsFailure)
{
return created.Error;
Expand All @@ -152,7 +151,8 @@ public async Task<Result<Image, Error>> UploadImageAsync(ICallerContext caller,
}

var uploaded =
await _repository.UploadImageAsync(image.Id, upload.ContentType, upload.Content, cancellationToken);
await _repository.UploadImageAsync(image.Id, upload.ContentType.MediaType!, upload.Content,
cancellationToken);
if (uploaded.IsFailure)
{
return uploaded.Error;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ public async Task WhenRequestRegisteredAvatar_ReturnsAvatar()
await _serviceClient.FindAvatarAsync(new TestCaller(), "[email protected]", CancellationToken.None);

result.Should().BeSuccess();
result.Value.Value.ContentType.Should().Be(HttpConstants.ContentTypes.ImagePng);
result.Value.Value.ContentType.Charset.Should().BeNull();
result.Value.Value.ContentType.MediaType.Should().Be(HttpConstants.ContentTypes.ImagePng);
result.Value.Value.Content.Should().NotBeNull();
result.Value.Value.Filename.Should().BeNull();
result.Value.Value.Size.Should().Be(156531);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Net;
using Application.Interfaces;
using Application.Resources.Shared;
using Common;
using FluentAssertions;
using Infrastructure.Shared.ApplicationServices.External;
Expand Down Expand Up @@ -90,15 +91,16 @@ public async Task WhenFindAvatarAsyncAndReturnsImage_ThenReturnsUpload()
{
StatusCode = HttpStatusCode.OK,
Content = stream,
ContentType = "acontenttype",
ContentType = HttpConstants.ContentTypes.ImageJpegWithCharset,
ContentLength = 999
});

var result =
await _client.FindAvatarAsync(_caller.Object, "anemailaddress", CancellationToken.None);

result.Should().BeSuccess();
result.Value.Value.ContentType.Should().Be("acontenttype");
result.Value.Value.ContentType.Should()
.BeEquivalentTo(FileUploadContentType.FromContentType(HttpConstants.ContentTypes.ImageJpegWithCharset));
result.Value.Value.Content.Should().BeSameAs(stream);
result.Value.Value.Filename.Should().BeNull();
result.Value.Value.Size.Should().Be(999);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public async Task WhenFindAvatarAsync_ThenReturnsUpload()
It.IsAny<CancellationToken>()))
.ReturnsAsync(new FileUpload
{
ContentType = "acontenttype",
ContentType = new FileUploadContentType { MediaType = "acontenttype" },
Content = stream,
Filename = "afilename",
Size = 99
Expand All @@ -57,7 +57,7 @@ public async Task WhenFindAvatarAsync_ThenReturnsUpload()

result.Should().BeSuccess();
result.Value.HasValue.Should().BeTrue();
result.Value.Value.ContentType.Should().Be("acontenttype");
result.Value.Value.ContentType.MediaType.Should().Be("acontenttype");
result.Value.Value.Content.Should().BeSameAs(stream);
result.Value.Value.Filename.Should().Be("afilename");
result.Value.Value.Size.Should().Be(99);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ public async Task<Result<Optional<FileUpload>, Error>> FindAvatarAsync(ICallerCo

return new FileUpload
{
ContentType = contentType,
ContentType = FileUploadContentType.FromContentType(contentType),
Content = content,
Filename = null,
Size = contentLength
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public void WhenGetUploadedFileAndNoContent_ThenReturnsError()
new()
{
Content = stream,
ContentType = "acontenttype",
ContentType = new FileUploadContentType { MediaType = "acontenttype" },
Size = 0,
Filename = "afilename"
}
Expand All @@ -63,7 +63,7 @@ public void WhenGetUploadedFileAndTooLarge_ThenReturnsError()
new()
{
Content = stream,
ContentType = "acontenttype",
ContentType = new FileUploadContentType { MediaType = "acontenttype" },
Size = content.Length,
Filename = "afilename"
}
Expand All @@ -76,7 +76,7 @@ public void WhenGetUploadedFileAndTooLarge_ThenReturnsError()
}

[Fact]
public void WhenGetUploadedFileAndNoAllowedContentTypes_ThenReturnsError()
public void WhenGetUploadedFileAndNotAllowedContentTypes_ThenReturnsError()
{
var content = new byte[] { 0x0, 0x1 };
using var stream = new MemoryStream(content);
Expand All @@ -85,7 +85,7 @@ public void WhenGetUploadedFileAndNoAllowedContentTypes_ThenReturnsError()
new()
{
Content = stream,
ContentType = "acontenttype",
ContentType = new FileUploadContentType { MediaType = "acontenttype" },
Size = content.Length,
Filename = "afilename"
}
Expand Down Expand Up @@ -131,7 +131,7 @@ public void WhenGetUploadedFileAndNoContentType_ThenReturnsError()
var result = _service.GetUploadedFile(uploads, 100, new List<string> { "allowed" });

result.Should().BeError(ErrorCode.Validation,
Resources.FileUploadService_DisallowedFileContent.Format(FileUploadService.UnknownContentType));
Resources.FileUploadService_DisallowedFileContent.Format(FileUploadService.UnknownMediaType));
}

[Fact]
Expand All @@ -144,7 +144,7 @@ public void WhenGetUploadedFileAndNotAllowedContentType_ThenReturnsError()
new()
{
Content = stream,
ContentType = "notallowed",
ContentType = new FileUploadContentType { MediaType = "notallowed" },
Size = content.Length,
Filename = "afilename"
}
Expand All @@ -166,16 +166,18 @@ public void WhenGetUploadedFileAndAllowedContentType_ThenReturns()
new()
{
Content = stream,
ContentType = "allowed",
ContentType = FileUploadContentType.FromContentType(HttpConstants.ContentTypes.ImageJpeg),
Size = content.Length,
Filename = "afilename"
}
};

var result = _service.GetUploadedFile(uploads, 100, new List<string> { "allowed" });
var result =
_service.GetUploadedFile(uploads, 100, new List<string> { HttpConstants.ContentTypes.ImageJpeg });

result.Should().BeSuccess();
result.Value.ContentType.Should().Be("allowed");
result.Value.ContentType.Should()
.BeEquivalentTo(FileUploadContentType.FromContentType(HttpConstants.ContentTypes.ImageJpeg));
result.Value.Filename.Should().Be("afilename");
result.Value.Size.Should().Be(2);
result.Value.Content.Position.Should().Be(0);
Expand Down Expand Up @@ -203,7 +205,7 @@ public void WhenGetUploadedFileAndNotAllowedContentType_ReturnsError()
new()
{
Content = stream,
ContentType = HttpConstants.ContentTypes.ImageJpeg,
ContentType = FileUploadContentType.FromContentType(HttpConstants.ContentTypes.ImageJpeg),
Size = content.Length,
Filename = "afilename"
}
Expand All @@ -226,7 +228,7 @@ public void WhenGetUploadedFileAndContentTypeAndContentDiffers_ReturnsError()
new()
{
Content = stream,
ContentType = HttpConstants.ContentTypes.ImagePng,
ContentType = FileUploadContentType.FromContentType(HttpConstants.ContentTypes.ImagePng),
Size = content.Length,
Filename = "afilename"
}
Expand All @@ -251,7 +253,7 @@ public void WhenGetUploadedFileAndFilenameExtensionDiffers_ThenReturnsError()
new()
{
Content = stream,
ContentType = HttpConstants.ContentTypes.ImageJpeg,
ContentType = FileUploadContentType.FromContentType(HttpConstants.ContentTypes.ImageJpeg),
Size = content.Length,
Filename = "afilename.txt"
}
Expand All @@ -275,7 +277,7 @@ public void WhenGetUploadedFileAndFileExtensionMissing_ThenSetsExtension()
new()
{
Content = stream,
ContentType = HttpConstants.ContentTypes.ImageJpeg,
ContentType = FileUploadContentType.FromContentType(HttpConstants.ContentTypes.ImageJpeg),
Size = content.Length,
Filename = "afilename"
}
Expand All @@ -285,7 +287,8 @@ public void WhenGetUploadedFileAndFileExtensionMissing_ThenSetsExtension()
_service.GetUploadedFile(uploads, 1000, new List<string> { HttpConstants.ContentTypes.ImageJpeg });

result.Should().BeSuccess();
result.Value.ContentType.Should().Be(HttpConstants.ContentTypes.ImageJpeg);
result.Value.ContentType.Should()
.BeEquivalentTo(FileUploadContentType.FromContentType(HttpConstants.ContentTypes.ImageJpeg));
result.Value.Filename.Should().Be("afilename.jpg");
result.Value.Size.Should().Be(103);
result.Value.Content.Position.Should().Be(0);
Expand All @@ -312,7 +315,37 @@ public void WhenGetUploadedFileAndContentTypeMissing_ThenSetsContentType()
_service.GetUploadedFile(uploads, 1000, new List<string> { HttpConstants.ContentTypes.ImageJpeg });

result.Should().BeSuccess();
result.Value.ContentType.Should().Be(HttpConstants.ContentTypes.ImageJpeg);
result.Value.ContentType.Should()
.BeEquivalentTo(FileUploadContentType.FromContentType(HttpConstants.ContentTypes.ImageJpeg));
result.Value.Filename.Should().Be("afilename.jpg");
result.Value.Size.Should().Be(103L);
result.Value.Content.Position.Should().Be(0);
}

[Fact]
public void WhenGetUploadedWithEncoding_ThenReturns()
{
var content = FileUploadService.ImageJpegMagicBytes.Concat(Enumerable.Repeat((byte)0x01, 100))
.ToArray();
using var stream = new MemoryStream(content);
var uploads = new List<FileUpload>
{
new()
{
Content = stream,
ContentType =
FileUploadContentType.FromContentType(HttpConstants.ContentTypes.ImageJpegWithCharset),
Size = content.Length,
Filename = "afilename.jpg"
}
};

var result =
_service.GetUploadedFile(uploads, 1000, new List<string> { HttpConstants.ContentTypes.ImageJpeg });

result.Should().BeSuccess();
result.Value.ContentType.Should()
.BeEquivalentTo(FileUploadContentType.FromContentType(HttpConstants.ContentTypes.ImageJpegWithCharset));
result.Value.Filename.Should().Be("afilename.jpg");
result.Value.Size.Should().Be(103L);
result.Value.Content.Position.Should().Be(0);
Expand All @@ -329,7 +362,7 @@ public void WhenGetUploaded_ThenReturns()
new()
{
Content = stream,
ContentType = HttpConstants.ContentTypes.ImageJpeg,
ContentType = FileUploadContentType.FromContentType(HttpConstants.ContentTypes.ImageJpeg),
Size = content.Length,
Filename = "afilename.jpg"
}
Expand All @@ -339,7 +372,8 @@ public void WhenGetUploaded_ThenReturns()
_service.GetUploadedFile(uploads, 1000, new List<string> { HttpConstants.ContentTypes.ImageJpeg });

result.Should().BeSuccess();
result.Value.ContentType.Should().Be(HttpConstants.ContentTypes.ImageJpeg);
result.Value.ContentType.Should()
.BeEquivalentTo(FileUploadContentType.FromContentType(HttpConstants.ContentTypes.ImageJpeg));
result.Value.Filename.Should().Be("afilename.jpg");
result.Value.Size.Should().Be(103L);
result.Value.Content.Position.Should().Be(0);
Expand Down
Loading

0 comments on commit 3581cd0

Please sign in to comment.