Tech-Debt-Cleanup: Doku, Security-Hardening, Server-Validierung, Client-Stabilitaet #2

Merged
tim merged 4 commits from feature/techdebt-cleanup into master 2026-09-01 19:00:25 +00:00
3 changed files with 53 additions and 0 deletions
Showing only changes of commit 7953b8ddd0 - Show all commits

View File

@@ -19,6 +19,9 @@ jobs:
with:
dotnet-version: "10.0.x"
- name: Install MAUI workload
run: dotnet workload install maui
- name: Restore
run: dotnet restore SlipItIn.slnx

View File

@@ -313,6 +313,51 @@ public class GameServiceTests
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]

View File

@@ -176,6 +176,11 @@ public class GameService : IGameService
.Where(gr => gr.GameId == gameId && gr.Status == RoundStatus.Active)
.FirstOrDefaultAsync() ?? throw new InvalidOperationException("No active round found");
var challengerBelongsToGame = await context.Players
.AnyAsync(p => p.Id == challengingPlayerId && p.GameId == gameId);
if (!challengerBelongsToGame)
throw new UnauthorizedAccessException("Challenger does not belong to this game");
var targetCard = await context.PlayerCards
.FirstOrDefaultAsync(pc => pc.Id == targetCardId) ?? throw new InvalidOperationException("Target card not found");