C# formatting

This commit is contained in:
2026-02-05 20:39:12 +01:00
parent 78cdbfe51e
commit c0756ff2c6
34 changed files with 830 additions and 582 deletions

View File

@@ -12,7 +12,7 @@ public class AdminTests
[Fact]
public async Task Admin_vote_status_marks_ready_when_all_finalized()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var admin = factory.CreateClientWithCookies();
await admin.RegisterAsync("admin", admin: true);
await admin.PostAsJsonAsync("/api/me/phase/next", new { }); // move to Vote
@@ -25,9 +25,17 @@ public class AdminTests
var s1 = await p1.CreateSuggestionAsync("A");
await p1.PostAsJsonAsync("/api/me/phase/next", new { });
await p1.PostAsJsonAsync("/api/votes", new { SuggestionId = s1, Score = 5 });
await p1.PostAsJsonAsync("/api/votes", new
{
SuggestionId = s1,
Score = 5
});
await p1.PostAsJsonAsync("/api/votes/finalize", new { Final = true });
await p2.PostAsJsonAsync("/api/votes", new { SuggestionId = s1, Score = 7 });
await p2.PostAsJsonAsync("/api/votes", new
{
SuggestionId = s1,
Score = 7
});
await p2.PostAsJsonAsync("/api/votes/finalize", new { Final = true });
await admin.PostAsJsonAsync("/api/votes/finalize", new { Final = true });
@@ -40,7 +48,7 @@ public class AdminTests
[Fact]
public async Task Grant_joker_only_in_vote_phase()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var admin = factory.CreateClientWithCookies();
await admin.RegisterAsync("admin", admin: true);
@@ -54,7 +62,7 @@ public class AdminTests
[Fact]
public async Task Delete_player_cascades_suggestions_and_votes()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var admin = factory.CreateClientWithCookies();
await admin.RegisterAsync("admin", admin: true);
@@ -63,23 +71,37 @@ public class AdminTests
var suggestionId = await player.CreateSuggestionAsync("DeleteGame");
await player.PostAsJsonAsync("/api/me/phase/next", new { });
await player.PostAsJsonAsync("/api/votes", new { SuggestionId = suggestionId, Score = 8 });
await player.PostAsJsonAsync("/api/votes", new
{
SuggestionId = suggestionId,
Score = 8
});
var resp = await admin.DeleteAsync($"/api/admin/players/{await player.GetProfileIdAsync()}");
resp.EnsureSuccessStatusCode();
await factory.WithDbContextAsync(async db =>
await factory.WithDbContextAsync(db =>
{
Assert.Single(db.Players); // admin remains
Assert.Empty(db.Suggestions);
Assert.Empty(db.Votes);
try
{
Assert.Single(db.Players); // admin remains
Assert.Empty(db.Suggestions);
Assert.Empty(db.Votes);
return Task.CompletedTask;
}
catch (Exception exception)
{
return Task.FromException(exception);
}
});
}
[Fact]
public async Task Link_suggestions_errors_on_same_id_and_already_linked()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var admin = factory.CreateClientWithCookies();
await admin.RegisterAsync("admin", admin: true);
var player = factory.CreateClientWithCookies();
@@ -91,20 +113,32 @@ public class AdminTests
await player.PostAsJsonAsync("/api/me/phase/next", new { });
await admin.PostAsJsonAsync("/api/me/phase/next", new { });
var same = await admin.PostAsJsonAsync("/api/admin/link-suggestions", new { SourceSuggestionId = a, TargetSuggestionId = a });
var same = await admin.PostAsJsonAsync("/api/admin/link-suggestions", new
{
SourceSuggestionId = a,
TargetSuggestionId = a
});
Assert.Equal(HttpStatusCode.BadRequest, same.StatusCode);
var first = await admin.PostAsJsonAsync("/api/admin/link-suggestions", new { SourceSuggestionId = a, TargetSuggestionId = b });
var first = await admin.PostAsJsonAsync("/api/admin/link-suggestions", new
{
SourceSuggestionId = a,
TargetSuggestionId = b
});
first.EnsureSuccessStatusCode();
var already = await admin.PostAsJsonAsync("/api/admin/link-suggestions", new { SourceSuggestionId = a, TargetSuggestionId = b });
var already = await admin.PostAsJsonAsync("/api/admin/link-suggestions", new
{
SourceSuggestionId = a,
TargetSuggestionId = b
});
Assert.Equal(HttpStatusCode.BadRequest, already.StatusCode);
}
[Fact]
public async Task Unlink_suggestions_clears_group_votes()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var admin = factory.CreateClientWithCookies();
await admin.RegisterAsync("admin", admin: true);
var player = factory.CreateClientWithCookies();
@@ -114,24 +148,41 @@ public class AdminTests
var b = await player.CreateSuggestionAsync("Game B");
await player.PostAsJsonAsync("/api/me/phase/next", new { });
await admin.PostAsJsonAsync("/api/me/phase/next", new { });
await admin.PostAsJsonAsync("/api/admin/link-suggestions", new { SourceSuggestionId = a, TargetSuggestionId = b });
await admin.PostAsJsonAsync("/api/admin/link-suggestions", new
{
SourceSuggestionId = a,
TargetSuggestionId = b
});
await player.PostAsJsonAsync("/api/votes", new { SuggestionId = a, Score = 6 });
await player.PostAsJsonAsync("/api/votes", new
{
SuggestionId = a,
Score = 6
});
var resp = await admin.PostAsJsonAsync("/api/admin/unlink-suggestions", new { suggestionId = a });
resp.EnsureSuccessStatusCode();
await factory.WithDbContextAsync(async db =>
await factory.WithDbContextAsync(db =>
{
Assert.Empty(db.Votes);
Assert.All(db.Suggestions, s => Assert.Null(s.ParentSuggestionId));
try
{
Assert.Empty(db.Votes);
Assert.All(db.Suggestions, s => Assert.Null(s.ParentSuggestionId));
return Task.CompletedTask;
}
catch (Exception exception)
{
return Task.FromException(exception);
}
});
}
[Fact]
public async Task Reset_and_factory_reset_clear_state()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var admin = factory.CreateClientWithCookies();
await admin.RegisterAsync("admin", admin: true);
var player = factory.CreateClientWithCookies();
@@ -141,27 +192,46 @@ public class AdminTests
var reset = await admin.PostAsJsonAsync("/api/admin/reset", new { });
reset.EnsureSuccessStatusCode();
await factory.WithDbContextAsync(async db =>
await factory.WithDbContextAsync(db =>
{
Assert.Empty(db.Suggestions);
Assert.Empty(db.Votes);
Assert.All(db.Players, p => Assert.Equal(Phase.Suggest, p.CurrentPhase));
try
{
Assert.Empty(db.Suggestions);
Assert.Empty(db.Votes);
Assert.All(db.Players, p => Assert.Equal(Phase.Suggest, p.CurrentPhase));
return Task.CompletedTask;
}
catch (Exception exception)
{
return Task.FromException(exception);
}
});
var factoryReset = await admin.PostAsJsonAsync("/api/admin/factory-reset", new { });
factoryReset.EnsureSuccessStatusCode();
await factory.WithDbContextAsync(async db =>
await factory.WithDbContextAsync(db =>
{
Assert.Empty(db.Players);
Assert.Single(db.AppState);
try
{
Assert.Empty(db.Players);
Assert.Single(db.AppState);
return Task.CompletedTask;
}
catch (Exception exception)
{
return Task.FromException(exception);
}
});
}
[Fact]
public async Task Admin_results_closing_moves_back_to_vote_and_clears_finalize()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var admin = factory.CreateClientWithCookies();
await admin.RegisterAsync("admin", admin: true);
var player = factory.CreateClientWithCookies();
@@ -196,7 +266,7 @@ public class AdminTests
[Fact]
public async Task Vote_status_lists_waiting_players()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var admin = factory.CreateClientWithCookies();
await admin.RegisterAsync("admin", admin: true);
await admin.PostAsJsonAsync("/api/me/phase/next", new { });
@@ -208,7 +278,11 @@ public class AdminTests
var s = await p1.CreateSuggestionAsync("Game");
await p1.PostAsJsonAsync("/api/me/phase/next", new { });
await p2.PostAsJsonAsync("/api/me/phase/next", new { });
await p1.PostAsJsonAsync("/api/votes", new { SuggestionId = s, Score = 5 });
await p1.PostAsJsonAsync("/api/votes", new
{
SuggestionId = s,
Score = 5
});
await p1.PostAsJsonAsync("/api/votes/finalize", new { Final = true });
var status = await admin.GetFromJsonAsync<JsonElement>("/api/admin/vote-status");
@@ -220,7 +294,7 @@ public class AdminTests
[Fact]
public async Task Grant_joker_in_vote_sets_flag_and_unfinalizes()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var admin = factory.CreateClientWithCookies();
await admin.RegisterAsync("admin", admin: true);
@@ -243,7 +317,7 @@ public class AdminTests
[Fact]
public async Task Link_requires_vote_phase_and_reparents_votes_reset()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var admin = factory.CreateClientWithCookies();
await admin.RegisterAsync("admin", admin: true);
var player = factory.CreateClientWithCookies();
@@ -252,16 +326,28 @@ public class AdminTests
var a = await player.CreateSuggestionAsync("A");
var b = await player.CreateSuggestionAsync("B");
var beforeVotePhase = await admin.PostAsJsonAsync("/api/admin/link-suggestions", new { SourceSuggestionId = a, TargetSuggestionId = b });
var beforeVotePhase = await admin.PostAsJsonAsync("/api/admin/link-suggestions", new
{
SourceSuggestionId = a,
TargetSuggestionId = b
});
Assert.Equal(HttpStatusCode.BadRequest, beforeVotePhase.StatusCode);
await admin.PostAsJsonAsync("/api/me/phase/next", new { });
await player.PostAsJsonAsync("/api/me/phase/next", new { });
await player.PostAsJsonAsync("/api/votes", new { SuggestionId = a, Score = 3 });
await player.PostAsJsonAsync("/api/votes", new
{
SuggestionId = a,
Score = 3
});
await player.PostAsJsonAsync("/api/votes/finalize", new { Final = true });
var link = await admin.PostAsJsonAsync("/api/admin/link-suggestions", new { SourceSuggestionId = a, TargetSuggestionId = b });
var link = await admin.PostAsJsonAsync("/api/admin/link-suggestions", new
{
SourceSuggestionId = a,
TargetSuggestionId = b
});
link.EnsureSuccessStatusCode();
await factory.WithDbContextAsync(async db =>
@@ -276,7 +362,7 @@ public class AdminTests
[Fact]
public async Task Unlink_not_found_returns_empty_payload()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var admin = factory.CreateClientWithCookies();
await admin.RegisterAsync("admin", admin: true);
await admin.PostAsJsonAsync("/api/me/phase/next", new { });
@@ -290,7 +376,7 @@ public class AdminTests
[Fact]
public async Task Reset_clears_flags_and_factory_reset_seeds_defaults()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var admin = factory.CreateClientWithCookies();
await admin.RegisterAsync("admin", admin: true);
var p = factory.CreateClientWithCookies();

View File

@@ -1,7 +1,6 @@
using System.Net;
using System.Net.Http.Json;
using System.Text.Json;
using GameList.Data;
using GameList.Infrastructure;
using GameList.Tests.Support;
using Microsoft.EntityFrameworkCore;
@@ -13,7 +12,7 @@ public class AuthTests
[Fact]
public async Task Register_trims_limits_and_sets_cookie_and_normalized_username()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var client = factory.CreateClientWithCookies();
var response = await client.PostAsJsonAsync("/api/auth/register", new
@@ -25,8 +24,7 @@ public class AuthTests
});
response.EnsureSuccessStatusCode();
Assert.True(response.Headers.TryGetValues("Set-Cookie", out var cookies) &&
cookies.Any(c => c.Contains(PlayerIdentityExtensions.PlayerCookieName)));
Assert.True(response.Headers.TryGetValues("Set-Cookie", out var cookies) && cookies.Any(c => c.Contains(PlayerIdentityExtensions.PlayerCookieName)));
await factory.WithDbContextAsync(async db =>
{
@@ -41,7 +39,7 @@ public class AuthTests
[Fact]
public async Task Register_rejects_overlength_username_or_display_name()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var client = factory.CreateClientWithCookies();
var tooLongUser = new string('u', 25);
@@ -67,7 +65,7 @@ public class AuthTests
[Fact]
public async Task Login_sets_last_login_and_fills_missing_display_name()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var client = factory.CreateClientWithCookies();
await client.RegisterAsync("loginfill");
@@ -93,7 +91,7 @@ public class AuthTests
[Fact]
public async Task Register_with_admin_key_sets_admin_flag()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var client = factory.CreateClientWithCookies();
var response = await client.RegisterAsync("adminuser", admin: true);
@@ -106,7 +104,7 @@ public class AuthTests
[Fact]
public async Task Register_duplicate_username_returns_conflict()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var client = factory.CreateClientWithCookies();
var first = await client.RegisterAsync("duplicate");
@@ -120,7 +118,7 @@ public class AuthTests
[Fact]
public async Task Login_with_wrong_password_returns_unauthorized()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var client = factory.CreateClientWithCookies();
await client.RegisterAsync("player1");
@@ -133,20 +131,31 @@ public class AuthTests
[Fact]
public async Task Register_validates_required_fields()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var client = factory.CreateClientWithCookies();
var missing = await client.PostAsJsonAsync("/api/auth/register", new { Username = "", Password = "", DisplayName = "" });
var missing = await client.PostAsJsonAsync("/api/auth/register", new
{
Username = "",
Password = "",
DisplayName = ""
});
Assert.Equal(HttpStatusCode.BadRequest, missing.StatusCode);
var badKey = await client.PostAsJsonAsync("/api/auth/register", new { Username = "u", Password = "p", DisplayName = "d", AdminKey = "wrong" });
var badKey = await client.PostAsJsonAsync("/api/auth/register", new
{
Username = "u",
Password = "p",
DisplayName = "d",
AdminKey = "wrong"
});
Assert.Equal(HttpStatusCode.BadRequest, badKey.StatusCode);
}
[Fact]
public async Task Non_admin_cannot_access_admin_routes()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var player = factory.CreateClientWithCookies();
await player.RegisterAsync("regular");
@@ -157,7 +166,7 @@ public class AuthTests
[Fact]
public async Task Admin_can_access_admin_routes()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var admin = factory.CreateClientWithCookies();
await admin.RegisterAsync("adminuser", admin: true);
@@ -168,7 +177,7 @@ public class AuthTests
[Fact]
public async Task Logout_clears_cookie()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var client = factory.CreateClientWithCookies();
await client.RegisterAsync("logoutme");

View File

@@ -1,13 +1,10 @@
using System.IO;
using System.Security.Claims;
using GameList.Data;
using GameList.Domain;
using GameList.Infrastructure;
using GameList.Tests.Support;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace GameList.Tests;
@@ -16,7 +13,7 @@ public class FiltersTests
[Fact]
public async Task Admin_only_filter_blocks_non_admin()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var client = factory.CreateClientWithCookies();
await client.RegisterAsync("user");
@@ -29,7 +26,7 @@ public class FiltersTests
[Fact]
public async Task Phase_requirement_allows_admin_override_when_enabled()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var ctx = await BuildContextAsync(factory, isAdmin: true, phase: Phase.Suggest);
var filter = new PhaseRequirementFilter(Phase.Vote, allowAdminOverride: true);
var called = false;
@@ -46,7 +43,7 @@ public class FiltersTests
[Fact]
public async Task Phase_or_joker_filter_blocks_without_joker_in_vote()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var ctx = await BuildContextAsync(factory, isAdmin: false, phase: Phase.Vote, hasJoker: false);
var filter = new PhaseOrJokerFilter();
var result = await filter.InvokeAsync(ctx, _ => ValueTask.FromResult<object?>(Results.Ok()));
@@ -62,8 +59,8 @@ public class FiltersTests
Id = Guid.NewGuid(),
Username = $"user-{Guid.NewGuid():N}",
NormalizedUsername = $"user-{Guid.NewGuid():N}",
PasswordHash = new byte[] { 1 },
PasswordSalt = new byte[] { 1 },
PasswordHash = [1],
PasswordSalt = [1],
IsAdmin = isAdmin,
CurrentPhase = phase,
HasJoker = hasJoker,
@@ -75,28 +72,20 @@ public class FiltersTests
var ctx = new DefaultHttpContext
{
RequestServices = scope.ServiceProvider,
User = new ClaimsPrincipal(new ClaimsIdentity(new[]
{
User = new ClaimsPrincipal(new ClaimsIdentity([
new Claim(ClaimTypes.NameIdentifier, player.Id.ToString()),
new Claim(ClaimTypes.Name, player.Username),
new Claim(PlayerIdentityExtensions.AdminClaim, isAdmin ? "true" : "false")
}, "cookie"))
], "cookie"))
};
return new TestInvocationContext(ctx);
}
private class TestInvocationContext : EndpointFilterInvocationContext
private class TestInvocationContext(DefaultHttpContext context) : EndpointFilterInvocationContext
{
private readonly DefaultHttpContext _context;
public TestInvocationContext(DefaultHttpContext context)
{
_context = context;
}
public override HttpContext HttpContext => _context;
public override object?[] Arguments => Array.Empty<object?>();
public override HttpContext HttpContext => context;
public override object?[] Arguments => [];
public override T GetArgument<T>(int index) => throw new NotImplementedException();
}
@@ -104,11 +93,9 @@ public class FiltersTests
{
var http = new DefaultHttpContext
{
RequestServices = new ServiceCollection()
.AddLogging()
.BuildServiceProvider()
RequestServices = new ServiceCollection().AddLogging().BuildServiceProvider(),
Response = { Body = new MemoryStream() }
};
http.Response.Body = new MemoryStream();
await ((IResult)result!).ExecuteAsync(http);
Assert.Equal(statusCode, http.Response.StatusCode);
}

View File

@@ -1,5 +1,4 @@
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Reflection;
using GameList.Infrastructure;
@@ -7,10 +6,7 @@ using GameList.Endpoints;
using GameList.Tests.Support;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using System.Linq;
using Microsoft.AspNetCore.Mvc.Testing;
using System.Text.Json;
using System.Net.Http.Json;
@@ -36,9 +32,8 @@ public class HelperTests
File.WriteAllText(index, "<meta name=\"app-base\" content=\"\">");
var env = new FakeEnv { WebRootPath = webRoot };
var method = typeof(Program).GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public)
.First(m => m.Name.Contains("UpdateIndexMetaBase"));
method.Invoke(null, new object?[] { env, "/pick" });
var method = typeof(Program).GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public).First(m => m.Name.Contains("UpdateIndexMetaBase"));
method.Invoke(null, [env, "/pick"]);
var text = File.ReadAllText(index);
Assert.Contains("content=\"/pick\"", text);
@@ -53,9 +48,8 @@ public class HelperTests
File.WriteAllText(index, "<html></html>");
var env = new FakeEnv { WebRootPath = webRoot };
var method = typeof(Program).GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public)
.First(m => m.Name.Contains("UpdateIndexMetaBase"));
method.Invoke(null, new object?[] { env, "/pick" });
var method = typeof(Program).GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public).First(m => m.Name.Contains("UpdateIndexMetaBase"));
method.Invoke(null, [env, "/pick"]);
Assert.Equal("<html></html>", File.ReadAllText(index));
}
@@ -76,14 +70,15 @@ public class HelperTests
if (req.Method == HttpMethod.Head)
{
var resp = new HttpResponseMessage(HttpStatusCode.OK);
resp.Content = new ByteArrayContent(Array.Empty<byte>());
resp.Content = new ByteArrayContent([]);
resp.Content.Headers.ContentType = new MediaTypeHeaderValue("image/png");
resp.Content.Headers.ContentLength = 100;
return resp;
}
return new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new ByteArrayContent(Array.Empty<byte>())
Content = new ByteArrayContent([])
{
Headers =
{
@@ -125,7 +120,7 @@ public class HelperTests
handler.SetResponder(_ =>
{
var resp = new HttpResponseMessage(HttpStatusCode.OK);
resp.Content = new ByteArrayContent(System.Text.Encoding.UTF8.GetBytes("not image"));
resp.Content = new ByteArrayContent("not image"u8.ToArray());
resp.Content.Headers.ContentType = new MediaTypeHeaderValue("text/plain");
resp.Content.Headers.ContentLength = 9;
return resp;
@@ -138,7 +133,7 @@ public class HelperTests
[Fact]
public void Link_root_helpers_handle_groups()
{
var roots = EndpointHelpers.BuildLinkRoots(new[] { (1, (int?)null), (2, 1), (3, (int?)null) });
var roots = EndpointHelpers.BuildLinkRoots([(1, null), (2, 1), (3, null)]);
Assert.Equal(1, roots[1]);
Assert.Equal(1, roots[2]);
Assert.Equal(3, roots[3]);
@@ -162,9 +157,9 @@ public class HelperTests
{
var parentMap = new Dictionary<int, int?>
{
{1, 2},
{2, 3},
{3, 1}
{ 1, 2 },
{ 2, 3 },
{ 3, 1 }
};
var root = EndpointHelpers.FindRootId(1, parentMap);
@@ -174,16 +169,13 @@ public class HelperTests
[Fact]
public async Task Global_exception_handler_returns_json_error()
{
using var factory = new TestWebApplicationFactory().WithWebHostBuilder(builder =>
await using var factory = new TestWebApplicationFactory().WithWebHostBuilder(builder =>
{
builder.Configure(app =>
{
app.UseGlobalExceptionLogging();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/boom", _ => throw new InvalidOperationException("boom"));
});
app.UseEndpoints(endpoints => { endpoints.MapGet("/boom", _ => throw new InvalidOperationException("boom")); });
});
});

View File

@@ -1,11 +1,9 @@
using System.Security.Claims;
using GameList.Domain;
using GameList.Infrastructure;
using GameList.Tests.Support;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace GameList.Tests;
@@ -14,16 +12,16 @@ public class IdentityTests
[Fact]
public async Task Sign_in_sets_claims_and_cookie()
{
using var factory = new TestWebApplicationFactory();
var ctx = BuildAuthContext(factory.Services);
await using var factory = new TestWebApplicationFactory();
var ctx = BuildAuthContext();
var player = new Player
{
Id = Guid.NewGuid(),
Username = "claimuser",
NormalizedUsername = "claimuser",
PasswordHash = new byte[] { 1 },
PasswordSalt = new byte[] { 1 },
PasswordHash = [1],
PasswordSalt = [1],
DisplayName = "Claim",
IsAdmin = true
};
@@ -31,15 +29,14 @@ public class IdentityTests
await PlayerIdentityExtensions.SignInPlayerAsync(ctx, player);
var cookies = ctx.Response.Headers["Set-Cookie"];
Assert.NotNull(cookies);
Assert.Contains(cookies!, v => v.Contains(PlayerIdentityExtensions.PlayerCookieName));
Assert.Contains(cookies, v => v != null && v.Contains(PlayerIdentityExtensions.PlayerCookieName));
}
[Fact]
public async Task Sign_out_clears_principal()
{
using var factory = new TestWebApplicationFactory();
var ctx = BuildAuthContext(factory.Services);
await using var factory = new TestWebApplicationFactory();
var ctx = BuildAuthContext();
var player = new Player();
await PlayerIdentityExtensions.SignInPlayerAsync(ctx, player);
@@ -48,15 +45,11 @@ public class IdentityTests
Assert.False(ctx.User.Identity?.IsAuthenticated ?? false);
}
private static DefaultHttpContext BuildAuthContext(IServiceProvider services)
private static DefaultHttpContext BuildAuthContext()
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
serviceCollection.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.Cookie.Name = PlayerIdentityExtensions.PlayerCookieName;
});
serviceCollection.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options => { options.Cookie.Name = PlayerIdentityExtensions.PlayerCookieName; });
serviceCollection.AddLogging();
var provider = serviceCollection.BuildServiceProvider();

View File

@@ -8,7 +8,7 @@ public class MiddlewareTests
[Fact]
public async Task Deleted_player_cookie_is_signed_out()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var client = factory.CreateClientWithCookies();
await client.RegisterAsync("ghost");
@@ -29,7 +29,7 @@ public class MiddlewareTests
[Fact]
public async Task Existing_player_passes_through_middleware()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var client = factory.CreateClientWithCookies();
await client.RegisterAsync("live");

View File

@@ -9,7 +9,7 @@ public class ResultsTests
[Fact]
public async Task Results_available_after_admin_unlocks()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var admin = factory.CreateClientWithCookies();
await admin.RegisterAsync("admin", admin: true);
@@ -18,7 +18,11 @@ public class ResultsTests
var suggestionId = await player.CreateSuggestionAsync("ResultGame");
await player.PostAsJsonAsync("/api/me/phase/next", new { });
await player.PostAsJsonAsync("/api/votes", new { SuggestionId = suggestionId, Score = 8 });
await player.PostAsJsonAsync("/api/votes", new
{
SuggestionId = suggestionId,
Score = 8
});
await player.PostAsJsonAsync("/api/votes/finalize", new { Final = true });
await admin.PostAsJsonAsync("/api/admin/results", new { resultsOpen = true });
@@ -36,7 +40,7 @@ public class ResultsTests
[Fact]
public async Task Results_locked_returns_error()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var client = factory.CreateClientWithCookies();
await client.RegisterAsync("user");
await client.PostAsJsonAsync("/api/me/phase/next", new { });
@@ -47,7 +51,7 @@ public class ResultsTests
[Fact]
public async Task Results_require_results_phase_and_auth()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var admin = factory.CreateClientWithCookies();
await admin.RegisterAsync("admin", admin: true);
var player = factory.CreateClientWithCookies();
@@ -65,17 +69,21 @@ public class ResultsTests
[Fact]
public async Task Results_payload_contains_fields_and_ordering()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var admin = factory.CreateClientWithCookies();
await admin.RegisterAsync("admin", admin: true);
var player = factory.CreateClientWithCookies();
await player.RegisterAsync("player");
var s1 = await player.CreateSuggestionAsync("High");
var s2 = await player.CreateSuggestionAsync("NoVotes");
_ = await player.CreateSuggestionAsync("NoVotes");
await player.PostAsJsonAsync("/api/me/phase/next", new { });
await player.PostAsJsonAsync("/api/votes", new { SuggestionId = s1, Score = 9 });
await player.PostAsJsonAsync("/api/votes", new
{
SuggestionId = s1,
Score = 9
});
await player.PostAsJsonAsync("/api/votes/finalize", new { Final = true });
await admin.PostAsJsonAsync("/api/admin/results", new { resultsOpen = true });
@@ -83,7 +91,7 @@ public class ResultsTests
var results = await player.GetFromJsonAsync<List<JsonElement>>("/api/results");
Assert.NotNull(results);
Assert.Equal(2, results!.Count);
Assert.Equal(2, results.Count);
Assert.Equal("High", results[0].GetProperty("name").GetString());
Assert.Equal(9, (int)results[0].GetProperty("average").GetDouble());
Assert.Equal(1, results[0].GetProperty("count").GetInt32());

View File

@@ -14,7 +14,7 @@ public class StateTests
[Fact]
public async Task State_endpoint_returns_expected_payload_for_authenticated_user()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var client = factory.CreateClientWithCookies();
await client.RegisterAsync("payload");
await factory.WithDbContextAsync(async db =>
@@ -27,7 +27,7 @@ public class StateTests
var state = await client.GetFromJsonAsync<JsonElement>("/api/state");
Assert.Equal(Phase.Suggest.ToString(), state.GetProperty("currentPhase").GetString());
Assert.Equal(nameof(Phase.Suggest), state.GetProperty("currentPhase").GetString());
Assert.False(state.GetProperty("votesFinal").GetBoolean());
Assert.True(state.GetProperty("hasJoker").GetBoolean());
Assert.True(state.GetProperty("players").GetInt32() >= 1);
@@ -38,7 +38,7 @@ public class StateTests
[Fact]
public async Task GetPhase_upgrades_reveal_and_resets_when_results_close()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
Guid playerId = Guid.Empty;
await factory.WithDbContextAsync(async db =>
{
@@ -47,8 +47,8 @@ public class StateTests
Id = Guid.NewGuid(),
Username = "legacy",
NormalizedUsername = "legacy",
PasswordHash = new byte[] { 1 },
PasswordSalt = new byte[] { 1 },
PasswordHash = [1],
PasswordSalt = [1],
DisplayName = "Legacy",
CurrentPhase = Phase.Reveal,
VotesFinal = true
@@ -63,7 +63,7 @@ public class StateTests
using (var scope = factory.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
var phase = await GameList.Endpoints.EndpointHelpers.GetPhase(db, playerId);
var phase = await Endpoints.EndpointHelpers.GetPhase(db, playerId);
Assert.Equal(Phase.Results, phase);
}
@@ -77,7 +77,7 @@ public class StateTests
using (var scope = factory.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
var phase = await GameList.Endpoints.EndpointHelpers.GetPhase(db, playerId);
var phase = await Endpoints.EndpointHelpers.GetPhase(db, playerId);
var player = await db.Players.FindAsync(playerId);
Assert.Equal(Phase.Vote, phase);
Assert.False(player!.VotesFinal);
@@ -87,7 +87,7 @@ public class StateTests
[Fact]
public async Task Phase_next_advances_and_clears_votesfinal()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var client = factory.CreateClientWithCookies();
await client.RegisterAsync("advance");
@@ -111,13 +111,13 @@ public class StateTests
toResults.EnsureSuccessStatusCode();
var me = await client.GetFromJsonAsync<JsonElement>("/api/me");
Assert.False(me.GetProperty("votesFinal").GetBoolean());
Assert.Equal(Phase.Results.ToString(), me.GetProperty("currentPhase").GetString());
Assert.Equal(nameof(Phase.Results), me.GetProperty("currentPhase").GetString());
}
[Fact]
public async Task Phase_prev_moves_back_and_clears_votesfinal()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var admin = factory.CreateClientWithCookies();
await admin.RegisterAsync("admin", admin: true);
@@ -132,14 +132,14 @@ public class StateTests
backToSuggest.EnsureSuccessStatusCode();
var me = await admin.GetFromJsonAsync<JsonElement>("/api/me");
Assert.Equal(Phase.Suggest.ToString(), me.GetProperty("currentPhase").GetString());
Assert.Equal(nameof(Phase.Suggest), me.GetProperty("currentPhase").GetString());
Assert.False(me.GetProperty("votesFinal").GetBoolean());
}
[Fact]
public async Task Name_endpoint_rejects_over_16_chars()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var client = factory.CreateClientWithCookies();
await client.RegisterAsync("namelimit");
@@ -150,7 +150,7 @@ public class StateTests
[Fact]
public async Task Cannot_advance_to_results_when_locked()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var client = factory.CreateClientWithCookies();
await client.RegisterAsync("player");
@@ -165,7 +165,7 @@ public class StateTests
[Fact]
public async Task Admin_opening_results_moves_players_to_results_phase()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var admin = factory.CreateClientWithCookies();
await admin.RegisterAsync("admin", admin: true);
@@ -177,14 +177,14 @@ public class StateTests
var state = await player.GetFromJsonAsync<JsonElement>("/api/state");
Assert.Equal(Phase.Results.ToString(), state.GetProperty("currentPhase").GetString());
Assert.Equal(nameof(Phase.Results), state.GetProperty("currentPhase").GetString());
Assert.True(state.GetProperty("resultsOpen").GetBoolean());
}
[Fact]
public async Task Name_endpoint_trims_and_rejects_blank()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var client = factory.CreateClientWithCookies();
await client.RegisterAsync("nametest");
@@ -200,7 +200,7 @@ public class StateTests
[Fact]
public async Task Phase_prev_admin_only()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var player = factory.CreateClientWithCookies();
await player.RegisterAsync("phase");
@@ -213,13 +213,13 @@ public class StateTests
var back = await admin.PostAsJsonAsync("/api/me/phase/prev", new { });
back.EnsureSuccessStatusCode();
var me = await admin.GetFromJsonAsync<JsonElement>("/api/me");
Assert.Equal(Phase.Suggest.ToString(), me.GetProperty("currentPhase").GetString());
Assert.Equal(nameof(Phase.Suggest), me.GetProperty("currentPhase").GetString());
}
[Fact]
public async Task State_endpoint_requires_auth_and_counts()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var anon = factory.CreateClient();
var unauthorized = await anon.GetAsync("/api/state");
Assert.NotEqual(HttpStatusCode.OK, unauthorized.StatusCode);
@@ -237,7 +237,7 @@ public class StateTests
[Fact]
public async Task Health_endpoint_ok()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var resp = await factory.CreateClient().GetFromJsonAsync<JsonElement>("/health");
Assert.Equal("ok", resp.GetProperty("status").GetString());
}
@@ -245,7 +245,7 @@ public class StateTests
[Fact]
public async Task GetPhase_aligns_to_results_when_open()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
await factory.WithDbContextAsync(async db =>
{
var player = new Player
@@ -253,8 +253,8 @@ public class StateTests
Id = Guid.NewGuid(),
Username = "phase",
NormalizedUsername = "phase",
PasswordHash = new byte[] { 1 },
PasswordSalt = new byte[] { 1 },
PasswordHash = [1],
PasswordSalt = [1],
DisplayName = "phase",
CurrentPhase = Phase.Vote
};
@@ -267,7 +267,7 @@ public class StateTests
using var scope = factory.Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
var playerId = await db.Players.Select(p => p.Id).FirstAsync();
var phase = await GameList.Endpoints.EndpointHelpers.GetPhase(db, playerId);
var phase = await Endpoints.EndpointHelpers.GetPhase(db, playerId);
Assert.Equal(Phase.Results, phase);
}

View File

@@ -2,7 +2,6 @@ using System.Net;
using System.Net.Http.Json;
using System.Text.Json;
using GameList.Tests.Support;
using GameList.Domain;
using Microsoft.EntityFrameworkCore;
namespace GameList.Tests;
@@ -12,7 +11,7 @@ public class SuggestionTests
[Fact]
public async Task Player_cannot_exceed_five_suggestions()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var client = factory.CreateClientWithCookies();
await client.RegisterAsync("suggestor");
@@ -50,7 +49,7 @@ public class SuggestionTests
[Fact]
public async Task Rejects_invalid_image_extension_and_player_counts()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var client = factory.CreateClientWithCookies();
await client.RegisterAsync("validate");
@@ -84,7 +83,7 @@ public class SuggestionTests
[Fact]
public async Task Joker_allows_single_extra_suggestion_and_unfinalizes_votes()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var player = factory.CreateClientWithCookies();
await player.RegisterAsync("joker");
var other = factory.CreateClientWithCookies();
@@ -126,7 +125,7 @@ public class SuggestionTests
[Fact]
public async Task Admin_can_update_during_vote_phase()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var admin = factory.CreateClientWithCookies();
await admin.RegisterAsync("admin", admin: true);
@@ -152,7 +151,7 @@ public class SuggestionTests
[Fact]
public async Task Phase_gate_blocks_player_update_in_vote_phase()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var player = factory.CreateClientWithCookies();
await player.RegisterAsync("phase");
var id = await player.CreateSuggestionAsync("Lock");
@@ -180,7 +179,7 @@ public class SuggestionTests
[Fact]
public async Task Player_cannot_edit_suggestion_in_results_phase()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var player = factory.CreateClientWithCookies();
await player.RegisterAsync("results");
var id = await player.CreateSuggestionAsync("Frozen");
@@ -217,7 +216,7 @@ public class SuggestionTests
[Fact]
public async Task Player_cannot_edit_other_players_suggestion()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var owner = factory.CreateClientWithCookies();
await owner.RegisterAsync("owner");
var other = factory.CreateClientWithCookies();
@@ -243,7 +242,7 @@ public class SuggestionTests
[Fact]
public async Task Joker_allows_unlimited_extra_suggestions_when_granted_multiple_times()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var client = factory.CreateClientWithCookies();
await client.RegisterAsync("sixth");
@@ -327,7 +326,7 @@ public class SuggestionTests
[Fact]
public async Task Unreachable_screenshot_url_is_rejected()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
factory.HttpHandler.SetResponder(_ => new HttpResponseMessage(HttpStatusCode.BadRequest));
var client = factory.CreateClientWithCookies();
@@ -351,7 +350,7 @@ public class SuggestionTests
[Fact]
public async Task Get_all_requires_vote_phase()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var client = factory.CreateClientWithCookies();
await client.RegisterAsync("viewer");
@@ -362,13 +361,33 @@ public class SuggestionTests
[Fact]
public async Task Mine_returns_ordered_list()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var client = factory.CreateClientWithCookies();
await client.RegisterAsync("mine");
await client.PostAsJsonAsync("/api/suggestions", new { Name = "Second", Genre = (string?)null, Description = (string?)null, ScreenshotUrl = (string?)null, YoutubeUrl = (string?)null, GameUrl = (string?)null, MinPlayers = (int?)null, MaxPlayers = (int?)null });
await client.PostAsJsonAsync("/api/suggestions", new
{
Name = "Second",
Genre = (string?)null,
Description = (string?)null,
ScreenshotUrl = (string?)null,
YoutubeUrl = (string?)null,
GameUrl = (string?)null,
MinPlayers = (int?)null,
MaxPlayers = (int?)null
});
await Task.Delay(10);
await client.PostAsJsonAsync("/api/suggestions", new { Name = "Third", Genre = (string?)null, Description = (string?)null, ScreenshotUrl = (string?)null, YoutubeUrl = (string?)null, GameUrl = (string?)null, MinPlayers = (int?)null, MaxPlayers = (int?)null });
await client.PostAsJsonAsync("/api/suggestions", new
{
Name = "Third",
Genre = (string?)null,
Description = (string?)null,
ScreenshotUrl = (string?)null,
YoutubeUrl = (string?)null,
GameUrl = (string?)null,
MinPlayers = (int?)null,
MaxPlayers = (int?)null
});
var mine = await client.GetFromJsonAsync<List<JsonElement>>("/api/suggestions/mine");
Assert.Equal("Second", mine![0].GetProperty("name").GetString());
@@ -377,7 +396,7 @@ public class SuggestionTests
[Fact]
public async Task Create_requires_suggest_phase_and_display_name()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var client = factory.CreateClientWithCookies();
await client.RegisterAsync("phasegate");
@@ -389,7 +408,17 @@ public class SuggestionTests
await db.SaveChangesAsync();
});
var badPhase = await client.PostAsJsonAsync("/api/suggestions", new { Name = "Nope", Genre = (string?)null, Description = (string?)null, ScreenshotUrl = (string?)null, YoutubeUrl = (string?)null, GameUrl = (string?)null, MinPlayers = (int?)null, MaxPlayers = (int?)null });
var badPhase = await client.PostAsJsonAsync("/api/suggestions", new
{
Name = "Nope",
Genre = (string?)null,
Description = (string?)null,
ScreenshotUrl = (string?)null,
YoutubeUrl = (string?)null,
GameUrl = (string?)null,
MinPlayers = (int?)null,
MaxPlayers = (int?)null
});
Assert.Equal(HttpStatusCode.BadRequest, badPhase.StatusCode);
await factory.WithDbContextAsync(async db =>
@@ -399,37 +428,97 @@ public class SuggestionTests
await db.SaveChangesAsync();
});
var noDisplay = await client.PostAsJsonAsync("/api/suggestions", new { Name = "NoDisplay", Genre = (string?)null, Description = (string?)null, ScreenshotUrl = (string?)null, YoutubeUrl = (string?)null, GameUrl = (string?)null, MinPlayers = (int?)null, MaxPlayers = (int?)null });
var noDisplay = await client.PostAsJsonAsync("/api/suggestions", new
{
Name = "NoDisplay",
Genre = (string?)null,
Description = (string?)null,
ScreenshotUrl = (string?)null,
YoutubeUrl = (string?)null,
GameUrl = (string?)null,
MinPlayers = (int?)null,
MaxPlayers = (int?)null
});
Assert.Equal(HttpStatusCode.BadRequest, noDisplay.StatusCode);
}
[Fact]
public async Task Rejects_invalid_urls_name_length_and_player_counts()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var client = factory.CreateClientWithCookies();
await client.RegisterAsync("validate2");
var badGame = await client.PostAsJsonAsync("/api/suggestions", new { Name = "Bad", Genre = (string?)null, Description = (string?)null, ScreenshotUrl = (string?)null, YoutubeUrl = (string?)null, GameUrl = "ftp://bad", MinPlayers = (int?)null, MaxPlayers = (int?)null });
var badGame = await client.PostAsJsonAsync("/api/suggestions", new
{
Name = "Bad",
Genre = (string?)null,
Description = (string?)null,
ScreenshotUrl = (string?)null,
YoutubeUrl = (string?)null,
GameUrl = "ftp://bad",
MinPlayers = (int?)null,
MaxPlayers = (int?)null
});
Assert.Equal(HttpStatusCode.BadRequest, badGame.StatusCode);
var badYoutube = await client.PostAsJsonAsync("/api/suggestions", new { Name = "BadYt", Genre = (string?)null, Description = (string?)null, ScreenshotUrl = (string?)null, YoutubeUrl = "file://bad", GameUrl = (string?)null, MinPlayers = (int?)null, MaxPlayers = (int?)null });
var badYoutube = await client.PostAsJsonAsync("/api/suggestions", new
{
Name = "BadYt",
Genre = (string?)null,
Description = (string?)null,
ScreenshotUrl = (string?)null,
YoutubeUrl = "file://bad",
GameUrl = (string?)null,
MinPlayers = (int?)null,
MaxPlayers = (int?)null
});
Assert.Equal(HttpStatusCode.BadRequest, badYoutube.StatusCode);
var longName = await client.PostAsJsonAsync("/api/suggestions", new { Name = new string('x', 101), Genre = (string?)null, Description = (string?)null, ScreenshotUrl = (string?)null, YoutubeUrl = (string?)null, GameUrl = (string?)null, MinPlayers = (int?)null, MaxPlayers = (int?)null });
var longName = await client.PostAsJsonAsync("/api/suggestions", new
{
Name = new string('x', 101),
Genre = (string?)null,
Description = (string?)null,
ScreenshotUrl = (string?)null,
YoutubeUrl = (string?)null,
GameUrl = (string?)null,
MinPlayers = (int?)null,
MaxPlayers = (int?)null
});
Assert.Equal(HttpStatusCode.BadRequest, longName.StatusCode);
var minOnly = await client.PostAsJsonAsync("/api/suggestions", new { Name = "MinOnly", Genre = (string?)null, Description = (string?)null, ScreenshotUrl = (string?)null, YoutubeUrl = (string?)null, GameUrl = (string?)null, MinPlayers = 2, MaxPlayers = (int?)null });
var minOnly = await client.PostAsJsonAsync("/api/suggestions", new
{
Name = "MinOnly",
Genre = (string?)null,
Description = (string?)null,
ScreenshotUrl = (string?)null,
YoutubeUrl = (string?)null,
GameUrl = (string?)null,
MinPlayers = 2,
MaxPlayers = (int?)null
});
Assert.Equal(HttpStatusCode.BadRequest, minOnly.StatusCode);
var maxTooHigh = await client.PostAsJsonAsync("/api/suggestions", new { Name = "MaxHigh", Genre = (string?)null, Description = (string?)null, ScreenshotUrl = (string?)null, YoutubeUrl = (string?)null, GameUrl = (string?)null, MinPlayers = 2, MaxPlayers = 40 });
var maxTooHigh = await client.PostAsJsonAsync("/api/suggestions", new
{
Name = "MaxHigh",
Genre = (string?)null,
Description = (string?)null,
ScreenshotUrl = (string?)null,
YoutubeUrl = (string?)null,
GameUrl = (string?)null,
MinPlayers = 2,
MaxPlayers = 40
});
Assert.Equal(HttpStatusCode.BadRequest, maxTooHigh.StatusCode);
}
[Fact]
public async Task Trims_and_truncates_optional_fields()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var client = factory.CreateClientWithCookies();
await client.RegisterAsync("trim");
@@ -460,7 +549,7 @@ public class SuggestionTests
[Fact]
public async Task Mine_excludes_other_players()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var a = factory.CreateClientWithCookies();
await a.RegisterAsync("alice");
var b = factory.CreateClientWithCookies();
@@ -470,14 +559,15 @@ public class SuggestionTests
await b.CreateSuggestionAsync("BobGame");
var mine = await a.GetFromJsonAsync<List<JsonElement>>("/api/suggestions/mine");
Assert.Single(mine!);
Assert.NotNull(mine);
Assert.Single(mine);
Assert.Equal("AliceGame", mine[0].GetProperty("name").GetString());
}
[Fact]
public async Task All_returns_link_metadata_and_ordering()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var client = factory.CreateClientWithCookies();
await client.RegisterAsync("owner");
@@ -507,7 +597,7 @@ public class SuggestionTests
[Fact]
public async Task Delete_respects_phase_and_clears_links_and_votes()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var owner = factory.CreateClientWithCookies();
await owner.RegisterAsync("deleter");
var other = factory.CreateClientWithCookies();
@@ -524,7 +614,11 @@ public class SuggestionTests
await owner.PostAsJsonAsync("/api/me/phase/next", new { }); // Vote
await other.PostAsJsonAsync("/api/me/phase/next", new { });
await other.PostAsJsonAsync("/api/votes", new { SuggestionId = id, Score = 5 });
await other.PostAsJsonAsync("/api/votes", new
{
SuggestionId = id,
Score = 5
});
var blocked = await owner.DeleteAsync($"/api/suggestions/{id}");
Assert.Equal(HttpStatusCode.BadRequest, blocked.StatusCode);

