GET /gerbils/{id}: loads first photo by sortOrder (2 queries, no N+1).
GET /gerbils list: batch-loads first photos for the page in one IN query.
profilePhotoUrl = /photos/files/{fileName} or null. No migration.
3 new tests: no-photo=null, sortOrder priority, list batch.
84 lines
3.1 KiB
C#
84 lines
3.1 KiB
C#
using System.Net;
|
|
using System.Net.Http.Json;
|
|
using System.Text.Json;
|
|
using System.Text.RegularExpressions;
|
|
using GerbilManagerWebAPI.Models;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
|
|
namespace GerbilManager.Tests;
|
|
|
|
/// <summary>profilePhotoUrl: computed field on GerbilDto — first photo by sortOrder, or null.</summary>
|
|
public class GerbilProfilePhotoTests : IClassFixture<ApiFactory>
|
|
{
|
|
private readonly HttpClient _client;
|
|
private readonly ApiFactory _factory;
|
|
|
|
public GerbilProfilePhotoTests(ApiFactory factory)
|
|
{
|
|
_factory = factory;
|
|
_client = factory.CreateClient();
|
|
}
|
|
|
|
private static Guid ExtractId(string json) =>
|
|
Guid.Parse(Regex.Match(json, "\"id\":\"([^\"]+)\"").Groups[1].Value);
|
|
|
|
private static string? GetStr(JsonElement el, string prop) =>
|
|
el.TryGetProperty(prop, out var v) && v.ValueKind == JsonValueKind.String
|
|
? v.GetString() : null;
|
|
|
|
private async Task<Guid> CreateGerbil(string name)
|
|
{
|
|
var resp = await _client.PostAsync("/gerbils", JsonContent.Create(new { name, gender = "female" }));
|
|
Assert.Equal(HttpStatusCode.Created, resp.StatusCode);
|
|
return ExtractId(await resp.Content.ReadAsStringAsync());
|
|
}
|
|
|
|
private void InsertPhotos(Guid gerbilId, params (string fileName, int sortOrder)[] photos)
|
|
{
|
|
using var scope = _factory.Services.CreateScope();
|
|
var db = scope.ServiceProvider.GetRequiredService<ApplicationContext>();
|
|
foreach (var (fn, so) in photos)
|
|
db.GerbilPhotos.Add(new GerbilPhoto
|
|
{
|
|
Id = Guid.NewGuid(), GerbilId = gerbilId,
|
|
FileName = fn, SortOrder = so, CreatedAt = DateTimeOffset.UtcNow,
|
|
});
|
|
db.SaveChanges();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetById_without_photos_returns_null_profilePhotoUrl()
|
|
{
|
|
var id = await CreateGerbil("NoPhoto");
|
|
var json = await _client.GetStringAsync($"/gerbils/{id}");
|
|
var el = JsonDocument.Parse(json).RootElement;
|
|
Assert.True(el.TryGetProperty("profilePhotoUrl", out var v));
|
|
Assert.Equal(JsonValueKind.Null, v.ValueKind);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetById_with_photos_returns_lowest_sortOrder_url()
|
|
{
|
|
var id = await CreateGerbil("PhotoGerbil");
|
|
InsertPhotos(id, ("second.jpg", 1), ("first.jpg", 0));
|
|
|
|
var json = await _client.GetStringAsync($"/gerbils/{id}");
|
|
var el = JsonDocument.Parse(json).RootElement;
|
|
Assert.Equal("/photos/files/first.jpg", GetStr(el, "profilePhotoUrl"));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetList_returns_profilePhotoUrl_without_n_plus_1()
|
|
{
|
|
var id = await CreateGerbil("ListPhoto");
|
|
InsertPhotos(id, ("list-photo.jpg", 0));
|
|
|
|
var json = await _client.GetStringAsync("/gerbils");
|
|
var root = JsonDocument.Parse(json).RootElement;
|
|
var items = root.GetProperty("items").EnumerateArray().ToList();
|
|
var gerbil = items.FirstOrDefault(x => GetStr(x, "id") == id.ToString());
|
|
Assert.NotEqual(default, gerbil);
|
|
Assert.Equal("/photos/files/list-photo.jpg", GetStr(gerbil, "profilePhotoUrl"));
|
|
}
|
|
}
|