All checks were successful
Build / build (pull_request) Successful in 6m10s
- GameService.CreateChallengeAsync prüft jetzt, dass challengingPlayerId zum gameId gehört - Zwei neue Tests für Cross-Game-/Fremdspieler-Challenges - CI: dotnet workload install maui vor dem Restore, damit MAUI auf frischen Runnern baut
557 lines
22 KiB
C#
557 lines
22 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using SlipItIn.Server.Data;
|
|
using SlipItIn.Server.Services;
|
|
using SlipItIn.Shared.Models;
|
|
|
|
namespace SlipItIn.Server.Tests;
|
|
|
|
public class GameServiceTests
|
|
{
|
|
private readonly IDbContextFactory<SlipItInDbContext> _factory = TestDbHelper.CreateFactory();
|
|
private readonly GameService _sut;
|
|
|
|
public GameServiceTests()
|
|
{
|
|
_sut = new GameService(_factory, NullLogger<GameService>.Instance);
|
|
}
|
|
|
|
// ---------- CreateGameAsync ----------
|
|
|
|
[Fact]
|
|
public async Task CreateGameAsync_CreatesGameWithHostAsFirstPlayer()
|
|
{
|
|
using var db = _factory.CreateDbContext();
|
|
var host = TestDbHelper.SeedUser(db);
|
|
|
|
var game = await _sut.CreateGameAsync(host.Id, "conn-1");
|
|
|
|
Assert.Equal(host.Id, game.HostId);
|
|
Assert.Equal(GameStatus.Lobby, game.Status);
|
|
Assert.Equal(6, game.LobbyCode.Length);
|
|
Assert.Single(game.Players);
|
|
Assert.Equal(host.Id, game.Players.First().UserId);
|
|
Assert.Equal("conn-1", game.Players.First().ConnectionId);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CreateGameAsync_Throws_WhenHostNotFound()
|
|
{
|
|
await Assert.ThrowsAsync<InvalidOperationException>(() => _sut.CreateGameAsync(999));
|
|
}
|
|
|
|
// ---------- JoinGameAsync ----------
|
|
|
|
[Fact]
|
|
public async Task JoinGameAsync_AddsPlayerToGame()
|
|
{
|
|
using var db = _factory.CreateDbContext();
|
|
var host = TestDbHelper.SeedUser(db, "host", "host@t.local");
|
|
var joiner = TestDbHelper.SeedUser(db, "joiner", "joiner@t.local");
|
|
var (game, _) = TestDbHelper.SeedGame(db, host);
|
|
|
|
var result = await _sut.JoinGameAsync(game.LobbyCode, joiner.Id, "conn-2");
|
|
|
|
Assert.Equal(game.Id, result.Id);
|
|
Assert.Equal(2, db.Players.Count(p => p.GameId == game.Id));
|
|
Assert.Equal("conn-2", db.Players.Single(p => p.UserId == joiner.Id).ConnectionId);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task JoinGameAsync_Throws_WhenGameNotFound()
|
|
{
|
|
using var db = _factory.CreateDbContext();
|
|
var user = TestDbHelper.SeedUser(db);
|
|
|
|
await Assert.ThrowsAsync<InvalidOperationException>(() => _sut.JoinGameAsync("NOCODE", user.Id));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task JoinGameAsync_Throws_WhenGameAlreadyStarted()
|
|
{
|
|
using var db = _factory.CreateDbContext();
|
|
var host = TestDbHelper.SeedUser(db, "host", "host@t.local");
|
|
var joiner = TestDbHelper.SeedUser(db, "joiner", "joiner@t.local");
|
|
var (game, _) = TestDbHelper.SeedGame(db, host, GameStatus.InProgress);
|
|
|
|
await Assert.ThrowsAsync<InvalidOperationException>(() => _sut.JoinGameAsync(game.LobbyCode, joiner.Id));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task JoinGameAsync_Throws_WhenGameIsFull()
|
|
{
|
|
using var db = _factory.CreateDbContext();
|
|
var host = TestDbHelper.SeedUser(db, "host", "host@t.local");
|
|
var (game, _) = TestDbHelper.SeedGame(db, host);
|
|
game.MaxPlayers = 1;
|
|
await db.SaveChangesAsync();
|
|
var joiner = TestDbHelper.SeedUser(db, "joiner", "joiner@t.local");
|
|
|
|
await Assert.ThrowsAsync<InvalidOperationException>(() => _sut.JoinGameAsync(game.LobbyCode, joiner.Id));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task JoinGameAsync_Throws_WhenUserNotFound()
|
|
{
|
|
using var db = _factory.CreateDbContext();
|
|
var host = TestDbHelper.SeedUser(db);
|
|
var (game, _) = TestDbHelper.SeedGame(db, host);
|
|
|
|
await Assert.ThrowsAsync<InvalidOperationException>(() => _sut.JoinGameAsync(game.LobbyCode, 999));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task JoinGameAsync_Throws_WhenUserAlreadyInGame()
|
|
{
|
|
using var db = _factory.CreateDbContext();
|
|
var host = TestDbHelper.SeedUser(db);
|
|
var (game, _) = TestDbHelper.SeedGame(db, host);
|
|
|
|
await Assert.ThrowsAsync<InvalidOperationException>(() => _sut.JoinGameAsync(game.LobbyCode, host.Id));
|
|
}
|
|
|
|
// ---------- StartGameAsync ----------
|
|
|
|
[Fact]
|
|
public async Task StartGameAsync_SetsStatusAndCreatesFirstRound()
|
|
{
|
|
using var db = _factory.CreateDbContext();
|
|
var host = TestDbHelper.SeedUser(db);
|
|
var other = TestDbHelper.SeedUser(db, "other", "other@t.local");
|
|
var (game, _) = TestDbHelper.SeedGame(db, host);
|
|
db.Players.Add(new Player { UserId = other.Id, GameId = game.Id });
|
|
await db.SaveChangesAsync();
|
|
|
|
var result = await _sut.StartGameAsync(game.Id);
|
|
|
|
Assert.Equal(GameStatus.InProgress, result.Status);
|
|
Assert.NotNull(result.StartedAt);
|
|
var round = db.GameRounds.Single(r => r.GameId == game.Id);
|
|
Assert.Equal(1, round.RoundNumber);
|
|
Assert.Equal(RoundStatus.Waiting, round.Status);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task StartGameAsync_Throws_WhenGameNotFound()
|
|
{
|
|
await Assert.ThrowsAsync<InvalidOperationException>(() => _sut.StartGameAsync(999));
|
|
}
|
|
|
|
// ---------- SetPlayerReadyAsync ----------
|
|
|
|
[Fact]
|
|
public async Task SetPlayerReadyAsync_MarksPlayerReady()
|
|
{
|
|
using var db = _factory.CreateDbContext();
|
|
var host = TestDbHelper.SeedUser(db);
|
|
var (game, hostPlayer) = TestDbHelper.SeedGame(db, host);
|
|
|
|
var result = await _sut.SetPlayerReadyAsync(game.Id, hostPlayer.Id);
|
|
|
|
Assert.Equal(game.Id, result.Id);
|
|
using var verify = _factory.CreateDbContext();
|
|
Assert.True(verify.Players.Single(p => p.Id == hostPlayer.Id).IsReady);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SetPlayerReadyAsync_Throws_WhenPlayerNotFound()
|
|
{
|
|
using var db = _factory.CreateDbContext();
|
|
var host = TestDbHelper.SeedUser(db);
|
|
var (game, _) = TestDbHelper.SeedGame(db, host);
|
|
|
|
await Assert.ThrowsAsync<InvalidOperationException>(() => _sut.SetPlayerReadyAsync(game.Id, 999));
|
|
}
|
|
|
|
// ---------- DealCardsAsync ----------
|
|
|
|
[Fact]
|
|
public async Task DealCardsAsync_DealsFiveCardsPerPlayerAndActivatesRound()
|
|
{
|
|
using var db = _factory.CreateDbContext();
|
|
var host = TestDbHelper.SeedUser(db, "host", "host@t.local");
|
|
var other = TestDbHelper.SeedUser(db, "other", "other@t.local");
|
|
var (game, _) = TestDbHelper.SeedGame(db, host);
|
|
db.Players.Add(new Player { UserId = other.Id, GameId = game.Id });
|
|
var round = new GameRound { GameId = game.Id, RoundNumber = 1, Status = RoundStatus.Waiting };
|
|
db.GameRounds.Add(round);
|
|
TestDbHelper.SeedPhrases(db, 10);
|
|
await db.SaveChangesAsync();
|
|
|
|
var cards = await _sut.DealCardsAsync(game.Id);
|
|
|
|
Assert.Equal(10, cards.Count); // 2 Spieler x 5 Karten
|
|
Assert.Equal(5, cards.Count(c => c.PlayerId == game.Players.First().Id));
|
|
Assert.All(cards, c => Assert.False(c.IsUsed));
|
|
using var verify = _factory.CreateDbContext();
|
|
Assert.Equal(RoundStatus.Active, verify.GameRounds.Single(r => r.Id == round.Id).Status);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DealCardsAsync_Throws_WhenNoWaitingRound()
|
|
{
|
|
using var db = _factory.CreateDbContext();
|
|
var host = TestDbHelper.SeedUser(db);
|
|
var (game, _) = TestDbHelper.SeedGame(db, host);
|
|
TestDbHelper.SeedPhrases(db, 10);
|
|
await db.SaveChangesAsync();
|
|
|
|
await Assert.ThrowsAsync<InvalidOperationException>(() => _sut.DealCardsAsync(game.Id));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DealCardsAsync_Throws_WhenNotEnoughPhrases()
|
|
{
|
|
using var db = _factory.CreateDbContext();
|
|
var host = TestDbHelper.SeedUser(db);
|
|
var (game, _) = TestDbHelper.SeedGame(db, host);
|
|
db.GameRounds.Add(new GameRound { GameId = game.Id, RoundNumber = 1, Status = RoundStatus.Waiting });
|
|
TestDbHelper.SeedPhrases(db, 4);
|
|
await db.SaveChangesAsync();
|
|
|
|
await Assert.ThrowsAsync<InvalidOperationException>(() => _sut.DealCardsAsync(game.Id));
|
|
}
|
|
|
|
// ---------- SubmitSlipAsync ----------
|
|
|
|
[Fact]
|
|
public async Task SubmitSlipAsync_MarksCardAsUsed()
|
|
{
|
|
using var db = _factory.CreateDbContext();
|
|
var host = TestDbHelper.SeedUser(db);
|
|
var (game, hostPlayer) = TestDbHelper.SeedGame(db, host, GameStatus.InProgress);
|
|
var round = new GameRound { GameId = game.Id, RoundNumber = 1, Status = RoundStatus.Active };
|
|
db.GameRounds.Add(round);
|
|
var phrase = TestDbHelper.SeedPhrases(db, 1).Single();
|
|
var card = new PlayerCard { PlayerId = hostPlayer.Id, PhraseId = phrase.Id, GameRoundId = round.Id };
|
|
db.PlayerCards.Add(card);
|
|
await db.SaveChangesAsync();
|
|
|
|
var result = await _sut.SubmitSlipAsync(game.Id, hostPlayer.Id, card.Id);
|
|
|
|
Assert.True(result);
|
|
using var verify = _factory.CreateDbContext();
|
|
Assert.True(verify.PlayerCards.Single(c => c.Id == card.Id).IsUsed);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SubmitSlipAsync_Throws_WhenCardNotFound()
|
|
{
|
|
using var db = _factory.CreateDbContext();
|
|
var host = TestDbHelper.SeedUser(db);
|
|
var (game, _) = TestDbHelper.SeedGame(db, host, GameStatus.InProgress);
|
|
|
|
await Assert.ThrowsAsync<InvalidOperationException>(() => _sut.SubmitSlipAsync(game.Id, 1, 999));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SubmitSlipAsync_Throws_WhenCardBelongsToOtherPlayer()
|
|
{
|
|
using var db = _factory.CreateDbContext();
|
|
var host = TestDbHelper.SeedUser(db, "host", "host@t.local");
|
|
var other = TestDbHelper.SeedUser(db, "other", "other@t.local");
|
|
var (game, hostPlayer) = TestDbHelper.SeedGame(db, host, GameStatus.InProgress);
|
|
var otherPlayer = new Player { UserId = other.Id, GameId = game.Id };
|
|
db.Players.Add(otherPlayer);
|
|
var round = new GameRound { GameId = game.Id, RoundNumber = 1, Status = RoundStatus.Active };
|
|
db.GameRounds.Add(round);
|
|
var phrase = TestDbHelper.SeedPhrases(db, 1).Single();
|
|
var card = new PlayerCard { PlayerId = hostPlayer.Id, PhraseId = phrase.Id, GameRoundId = round.Id };
|
|
db.PlayerCards.Add(card);
|
|
await db.SaveChangesAsync();
|
|
|
|
// otherPlayer versucht, die Karte des Hosts zu verwenden
|
|
await Assert.ThrowsAsync<UnauthorizedAccessException>(() => _sut.SubmitSlipAsync(game.Id, otherPlayer.Id, card.Id));
|
|
}
|
|
|
|
// ---------- CreateChallengeAsync ----------
|
|
|
|
[Fact]
|
|
public async Task CreateChallengeAsync_CreatesPendingChallenge()
|
|
{
|
|
using var db = _factory.CreateDbContext();
|
|
var host = TestDbHelper.SeedUser(db, "host", "host@t.local");
|
|
var other = TestDbHelper.SeedUser(db, "other", "other@t.local");
|
|
var (game, hostPlayer) = TestDbHelper.SeedGame(db, host);
|
|
var otherPlayer = new Player { UserId = other.Id, GameId = game.Id };
|
|
db.Players.Add(otherPlayer);
|
|
var round = new GameRound { GameId = game.Id, RoundNumber = 1, Status = RoundStatus.Active };
|
|
db.GameRounds.Add(round);
|
|
var phrase = TestDbHelper.SeedPhrases(db, 1).Single();
|
|
var card = new PlayerCard { PlayerId = otherPlayer.Id, PhraseId = phrase.Id, GameRoundId = round.Id, IsUsed = true };
|
|
db.PlayerCards.Add(card);
|
|
await db.SaveChangesAsync();
|
|
|
|
var challenge = await _sut.CreateChallengeAsync(game.Id, hostPlayer.Id, card.Id);
|
|
|
|
Assert.Equal(round.Id, challenge.GameRoundId);
|
|
Assert.Equal(hostPlayer.Id, challenge.ChallengingPlayerId);
|
|
Assert.Equal(otherPlayer.Id, challenge.TargetPlayerId);
|
|
Assert.Equal(card.Id, challenge.TargetCardId);
|
|
Assert.Equal(ChallengeStatus.Pending, challenge.Status);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CreateChallengeAsync_Throws_WhenTargetCardNotFound()
|
|
{
|
|
await Assert.ThrowsAsync<InvalidOperationException>(() => _sut.CreateChallengeAsync(1, 1, 999));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CreateChallengeAsync_Throws_WhenNoActiveRound()
|
|
{
|
|
using var db = _factory.CreateDbContext();
|
|
var host = TestDbHelper.SeedUser(db);
|
|
var (game, hostPlayer) = TestDbHelper.SeedGame(db, host);
|
|
var phrase = TestDbHelper.SeedPhrases(db, 1).Single();
|
|
var round = new GameRound { GameId = game.Id, RoundNumber = 1, Status = RoundStatus.Waiting };
|
|
db.GameRounds.Add(round);
|
|
var card = new PlayerCard { PlayerId = hostPlayer.Id, PhraseId = phrase.Id, GameRoundId = round.Id };
|
|
db.PlayerCards.Add(card);
|
|
await db.SaveChangesAsync();
|
|
|
|
await Assert.ThrowsAsync<InvalidOperationException>(() => _sut.CreateChallengeAsync(game.Id, hostPlayer.Id, card.Id));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CreateChallengeAsync_Throws_WhenChallengerNotInGame()
|
|
{
|
|
using var db = _factory.CreateDbContext();
|
|
var host = TestDbHelper.SeedUser(db, "host", "host@t.local");
|
|
var other = TestDbHelper.SeedUser(db, "other", "other@t.local");
|
|
var outsider = TestDbHelper.SeedUser(db, "outsider", "outsider@t.local");
|
|
var (game, hostPlayer) = TestDbHelper.SeedGame(db, host);
|
|
var otherPlayer = new Player { UserId = other.Id, GameId = game.Id };
|
|
db.Players.Add(otherPlayer);
|
|
var round = new GameRound { GameId = game.Id, RoundNumber = 1, Status = RoundStatus.Active };
|
|
db.GameRounds.Add(round);
|
|
var phrase = TestDbHelper.SeedPhrases(db, 1).Single();
|
|
var card = new PlayerCard { PlayerId = otherPlayer.Id, PhraseId = phrase.Id, GameRoundId = round.Id, IsUsed = true };
|
|
db.PlayerCards.Add(card);
|
|
await db.SaveChangesAsync();
|
|
|
|
// outsider ist nicht in diesem Spiel und versucht eine Challenge anzulegen
|
|
await Assert.ThrowsAsync<UnauthorizedAccessException>(
|
|
() => _sut.CreateChallengeAsync(game.Id, hostPlayer.Id + 1000, card.Id));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CreateChallengeAsync_Throws_WhenChallengerFromOtherGame()
|
|
{
|
|
using var db = _factory.CreateDbContext();
|
|
var hostA = TestDbHelper.SeedUser(db, "hostA", "hostA@t.local");
|
|
var hostB = TestDbHelper.SeedUser(db, "hostB", "hostB@t.local");
|
|
var other = TestDbHelper.SeedUser(db, "other", "other@t.local");
|
|
var (gameA, hostPlayerA) = TestDbHelper.SeedGame(db, hostA);
|
|
var (gameB, _) = TestDbHelper.SeedGame(db, hostB);
|
|
var otherPlayerB = new Player { UserId = other.Id, GameId = gameB.Id };
|
|
db.Players.Add(otherPlayerB);
|
|
var roundB = new GameRound { GameId = gameB.Id, RoundNumber = 1, Status = RoundStatus.Active };
|
|
db.GameRounds.Add(roundB);
|
|
var phrase = TestDbHelper.SeedPhrases(db, 1).Single();
|
|
var cardB = new PlayerCard { PlayerId = otherPlayerB.Id, PhraseId = phrase.Id, GameRoundId = roundB.Id, IsUsed = true };
|
|
db.PlayerCards.Add(cardB);
|
|
await db.SaveChangesAsync();
|
|
|
|
// hostPlayerA gehört zu gameA, versucht aber eine Karte aus gameB zu challengen
|
|
await Assert.ThrowsAsync<UnauthorizedAccessException>(
|
|
() => _sut.CreateChallengeAsync(gameB.Id, hostPlayerA.Id, cardB.Id));
|
|
}
|
|
|
|
// ---------- ResolveChallengeAsync ----------
|
|
|
|
[Fact]
|
|
public async Task ResolveChallengeAsync_Approved_TransfersCardToChallenger()
|
|
{
|
|
using var db = _factory.CreateDbContext();
|
|
var host = TestDbHelper.SeedUser(db, "host", "host@t.local");
|
|
var other = TestDbHelper.SeedUser(db, "other", "other@t.local");
|
|
var (game, hostPlayer) = TestDbHelper.SeedGame(db, host);
|
|
var otherPlayer = new Player { UserId = other.Id, GameId = game.Id };
|
|
db.Players.Add(otherPlayer);
|
|
var round = new GameRound { GameId = game.Id, RoundNumber = 1, Status = RoundStatus.Active };
|
|
db.GameRounds.Add(round);
|
|
var phrase = TestDbHelper.SeedPhrases(db, 1).Single();
|
|
var card = new PlayerCard { PlayerId = otherPlayer.Id, PhraseId = phrase.Id, GameRoundId = round.Id, IsUsed = true };
|
|
db.PlayerCards.Add(card);
|
|
var challenge = new SlipChallenge
|
|
{
|
|
GameRoundId = round.Id,
|
|
ChallengingPlayerId = hostPlayer.Id,
|
|
TargetPlayerId = otherPlayer.Id,
|
|
TargetCardId = card.Id,
|
|
Status = ChallengeStatus.Pending
|
|
};
|
|
db.SlipChallenges.Add(challenge);
|
|
await db.SaveChangesAsync();
|
|
|
|
var result = await _sut.ResolveChallengeAsync(challenge.Id, approved: true);
|
|
|
|
Assert.Equal(ChallengeStatus.Approved, result.Status);
|
|
Assert.NotNull(result.ResolvedAt);
|
|
// Berechtigte Beschuldigung: Karte geht an den Beschuldiger
|
|
using var verify = _factory.CreateDbContext();
|
|
Assert.Equal(hostPlayer.Id, verify.PlayerCards.Single(c => c.Id == card.Id).PlayerId);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ResolveChallengeAsync_Rejected_KeepsCardWithTargetPlayer()
|
|
{
|
|
using var db = _factory.CreateDbContext();
|
|
var host = TestDbHelper.SeedUser(db, "host", "host@t.local");
|
|
var other = TestDbHelper.SeedUser(db, "other", "other@t.local");
|
|
var (game, hostPlayer) = TestDbHelper.SeedGame(db, host);
|
|
var otherPlayer = new Player { UserId = other.Id, GameId = game.Id };
|
|
db.Players.Add(otherPlayer);
|
|
var round = new GameRound { GameId = game.Id, RoundNumber = 1, Status = RoundStatus.Active };
|
|
db.GameRounds.Add(round);
|
|
var phrase = TestDbHelper.SeedPhrases(db, 1).Single();
|
|
var card = new PlayerCard { PlayerId = otherPlayer.Id, PhraseId = phrase.Id, GameRoundId = round.Id, IsUsed = true };
|
|
db.PlayerCards.Add(card);
|
|
var challenge = new SlipChallenge
|
|
{
|
|
GameRoundId = round.Id,
|
|
ChallengingPlayerId = hostPlayer.Id,
|
|
TargetPlayerId = otherPlayer.Id,
|
|
TargetCardId = card.Id,
|
|
Status = ChallengeStatus.Pending
|
|
};
|
|
db.SlipChallenges.Add(challenge);
|
|
await db.SaveChangesAsync();
|
|
|
|
var result = await _sut.ResolveChallengeAsync(challenge.Id, approved: false);
|
|
|
|
Assert.Equal(ChallengeStatus.Rejected, result.Status);
|
|
Assert.NotNull(result.ResolvedAt);
|
|
// Falsche Beschuldigung: Karte bleibt beim Beschuldigten
|
|
using var verify = _factory.CreateDbContext();
|
|
Assert.Equal(otherPlayer.Id, verify.PlayerCards.Single(c => c.Id == card.Id).PlayerId);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ResolveChallengeAsync_Throws_WhenChallengeNotFound()
|
|
{
|
|
await Assert.ThrowsAsync<InvalidOperationException>(() => _sut.ResolveChallengeAsync(999, true));
|
|
}
|
|
|
|
// ---------- GetGameStateAsync ----------
|
|
|
|
[Fact]
|
|
public async Task GetGameStateAsync_ReturnsStateWithoutRound_WhenNoRoundsExist()
|
|
{
|
|
using var db = _factory.CreateDbContext();
|
|
var host = TestDbHelper.SeedUser(db);
|
|
var (game, _) = TestDbHelper.SeedGame(db, host);
|
|
|
|
var state = await _sut.GetGameStateAsync(game.Id);
|
|
|
|
Assert.Equal(game.Id, state.GameId);
|
|
Assert.Equal(game.LobbyCode, state.LobbyCode);
|
|
Assert.Equal("Lobby", state.Status);
|
|
Assert.Single(state.Players);
|
|
Assert.Equal(host.Username, state.Players[0].Username);
|
|
Assert.Equal(0, state.CurrentRound);
|
|
Assert.Equal(45, state.RoundTimeRemaining); // Default RoundDurationSeconds
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetGameStateAsync_ComputesRemainingTime_WhenRoundActive()
|
|
{
|
|
using var db = _factory.CreateDbContext();
|
|
var host = TestDbHelper.SeedUser(db);
|
|
var (game, _) = TestDbHelper.SeedGame(db, host);
|
|
game.RoundDurationSeconds = 45;
|
|
db.GameRounds.Add(new GameRound
|
|
{
|
|
GameId = game.Id,
|
|
RoundNumber = 2,
|
|
Status = RoundStatus.Active,
|
|
StartedAt = DateTime.UtcNow.AddSeconds(-20)
|
|
});
|
|
await db.SaveChangesAsync();
|
|
|
|
var state = await _sut.GetGameStateAsync(game.Id);
|
|
|
|
Assert.Equal(2, state.CurrentRound);
|
|
Assert.InRange(state.RoundTimeRemaining, 24, 25);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetGameStateAsync_ClampsRemainingTimeToZero_WhenRoundExpired()
|
|
{
|
|
using var db = _factory.CreateDbContext();
|
|
var host = TestDbHelper.SeedUser(db);
|
|
var (game, _) = TestDbHelper.SeedGame(db, host);
|
|
db.GameRounds.Add(new GameRound
|
|
{
|
|
GameId = game.Id,
|
|
RoundNumber = 1,
|
|
Status = RoundStatus.Active,
|
|
StartedAt = DateTime.UtcNow.AddSeconds(-120)
|
|
});
|
|
await db.SaveChangesAsync();
|
|
|
|
var state = await _sut.GetGameStateAsync(game.Id);
|
|
|
|
Assert.Equal(0, state.RoundTimeRemaining);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetGameStateAsync_Throws_WhenGameNotFound()
|
|
{
|
|
await Assert.ThrowsAsync<InvalidOperationException>(() => _sut.GetGameStateAsync(999));
|
|
}
|
|
|
|
// ---------- GetPlayerHandAsync ----------
|
|
|
|
[Fact]
|
|
public async Task GetPlayerHandAsync_ReturnsCardsWithPhraseText()
|
|
{
|
|
using var db = _factory.CreateDbContext();
|
|
var host = TestDbHelper.SeedUser(db);
|
|
var (game, hostPlayer) = TestDbHelper.SeedGame(db, host);
|
|
var round = new GameRound { GameId = game.Id, RoundNumber = 1, Status = RoundStatus.Active };
|
|
db.GameRounds.Add(round);
|
|
var phrases = TestDbHelper.SeedPhrases(db, 2);
|
|
db.PlayerCards.Add(new PlayerCard { PlayerId = hostPlayer.Id, PhraseId = phrases[0].Id, GameRoundId = round.Id });
|
|
db.PlayerCards.Add(new PlayerCard { PlayerId = hostPlayer.Id, PhraseId = phrases[1].Id, GameRoundId = round.Id, IsUsed = true });
|
|
await db.SaveChangesAsync();
|
|
|
|
var hand = await _sut.GetPlayerHandAsync(hostPlayer.Id);
|
|
|
|
Assert.Equal(hostPlayer.Id, hand.PlayerId);
|
|
Assert.Equal(2, hand.Cards.Count);
|
|
Assert.Contains(hand.Cards, c => c.Text == "Phrase 1" && !c.IsUsed);
|
|
Assert.Contains(hand.Cards, c => c.Text == "Phrase 2" && c.IsUsed);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetPlayerHandAsync_ReturnsEmptyHand_WhenNoCards()
|
|
{
|
|
var hand = await _sut.GetPlayerHandAsync(999);
|
|
|
|
Assert.Equal(999, hand.PlayerId);
|
|
Assert.Empty(hand.Cards);
|
|
}
|
|
|
|
// ---------- GetGameAsync ----------
|
|
|
|
[Fact]
|
|
public async Task GetGameAsync_ReturnsGame()
|
|
{
|
|
using var db = _factory.CreateDbContext();
|
|
var host = TestDbHelper.SeedUser(db);
|
|
var (game, _) = TestDbHelper.SeedGame(db, host);
|
|
|
|
var result = await _sut.GetGameAsync(game.Id);
|
|
|
|
Assert.Equal(game.Id, result.Id);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetGameAsync_Throws_WhenGameNotFound()
|
|
{
|
|
await Assert.ThrowsAsync<InvalidOperationException>(() => _sut.GetGameAsync(999));
|
|
}
|
|
}
|