mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-10-15 01:54:43 +00:00
Load the current avatar photo alongside user accounts
This commit is contained in:
@@ -183,6 +183,25 @@ public class ApiGeneralSteps(ScenarioContext scenario)
|
||||
AssertFieldAbsent(payloadElem, field);
|
||||
}
|
||||
|
||||
[Then("the response JSON should contain {string}")]
|
||||
public void ThenTheResponseJsonShouldContainString(string field)
|
||||
{
|
||||
scenario.TryGetValue<string>(ResponseBodyKey, out string? responseBody).Should().BeTrue();
|
||||
|
||||
using JsonDocument doc = JsonDocument.Parse(responseBody!);
|
||||
JsonElement root = doc.RootElement;
|
||||
|
||||
if (root.ValueKind == JsonValueKind.Object &&
|
||||
!root.TryGetProperty(field, out _) &&
|
||||
root.TryGetProperty("payload", out JsonElement payloadElem))
|
||||
{
|
||||
AssertFieldPresent(payloadElem, field);
|
||||
return;
|
||||
}
|
||||
|
||||
AssertFieldPresent(root, field);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recurses into arrays so a listing endpoint's response can be asserted the same way as a
|
||||
/// single-resource response.
|
||||
@@ -203,4 +222,31 @@ public class ApiGeneralSteps(ScenarioContext scenario)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recurses into arrays so every item of a listing endpoint's response must carry the field.
|
||||
/// </summary>
|
||||
private static void AssertFieldPresent(JsonElement element, string field)
|
||||
{
|
||||
switch (element.ValueKind)
|
||||
{
|
||||
case JsonValueKind.Object:
|
||||
element
|
||||
.TryGetProperty(field, out _)
|
||||
.Should()
|
||||
.BeTrue("Expected field '{0}' to be present in the response", field);
|
||||
break;
|
||||
case JsonValueKind.Array:
|
||||
element
|
||||
.EnumerateArray()
|
||||
.Should()
|
||||
.NotBeEmpty(
|
||||
"Expected at least one item to assert field '{0}' against",
|
||||
field
|
||||
);
|
||||
foreach (JsonElement item in element.EnumerateArray())
|
||||
AssertFieldPresent(item, field);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user