Files
SlipItIn/SlipItIn.Server.Tests/GameServiceTests.cs
Tim Krampitz 510cc0e520 Testprojekt hinzugefügt & umfassende Server-Tests
Neues Testprojekt SlipItIn.Server.Tests erstellt und in die Solution eingebunden. Unit- und Integrationstests für AuthController, DbSeeder, GameHub und GameService implementiert. TestDbHelper für InMemory-DbContexts und Testdaten hinzugefügt. Tests prüfen Erfolgs- und Fehlerfälle inkl. Validierungen, Fehlerbehandlung und DB-Zustände.
2026-08-22 19:31:45 +02:00

505 lines
19 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 (game, _) = TestDbHelper.SeedGame(db, host);
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);
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()
{
await Assert.ThrowsAsync<InvalidOperationException>(() => _sut.SubmitSlipAsync(1, 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);
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 };
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));
}
// ---------- ResolveChallengeAsync ----------
[Fact]
public async Task ResolveChallengeAsync_Approved_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 };
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);
// 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_Rejected_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 };
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);
// 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_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 };
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));
}
}