From 02d15e9c50b6c38845268708ef7b4eabf8177f67 Mon Sep 17 00:00:00 2001 From: Frank Tovar Date: Sun, 8 Feb 2026 14:54:12 +0100 Subject: [PATCH] Restrict results-close rollback to players with suggestions --- Endpoints/AdminWorkflowService.cs | 7 ++++++- GameList.Tests/AdminTests.cs | 32 +++++++++++++++++++++++++++++++ SPEC.md | 1 + wwwroot/data/i18n/faq/de.md | 2 +- wwwroot/data/i18n/faq/en.md | 2 +- 5 files changed, 41 insertions(+), 3 deletions(-) diff --git a/Endpoints/AdminWorkflowService.cs b/Endpoints/AdminWorkflowService.cs index 973bb09..dbed167 100644 --- a/Endpoints/AdminWorkflowService.cs +++ b/Endpoints/AdminWorkflowService.cs @@ -21,7 +21,12 @@ internal sealed class AdminWorkflowService(AppDbContext db) } else { - await db.Players.ExecuteUpdateAsync(p => p.SetProperty(x => x.CurrentPhase, Phase.Vote).SetProperty(x => x.VotesFinal, false)); + await db.Players + .Where(p => p.Suggestions.Any()) + .ExecuteUpdateAsync(p => p.SetProperty(x => x.CurrentPhase, Phase.Vote).SetProperty(x => x.VotesFinal, false)); + await db.Players + .Where(p => !p.Suggestions.Any()) + .ExecuteUpdateAsync(p => p.SetProperty(x => x.CurrentPhase, Phase.Suggest).SetProperty(x => x.VotesFinal, false)); } await db.SaveChangesAsync(); diff --git a/GameList.Tests/AdminTests.cs b/GameList.Tests/AdminTests.cs index a407b58..673a902 100644 --- a/GameList.Tests/AdminTests.cs +++ b/GameList.Tests/AdminTests.cs @@ -236,6 +236,7 @@ public class AdminTests await admin.RegisterAsync("admin", admin: true); var player = factory.CreateClientWithCookies(); await player.RegisterAsync("player"); + await player.CreateSuggestionAsync("Player game"); var open = await admin.PostAsJsonAsync("/api/admin/results", new { resultsOpen = true }); open.EnsureSuccessStatusCode(); @@ -263,6 +264,37 @@ public class AdminTests }); } + [Fact] + public async Task Admin_results_closing_sends_players_without_suggestions_to_suggest_phase() + { + await using var factory = new TestWebApplicationFactory(); + var admin = factory.CreateClientWithCookies(); + await admin.RegisterAsync("admin", admin: true); + + var voter = factory.CreateClientWithCookies(); + await voter.RegisterAsync("voter"); + await voter.CreateSuggestionAsync("Voter game"); + + var open = await admin.PostAsJsonAsync("/api/admin/results", new { resultsOpen = true }); + open.EnsureSuccessStatusCode(); + + var lateJoiner = factory.CreateClientWithCookies(); + await lateJoiner.RegisterAsync("late"); + + var close = await admin.PostAsJsonAsync("/api/admin/results", new { resultsOpen = false }); + close.EnsureSuccessStatusCode(); + + await factory.WithDbContextAsync(async db => + { + var voterPlayer = await db.Players.SingleAsync(p => p.Username == "voter"); + var latePlayer = await db.Players.SingleAsync(p => p.Username == "late"); + Assert.Equal(Phase.Vote, voterPlayer.CurrentPhase); + Assert.Equal(Phase.Suggest, latePlayer.CurrentPhase); + Assert.False(voterPlayer.VotesFinal); + Assert.False(latePlayer.VotesFinal); + }); + } + [Fact] public async Task Vote_status_lists_waiting_players() { diff --git a/SPEC.md b/SPEC.md index ac8db01..ad22cc9 100644 --- a/SPEC.md +++ b/SPEC.md @@ -30,6 +30,7 @@ Help a small Discord group (4–8 players) pick a co-op game via phased flow: ## Results Phase - Visible only after admin enables results; players auto-advance when opened - Leaderboard sorted by average score; shows totals, counts, player’s own vote, and links/media +- When results are closed again, only accounts with at least one suggestion return to Vote; accounts without suggestions return to Suggest ## Non-functional - Desktop + mobile friendly diff --git a/wwwroot/data/i18n/faq/de.md b/wwwroot/data/i18n/faq/de.md index 901cf9c..f83af2e 100644 --- a/wwwroot/data/i18n/faq/de.md +++ b/wwwroot/data/i18n/faq/de.md @@ -138,7 +138,7 @@ Admins können bei Bedarf zusätzliche Joker vergeben. ### Wann sind die Ergebnisse sichtbar? -Die Ergebnisse bleiben verborgen, bis ein Admin sie freigibt. Danach werden alle Spieler automatisch in die **Ergebnisphase** verschoben. Falls nötig, kann ein Admin die Ergebnisse wieder schließen: Alle kehren in die Abstimmungsphase zurück und alle Abstimmungen werden zur Anpassung zurückgesetzt. +Die Ergebnisse bleiben verborgen, bis ein Admin sie freigibt. Danach werden alle Spieler automatisch in die **Ergebnisphase** verschoben. Falls nötig, kann ein Admin die Ergebnisse wieder schließen: Konten mit mindestens einem eigenen Vorschlag kehren in die Abstimmungsphase zurück, Konten ohne Vorschläge in die Vorschlagsphase, und alle Abstimmungen werden zur Anpassung zurückgesetzt. ### Kann ich in der Ergebnisphase etwas bearbeiten? diff --git a/wwwroot/data/i18n/faq/en.md b/wwwroot/data/i18n/faq/en.md index 84d4e37..9affefb 100644 --- a/wwwroot/data/i18n/faq/en.md +++ b/wwwroot/data/i18n/faq/en.md @@ -142,7 +142,7 @@ Review your list and rescore before finalizing again. ### When are results visible? Results are hidden until an admin opens them. When opened, all players are automatically moved to the **Results phase**. -If needed, an admin can close the Results: everyone returns to the Vote phase, and all ballots are unfinalized for adjustments. +If needed, an admin can close the Results: players with at least one own suggestion return to the Vote phase, accounts without suggestions return to Suggest, and all ballots are unfinalized for adjustments. ### Can I edit anything in Results?