414 lines
15 KiB
C#
414 lines
15 KiB
C#
using System.Net;
|
|
using System.Net.Http.Json;
|
|
using System.Text.Json;
|
|
using GameList.Domain;
|
|
using GameList.Tests.Support;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace GameList.Tests;
|
|
|
|
public class AdminTests
|
|
{
|
|
[Fact]
|
|
public async Task Admin_vote_status_marks_ready_when_all_finalized()
|
|
{
|
|
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
|
|
|
|
var p1 = factory.CreateClientWithCookies();
|
|
await p1.RegisterAsync("alice");
|
|
var p2 = factory.CreateClientWithCookies();
|
|
await p2.RegisterAsync("bob");
|
|
await p2.PostAsJsonAsync("/api/me/phase/next", new { });
|
|
|
|
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/finalize", new { Final = true });
|
|
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 });
|
|
|
|
var status = await admin.GetFromJsonAsync<JsonElement>("/api/admin/vote-status");
|
|
|
|
Assert.True(status.GetProperty("ready").GetBoolean());
|
|
Assert.Equal(0, status.GetProperty("waiting").GetArrayLength());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Grant_joker_only_in_vote_phase()
|
|
{
|
|
await using var factory = new TestWebApplicationFactory();
|
|
var admin = factory.CreateClientWithCookies();
|
|
await admin.RegisterAsync("admin", admin: true);
|
|
|
|
var p = factory.CreateClientWithCookies();
|
|
await p.RegisterAsync("player");
|
|
|
|
var give = await admin.PostAsJsonAsync("/api/admin/joker", new { playerId = (await p.GetProfileIdAsync()) });
|
|
Assert.Equal(HttpStatusCode.BadRequest, give.StatusCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Delete_player_cascades_suggestions_and_votes()
|
|
{
|
|
await using var factory = new TestWebApplicationFactory();
|
|
var admin = factory.CreateClientWithCookies();
|
|
await admin.RegisterAsync("admin", admin: true);
|
|
|
|
var player = factory.CreateClientWithCookies();
|
|
await player.RegisterAsync("deleteme");
|
|
var suggestionId = await player.CreateSuggestionAsync("DeleteGame");
|
|
|
|
await player.PostAsJsonAsync("/api/me/phase/next", new { });
|
|
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(db =>
|
|
{
|
|
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()
|
|
{
|
|
await using var factory = new TestWebApplicationFactory();
|
|
var admin = factory.CreateClientWithCookies();
|
|
await admin.RegisterAsync("admin", admin: true);
|
|
var player = factory.CreateClientWithCookies();
|
|
await player.RegisterAsync("linker");
|
|
|
|
var a = await player.CreateSuggestionAsync("Game A");
|
|
var b = await player.CreateSuggestionAsync("Game B");
|
|
|
|
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
|
|
});
|
|
Assert.Equal(HttpStatusCode.BadRequest, same.StatusCode);
|
|
|
|
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
|
|
});
|
|
Assert.Equal(HttpStatusCode.BadRequest, already.StatusCode);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Unlink_suggestions_clears_group_votes()
|
|
{
|
|
await using var factory = new TestWebApplicationFactory();
|
|
var admin = factory.CreateClientWithCookies();
|
|
await admin.RegisterAsync("admin", admin: true);
|
|
var player = factory.CreateClientWithCookies();
|
|
await player.RegisterAsync("unlinker");
|
|
|
|
var a = await player.CreateSuggestionAsync("Game A");
|
|
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 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(db =>
|
|
{
|
|
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()
|
|
{
|
|
await using var factory = new TestWebApplicationFactory();
|
|
var admin = factory.CreateClientWithCookies();
|
|
await admin.RegisterAsync("admin", admin: true);
|
|
var player = factory.CreateClientWithCookies();
|
|
await player.RegisterAsync("player");
|
|
await player.CreateSuggestionAsync("Keep");
|
|
|
|
var reset = await admin.PostAsJsonAsync("/api/admin/reset", new { });
|
|
reset.EnsureSuccessStatusCode();
|
|
|
|
await factory.WithDbContextAsync(db =>
|
|
{
|
|
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(db =>
|
|
{
|
|
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()
|
|
{
|
|
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 open = await admin.PostAsJsonAsync("/api/admin/results", new { resultsOpen = true });
|
|
open.EnsureSuccessStatusCode();
|
|
|
|
await factory.WithDbContextAsync(async db =>
|
|
{
|
|
var p = await db.Players.FirstAsync(x => !x.IsAdmin);
|
|
p.VotesFinal = true;
|
|
await db.SaveChangesAsync();
|
|
});
|
|
|
|
var beforeState = await factory.WithDbContextAsync(async db => await db.AppState.AsNoTracking().FirstAsync());
|
|
await Task.Delay(5);
|
|
var close = await admin.PostAsJsonAsync("/api/admin/results", new { resultsOpen = false });
|
|
close.EnsureSuccessStatusCode();
|
|
|
|
await factory.WithDbContextAsync(async db =>
|
|
{
|
|
var p = await db.Players.FirstAsync(x => !x.IsAdmin);
|
|
Assert.Equal(Phase.Vote, p.CurrentPhase);
|
|
Assert.False(p.VotesFinal);
|
|
var state = await db.AppState.AsNoTracking().FirstAsync();
|
|
Assert.False(state.ResultsOpen);
|
|
Assert.True(state.UpdatedAt > beforeState.UpdatedAt);
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Vote_status_lists_waiting_players()
|
|
{
|
|
await using var factory = new TestWebApplicationFactory();
|
|
var admin = factory.CreateClientWithCookies();
|
|
await admin.RegisterAsync("admin", admin: true);
|
|
await admin.PostAsJsonAsync("/api/me/phase/next", new { });
|
|
|
|
var p1 = factory.CreateClientWithCookies();
|
|
await p1.RegisterAsync("alice");
|
|
var p2 = factory.CreateClientWithCookies();
|
|
await p2.RegisterAsync("bob");
|
|
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/finalize", new { Final = true });
|
|
|
|
var status = await admin.GetFromJsonAsync<JsonElement>("/api/admin/vote-status");
|
|
Assert.False(status.GetProperty("ready").GetBoolean());
|
|
var waiting = status.GetProperty("waiting").EnumerateArray().Select(e => e.GetString()).ToList();
|
|
Assert.Contains("bob-name", waiting);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Grant_joker_in_vote_sets_flag_and_unfinalizes()
|
|
{
|
|
await using var factory = new TestWebApplicationFactory();
|
|
var admin = factory.CreateClientWithCookies();
|
|
await admin.RegisterAsync("admin", admin: true);
|
|
|
|
var p = factory.CreateClientWithCookies();
|
|
await p.RegisterAsync("player");
|
|
await p.PostAsJsonAsync("/api/me/phase/next", new { });
|
|
await p.PostAsJsonAsync("/api/votes/finalize", new { Final = true });
|
|
|
|
var give = await admin.PostAsJsonAsync("/api/admin/joker", new { playerId = (await p.GetProfileIdAsync()) });
|
|
give.EnsureSuccessStatusCode();
|
|
|
|
await factory.WithDbContextAsync(async db =>
|
|
{
|
|
var player = await db.Players.SingleAsync(x => x.Username == "player");
|
|
Assert.True(player.HasJoker);
|
|
Assert.False(player.VotesFinal);
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Link_requires_vote_phase_and_reparents_votes_reset()
|
|
{
|
|
await using var factory = new TestWebApplicationFactory();
|
|
var admin = factory.CreateClientWithCookies();
|
|
await admin.RegisterAsync("admin", admin: true);
|
|
var player = factory.CreateClientWithCookies();
|
|
await player.RegisterAsync("linker");
|
|
|
|
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
|
|
});
|
|
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/finalize", new { Final = true });
|
|
|
|
var link = await admin.PostAsJsonAsync("/api/admin/link-suggestions", new
|
|
{
|
|
SourceSuggestionId = a,
|
|
TargetSuggestionId = b
|
|
});
|
|
link.EnsureSuccessStatusCode();
|
|
|
|
await factory.WithDbContextAsync(async db =>
|
|
{
|
|
var votes = await db.Votes.ToListAsync();
|
|
Assert.Empty(votes);
|
|
var p = await db.Players.SingleAsync(x => x.Username == "linker");
|
|
Assert.False(p.VotesFinal);
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Unlink_not_found_returns_empty_payload()
|
|
{
|
|
await using var factory = new TestWebApplicationFactory();
|
|
var admin = factory.CreateClientWithCookies();
|
|
await admin.RegisterAsync("admin", admin: true);
|
|
await admin.PostAsJsonAsync("/api/me/phase/next", new { });
|
|
|
|
var resp = await admin.PostAsJsonAsync("/api/admin/unlink-suggestions", new { suggestionId = 9999 });
|
|
resp.EnsureSuccessStatusCode();
|
|
var json = await resp.Content.ReadFromJsonAsync<JsonElement>();
|
|
Assert.Equal(0, json.GetProperty("unlinkedSuggestionIds").GetArrayLength());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Reset_clears_flags_and_factory_reset_seeds_defaults()
|
|
{
|
|
await using var factory = new TestWebApplicationFactory();
|
|
var admin = factory.CreateClientWithCookies();
|
|
await admin.RegisterAsync("admin", admin: true);
|
|
var p = factory.CreateClientWithCookies();
|
|
await p.RegisterAsync("flags");
|
|
|
|
await factory.WithDbContextAsync(async db =>
|
|
{
|
|
var player = await db.Players.SingleAsync(x => x.Username == "flags");
|
|
player.HasJoker = true;
|
|
player.VotesFinal = true;
|
|
await db.SaveChangesAsync();
|
|
});
|
|
|
|
var reset = await admin.PostAsJsonAsync("/api/admin/reset", new { });
|
|
reset.EnsureSuccessStatusCode();
|
|
|
|
await factory.WithDbContextAsync(async db =>
|
|
{
|
|
var player = await db.Players.SingleAsync(x => x.Username == "flags");
|
|
Assert.False(player.HasJoker);
|
|
Assert.False(player.VotesFinal);
|
|
var state = await db.AppState.AsNoTracking().FirstAsync();
|
|
Assert.False(state.ResultsOpen);
|
|
});
|
|
|
|
var factoryReset = await admin.PostAsJsonAsync("/api/admin/factory-reset", new { });
|
|
factoryReset.EnsureSuccessStatusCode();
|
|
await factory.WithDbContextAsync(async db =>
|
|
{
|
|
var state = await db.AppState.AsNoTracking().FirstAsync();
|
|
Assert.False(state.ResultsOpen);
|
|
});
|
|
}
|
|
}
|