Load the current avatar photo alongside user accounts

This commit is contained in:
Aaron Po
2026-09-30 22:16:04 -04:00
parent c5972b9f04
commit bce8fb0472
11 changed files with 390 additions and 29 deletions

View File

@@ -1,5 +1,9 @@
using System.Text.Json;
using Features.Users.Commands.Profile.UploadAvatar;
using FluentAssertions;
using MediatR;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Reqnroll;
namespace API.Specs.Steps;
@@ -10,6 +14,10 @@ public class UserSteps(ScenarioContext scenario) : ApiStepsBase(scenario)
private const string AccessTokenKey = "accessToken";
private const string RegisteredUserIdKey = "registeredUserId";
private const string RegisteredUsernameKey = "registeredUsername";
private const string CurrentAvatarUrlKey = "currentAvatarUrl";
private const string PreviousAvatarUrlKey = "previousAvatarUrl";
private static readonly byte[] PngSignature = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A];
private async Task SendAsync(HttpRequestMessage requestMessage)
{
@@ -36,6 +44,99 @@ public class UserSteps(ScenarioContext scenario) : ApiStepsBase(scenario)
: throw new InvalidOperationException("registered user ID not found in scenario");
}
/// <summary>
/// Builds the smallest upload the photo validator accepts: a PNG signature padded out to the
/// 1 KiB minimum.
/// </summary>
private static IFormFile CreateAvatarFile()
{
byte[] content = new byte[2048];
PngSignature.CopyTo(content, 0);
MemoryStream stream = new(content);
return new FormFile(stream, 0, content.Length, "file", "avatar.png")
{
Headers = new HeaderDictionary { ["Content-Type"] = "image/png" },
};
}
private async Task UploadAvatarAsync()
{
Guid userId = GetRegisteredUserId();
using IServiceScope scope = GetFactory().Services.CreateScope();
IMediator mediator = scope.ServiceProvider.GetRequiredService<IMediator>();
Guid photoId = await mediator.Send(new UploadAvatarCommand(userId, CreateAvatarFile()));
if (Scenario.TryGetValue<string>(CurrentAvatarUrlKey, out string? existing))
Scenario[PreviousAvatarUrlKey] = existing;
Scenario[CurrentAvatarUrlKey] = $"avatars/{userId}/{photoId}";
}
private string GetCurrentAvatarUrl()
{
return Scenario.TryGetValue(CurrentAvatarUrlKey, out string? url) && url is not null
? url
: throw new InvalidOperationException("no avatar has been uploaded in this scenario");
}
private string GetPreviousAvatarUrl()
{
return Scenario.TryGetValue(PreviousAvatarUrlKey, out string? url) && url is not null
? url
: throw new InvalidOperationException("only one avatar has been uploaded in this scenario");
}
[Given("I have uploaded an avatar")]
public async Task GivenIHaveUploadedAnAvatar()
{
await UploadAvatarAsync();
}
[Given("I have uploaded a new avatar, replacing the previous one")]
public async Task GivenIHaveUploadedANewAvatarReplacingThePreviousOne()
{
await UploadAvatarAsync();
}
[Then("the public profile response should carry my current avatar")]
public void ThenThePublicProfileResponseShouldCarryMyCurrentAvatar()
{
Scenario.TryGetValue<string>(ResponseBodyKey, out string? responseBody).Should().BeTrue();
using JsonDocument doc = JsonDocument.Parse(responseBody!);
doc.RootElement.GetProperty("avatarUrl").GetString().Should().Be(GetCurrentAvatarUrl());
}
[Then("the public profile response should not carry my previous avatar")]
public void ThenThePublicProfileResponseShouldNotCarryMyPreviousAvatar()
{
Scenario.TryGetValue<string>(ResponseBodyKey, out string? responseBody).Should().BeTrue();
using JsonDocument doc = JsonDocument.Parse(responseBody!);
doc.RootElement.GetProperty("avatarUrl").GetString().Should().NotBe(GetPreviousAvatarUrl());
}
[Then("my listed account should carry my current avatar")]
public void ThenMyListedAccountShouldCarryMyCurrentAvatar()
{
Scenario.TryGetValue<string>(ResponseBodyKey, out string? responseBody).Should().BeTrue();
using JsonDocument doc = JsonDocument.Parse(responseBody!);
doc.RootElement.ValueKind.Should().Be(JsonValueKind.Array);
JsonElement listed = doc
.RootElement.EnumerateArray()
.Should()
.ContainSingle(item =>
item.GetProperty("userAccountId").GetGuid() == GetRegisteredUserId()
)
.Subject;
listed.GetProperty("avatarUrl").GetString().Should().Be(GetCurrentAvatarUrl());
}
[When("I retrieve the public profile by ID")]
public async Task WhenIRetrieveThePublicProfileById()
{