View File

@@ -1,18 +1,9 @@
using System.Net.Http;
namespace GameList.Tests.Support;
internal class StubHttpClientFactory : IHttpClientFactory
internal class StubHttpClientFactory(StubHttpMessageHandler handler) : IHttpClientFactory
{
private readonly StubHttpMessageHandler _handler;
public StubHttpClientFactory(StubHttpMessageHandler handler)
{
_handler = handler;
}
public HttpClient CreateClient(string name)
{
return new HttpClient(_handler, disposeHandler: false);
return new HttpClient(handler, disposeHandler: false);
}
}

View File

@@ -5,24 +5,16 @@ namespace GameList.Tests.Support;
internal class StubHttpMessageHandler : HttpMessageHandler
{
private Func<HttpRequestMessage, HttpResponseMessage> _responder;
public StubHttpMessageHandler()
{
_responder = DefaultResponder;
}
private Func<HttpRequestMessage, HttpResponseMessage> _responder = DefaultResponder;
public void SetResponder(Func<HttpRequestMessage, HttpResponseMessage> responder)
{
_responder = responder ?? DefaultResponder;
_responder = responder;
}
private static HttpResponseMessage DefaultResponder(HttpRequestMessage _)
{
var response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new ByteArrayContent(Array.Empty<byte>())
};
var response = new HttpResponseMessage(HttpStatusCode.OK) { Content = new ByteArrayContent([]) };
response.Content.Headers.ContentType = new MediaTypeHeaderValue("image/png");
response.Content.Headers.ContentLength = 0;
return response;

View File

@@ -49,5 +49,4 @@ internal static class TestClientExtensions
var me = await client.GetFromJsonAsync<JsonElement>("/api/me");
return Guid.Parse(me.GetProperty("id").GetString()!);
}
}

