using System.Net; using System.Net.Http.Json; using System.Text.Json; using GameList.Tests.Support; using Microsoft.EntityFrameworkCore; namespace GameList.Tests; public class VoteTests { [Fact] public async Task Finalizing_votes_blocks_further_changes() { await using var factory = new TestWebApplicationFactory(); var client = factory.CreateClientWithCookies(); await client.RegisterAsync("voter"); var suggestionId = await client.CreateSuggestionAsync("VoteGame"); await client.PostAsJsonAsync("/api/me/phase/next", new { }); 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 }); Assert.Equal(HttpStatusCode.BadRequest, change.StatusCode); } [Fact] public async Task Score_out_of_range_rejected() { 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 }); Assert.Equal(HttpStatusCode.BadRequest, resp.StatusCode); } [Fact] public async Task Negative_score_rejected() { 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 }); Assert.Equal(HttpStatusCode.BadRequest, resp.StatusCode); } [Fact] public async Task Invalid_suggestion_id_rejected() { await using var factory = new TestWebApplicationFactory(); var client = factory.CreateClientWithCookies(); await client.RegisterAsync("invalid"); await client.AdvanceToVoteAsync("Invalid seed"); 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() { await using var factory = new TestWebApplicationFactory(); var client = factory.CreateClientWithCookies(); await client.RegisterAsync("anon"); var id = await client.CreateSuggestionAsync("NeedName"); await factory.WithDbContextAsync(async db => { var p = await db.Players.SingleAsync(); p.DisplayName = null; await db.SaveChangesAsync(); }); await client.PostAsJsonAsync("/api/me/phase/next", new { }); 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() { await using var factory = new TestWebApplicationFactory(); var client = factory.CreateClientWithCookies(); await client.RegisterAsync("phase"); var resp = await client.PostAsJsonAsync("/api/votes/finalize", new { Final = true }); Assert.Equal(HttpStatusCode.BadRequest, resp.StatusCode); } [Fact] public async Task Finalize_toggle_allows_unfinalize() { 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 }); var finalize = await client.PostAsJsonAsync("/api/votes/finalize", new { Final = true }); finalize.EnsureSuccessStatusCode(); var unfinalize = await client.PostAsJsonAsync("/api/votes/finalize", new { Final = false }); unfinalize.EnsureSuccessStatusCode(); var me = await client.GetFromJsonAsync("/api/me"); Assert.False(me.GetProperty("votesFinal").GetBoolean()); } [Fact] public async Task Linked_votes_apply_to_all_linked_suggestions() { await using var factory = new TestWebApplicationFactory(); var admin = factory.CreateClientWithCookies(); await admin.RegisterAsync("admin", admin: true); await admin.AdvanceToVoteAsync("Admin link seed"); var player = factory.CreateClientWithCookies(); await player.RegisterAsync("linker"); var id1 = await player.CreateSuggestionAsync("Game A"); var id2 = await player.CreateSuggestionAsync("Game B"); await player.PostAsJsonAsync("/api/me/phase/next", new { }); 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 }); vote.EnsureSuccessStatusCode(); var mine = await player.GetFromJsonAsync>("/api/votes/mine"); Assert.NotNull(mine); Assert.Equal(2, mine.Count); Assert.All(mine, v => Assert.Equal(9, v.Score)); } [Fact] public async Task Linked_votes_apply_across_chain() { await using var factory = new TestWebApplicationFactory(); var admin = factory.CreateClientWithCookies(); await admin.RegisterAsync("admin", admin: true); await admin.AdvanceToVoteAsync("Admin chain seed"); var player = factory.CreateClientWithCookies(); await player.RegisterAsync("chain"); var a = await player.CreateSuggestionAsync("A"); var b = await player.CreateSuggestionAsync("B"); var c = await player.CreateSuggestionAsync("C"); 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 }); var vote = await player.PostAsJsonAsync("/api/votes", new { SuggestionId = c, Score = 6 }); vote.EnsureSuccessStatusCode(); var mine = await player.GetFromJsonAsync>("/api/votes/mine"); Assert.NotNull(mine); 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() { await using var factory = new TestWebApplicationFactory(); var anon = factory.CreateClient(); var unauth = await anon.GetAsync("/api/votes/mine"); Assert.Equal(HttpStatusCode.Unauthorized, unauth.StatusCode); var client = factory.CreateClientWithCookies(); await client.RegisterAsync("phaseguard"); var resp = await client.GetAsync("/api/votes/mine"); Assert.Equal(HttpStatusCode.BadRequest, resp.StatusCode); } // ReSharper disable once NotAccessedPositionalProperty.Local // ReSharper disable once ClassNeverInstantiated.Local private record VoteRecord(int SuggestionId, int Score); }