Expand test coverage to match specs

This commit is contained in:
2026-02-05 18:57:25 +01:00
parent e11cb23313
commit 67a164e53b
14 changed files with 861 additions and 32 deletions

View File

@@ -191,7 +191,7 @@ public static class AdminEndpoints
var suggestions = await db.Suggestions.ToListAsync();
var target = suggestions.FirstOrDefault(s => s.Id == request.SuggestionId);
if (target is null)
return Results.NotFound(new { error = "Suggestion not found." });
return Results.Ok(new { UnlinkedSuggestionIds = Array.Empty<int>(), UnfinalizedPlayers = 0 });
var rootIndex = EndpointHelpers.BuildLinkRoots(suggestions.Select(s => (s.Id, s.ParentSuggestionId)));
if (!rootIndex.TryGetValue(target.Id, out var rootId))

View File

@@ -22,6 +22,9 @@ public static class AuthEndpoints
if (string.IsNullOrWhiteSpace(request.Password))
return Results.BadRequest(new { error = "Password is required." });
if (request.DisplayName?.Trim().Length > 16)
return Results.BadRequest(new { error = "Display name must be <= 16 characters." });
var displayName = EndpointHelpers.TrimTo(request.DisplayName, 16);
if (string.IsNullOrWhiteSpace(displayName))
return Results.BadRequest(new { error = "Display name is required." });

View File

@@ -91,7 +91,7 @@ internal static class EndpointHelpers
|| path.EndsWith(".gif") || path.EndsWith(".webp") || path.EndsWith(".avif");
}
public static async Task<bool> IsReachableImageAsync(string? url, IHttpClientFactory httpFactory, CancellationToken ct = default)
public static async Task<bool> IsReachableImageAsync(string? url, IHttpClientFactory httpFactory, HttpMessageHandler? handler = null, CancellationToken ct = default)
{
if (string.IsNullOrWhiteSpace(url)) return true;
if (!Uri.TryCreate(url, UriKind.Absolute, out var uri)) return false;
@@ -101,11 +101,9 @@ internal static class EndpointHelpers
using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
cts.CancelAfter(TimeSpan.FromSeconds(3));
var handler = new HttpClientHandler
{
AllowAutoRedirect = false
};
var client = new HttpClient(handler);
var client = handler is null
? httpFactory.CreateClient("imageValidation")
: new HttpClient(handler, disposeHandler: false);
try
{
@@ -113,10 +111,10 @@ internal static class EndpointHelpers
var headResp = await client.SendAsync(head, HttpCompletionOption.ResponseHeadersRead, cts.Token);
if (headResp.IsSuccessStatusCode && headResp.StatusCode is not System.Net.HttpStatusCode.Redirect)
{
if (headResp.Content.Headers.ContentLength is long headLen && headLen > MaxImageBytes) return false;
var ctHeader = headResp.Content.Headers.ContentType?.MediaType;
if (!string.IsNullOrWhiteSpace(ctHeader) && ctHeader.StartsWith("image/", StringComparison.OrdinalIgnoreCase))
return true;
if (headResp.Content.Headers.ContentLength is long len && len > MaxImageBytes) return false;
}
}
catch { /* fallback */ }

View File

@@ -80,6 +80,11 @@ public static class StateEndpoints
group.MapPost("/me/name", async ([FromBody] SetNameRequest request, HttpContext ctx, AppDbContext db) =>
{
if (request.Name?.Trim().Length > 16)
{
return Results.BadRequest(new { error = "Name is required and must be <= 16 characters." });
}
var name = EndpointHelpers.TrimTo(request.Name, 16);
if (string.IsNullOrWhiteSpace(name))
{

View File

@@ -116,6 +116,13 @@ public static class SuggestEndpoints
if (player is null) return Results.Unauthorized();
var isAdmin = await EndpointHelpers.IsAdmin(ctx, db);
if (!isAdmin)
{
var phase = await EndpointHelpers.GetPhase(db, player.Id);
if (phase != Phase.Suggest)
return EndpointHelpers.PhaseMismatch(Phase.Suggest, phase);
}
var suggestion = isAdmin
? await db.Suggestions.FirstOrDefaultAsync(s => s.Id == id)
: await db.Suggestions.FirstOrDefaultAsync(s => s.Id == id && s.PlayerId == player.Id);