View File

@@ -1,5 +1,3 @@
using System.Collections.Generic;
using System.Linq;
using GameList.Data;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.Testing;
@@ -18,13 +16,7 @@ internal class TestWebApplicationFactory : WebApplicationFactory<Program>
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.UseEnvironment("Development");
builder.ConfigureAppConfiguration((context, config) =>
{
config.AddInMemoryCollection(new Dictionary<string, string?>
{
["ADMIN_PASSWORD"] = "admin-key"
});
});
builder.ConfigureAppConfiguration((_, config) => { config.AddInMemoryCollection(new Dictionary<string, string?> { ["ADMIN_PASSWORD"] = "admin-key" }); });
builder.ConfigureServices(services =>
{
@@ -37,10 +29,7 @@ internal class TestWebApplicationFactory : WebApplicationFactory<Program>
_connection = new SqliteConnection("Data Source=:memory:;Cache=Shared");
_connection.Open();
services.AddDbContext<AppDbContext>(options =>
{
options.UseSqlite(_connection);
});
services.AddDbContext<AppDbContext>(options => { options.UseSqlite(_connection); });
services.AddSingleton<StubHttpMessageHandler>();
services.AddSingleton<IHttpClientFactory, StubHttpClientFactory>();

View File

@@ -11,7 +11,7 @@ public class VoteTests
[Fact]
public async Task Finalizing_votes_blocks_further_changes()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var client = factory.CreateClientWithCookies();
await client.RegisterAsync("voter");
@@ -19,13 +19,21 @@ public class VoteTests
await client.PostAsJsonAsync("/api/me/phase/next", new { });
var vote = await client.PostAsJsonAsync("/api/votes", new { SuggestionId = suggestionId, Score = 7 });
var vote = await client.PostAsJsonAsync("/api/votes", new
{
SuggestionId = suggestionId,
Score = 7
});
vote.EnsureSuccessStatusCode();
var finalize = await client.PostAsJsonAsync("/api/votes/finalize", new { Final = true });
finalize.EnsureSuccessStatusCode();
var change = await client.PostAsJsonAsync("/api/votes", new { SuggestionId = suggestionId, Score = 5 });
var change = await client.PostAsJsonAsync("/api/votes", new
{
SuggestionId = suggestionId,
Score = 5
});
Assert.Equal(HttpStatusCode.BadRequest, change.StatusCode);
}
@@ -33,45 +41,57 @@ public class VoteTests
[Fact]
public async Task Score_out_of_range_rejected()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var client = factory.CreateClientWithCookies();
await client.RegisterAsync("score");
var id = await client.CreateSuggestionAsync("RangeGame");
await client.PostAsJsonAsync("/api/me/phase/next", new { });
var resp = await client.PostAsJsonAsync("/api/votes", new { SuggestionId = id, Score = 11 });
var resp = await client.PostAsJsonAsync("/api/votes", new
{
SuggestionId = id,
Score = 11
});
Assert.Equal(HttpStatusCode.BadRequest, resp.StatusCode);
}
[Fact]
public async Task Negative_score_rejected()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var client = factory.CreateClientWithCookies();
await client.RegisterAsync("negative");
var id = await client.CreateSuggestionAsync("RangeGame2");
await client.PostAsJsonAsync("/api/me/phase/next", new { });
var resp = await client.PostAsJsonAsync("/api/votes", new { SuggestionId = id, Score = -1 });
var resp = await client.PostAsJsonAsync("/api/votes", new
{
SuggestionId = id,
Score = -1
});
Assert.Equal(HttpStatusCode.BadRequest, resp.StatusCode);
}
[Fact]
public async Task Invalid_suggestion_id_rejected()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var client = factory.CreateClientWithCookies();
await client.RegisterAsync("invalid");
await client.PostAsJsonAsync("/api/me/phase/next", new { });
var resp = await client.PostAsJsonAsync("/api/votes", new { SuggestionId = 9999, Score = 5 });
var resp = await client.PostAsJsonAsync("/api/votes", new
{
SuggestionId = 9999,
Score = 5
});
Assert.Equal(HttpStatusCode.BadRequest, resp.StatusCode);
}
[Fact]
public async Task Votes_require_display_name()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var client = factory.CreateClientWithCookies();
await client.RegisterAsync("anon");
var id = await client.CreateSuggestionAsync("NeedName");
@@ -84,14 +104,18 @@ public class VoteTests
});
await client.PostAsJsonAsync("/api/me/phase/next", new { });
var resp = await client.PostAsJsonAsync("/api/votes", new { SuggestionId = id, Score = 5 });
var resp = await client.PostAsJsonAsync("/api/votes", new
{
SuggestionId = id,
Score = 5
});
Assert.Equal(HttpStatusCode.BadRequest, resp.StatusCode);
}
[Fact]
public async Task Finalize_only_in_vote_phase()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var client = factory.CreateClientWithCookies();
await client.RegisterAsync("phase");
@@ -102,12 +126,16 @@ public class VoteTests
[Fact]
public async Task Finalize_toggle_allows_unfinalize()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var client = factory.CreateClientWithCookies();
await client.RegisterAsync("toggle");
var id = await client.CreateSuggestionAsync("Toggle");
await client.PostAsJsonAsync("/api/me/phase/next", new { });
await client.PostAsJsonAsync("/api/votes", new { SuggestionId = id, Score = 5 });
await client.PostAsJsonAsync("/api/votes", new
{
SuggestionId = id,
Score = 5
});
var finalize = await client.PostAsJsonAsync("/api/votes/finalize", new { Final = true });
finalize.EnsureSuccessStatusCode();
@@ -121,7 +149,7 @@ public class VoteTests
[Fact]
public async Task Linked_votes_apply_to_all_linked_suggestions()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var admin = factory.CreateClientWithCookies();
await admin.RegisterAsync("admin", admin: true);
await admin.PostAsJsonAsync("/api/me/phase/next", new { });
@@ -134,23 +162,31 @@ public class VoteTests
await player.PostAsJsonAsync("/api/me/phase/next", new { });
var linkResponse = await admin.PostAsJsonAsync("/api/admin/link-suggestions", new { SourceSuggestionId = id1, TargetSuggestionId = id2 });
var linkResponse = await admin.PostAsJsonAsync("/api/admin/link-suggestions", new
{
SourceSuggestionId = id1,
TargetSuggestionId = id2
});
linkResponse.EnsureSuccessStatusCode();
var vote = await player.PostAsJsonAsync("/api/votes", new { SuggestionId = id1, Score = 9 });
var vote = await player.PostAsJsonAsync("/api/votes", new
{
SuggestionId = id1,
Score = 9
});
vote.EnsureSuccessStatusCode();
var mine = await player.GetFromJsonAsync<List<VoteRecord>>("/api/votes/mine");
Assert.NotNull(mine);
Assert.Equal(2, mine!.Count);
Assert.Equal(2, mine.Count);
Assert.All(mine, v => Assert.Equal(9, v.Score));
}
[Fact]
public async Task Linked_votes_apply_across_chain()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var admin = factory.CreateClientWithCookies();
await admin.RegisterAsync("admin", admin: true);
await admin.PostAsJsonAsync("/api/me/phase/next", new { });
@@ -164,22 +200,34 @@ public class VoteTests
await player.PostAsJsonAsync("/api/me/phase/next", new { });
await admin.PostAsJsonAsync("/api/admin/link-suggestions", new { SourceSuggestionId = a, TargetSuggestionId = b });
await admin.PostAsJsonAsync("/api/admin/link-suggestions", new { SourceSuggestionId = b, TargetSuggestionId = c });
await admin.PostAsJsonAsync("/api/admin/link-suggestions", new
{
SourceSuggestionId = a,
TargetSuggestionId = b
});
await admin.PostAsJsonAsync("/api/admin/link-suggestions", new
{
SourceSuggestionId = b,
TargetSuggestionId = c
});
var vote = await player.PostAsJsonAsync("/api/votes", new { SuggestionId = c, Score = 6 });
var vote = await player.PostAsJsonAsync("/api/votes", new
{
SuggestionId = c,
Score = 6
});
vote.EnsureSuccessStatusCode();
var mine = await player.GetFromJsonAsync<List<VoteRecord>>("/api/votes/mine");
Assert.NotNull(mine);
Assert.Equal(3, mine!.Count);
Assert.Equal(3, mine.Count);
Assert.All(mine, v => Assert.Equal(6, v.Score));
}
[Fact]
public async Task Votes_mine_requires_vote_phase_and_auth()
{
using var factory = new TestWebApplicationFactory();
await using var factory = new TestWebApplicationFactory();
var anon = factory.CreateClient();
var unauth = await anon.GetAsync("/api/votes/mine");
Assert.Equal(HttpStatusCode.Unauthorized, unauth.StatusCode);
@@ -190,5 +238,7 @@ public class VoteTests
Assert.Equal(HttpStatusCode.BadRequest, resp.StatusCode);
}
// ReSharper disable once NotAccessedPositionalProperty.Local
// ReSharper disable once ClassNeverInstantiated.Local
private record VoteRecord(int SuggestionId, int Score);
}