Compare commits
3 Commits
5e22f0ab9b
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 118660a207 | |||
|
|
7953b8ddd0 | ||
|
|
660fc4d311 |
18
.github/workflows/build.yml
vendored
18
.github/workflows/build.yml
vendored
@@ -19,8 +19,22 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
dotnet-version: "10.0.x"
|
dotnet-version: "10.0.x"
|
||||||
|
|
||||||
|
- name: Install MAUI workload
|
||||||
|
run: dotnet workload install maui
|
||||||
|
|
||||||
- name: Restore
|
- name: Restore
|
||||||
run: dotnet restore SlipItIn.slnx
|
run: dotnet restore SlipItIn.slnx
|
||||||
|
|
||||||
- name: Build
|
- name: Build Server-Stack
|
||||||
run: dotnet build SlipItIn.slnx --no-restore --configuration Release
|
run: |
|
||||||
|
dotnet build SlipItIn.Shared/SlipItIn.Shared.csproj --no-restore --configuration Release
|
||||||
|
dotnet build SlipItIn.ServiceDefaults/SlipItIn.ServiceDefaults.csproj --no-restore --configuration Release
|
||||||
|
dotnet build SlipItIn.Server/SlipItIn.Server.csproj --no-restore --configuration Release
|
||||||
|
dotnet build SlipItIn.Server.Tests/SlipItIn.Server.Tests.csproj --no-restore --configuration Release
|
||||||
|
dotnet build SlipItIn.AppHost/SlipItIn.AppHost.csproj --no-restore --configuration Release
|
||||||
|
|
||||||
|
- name: Build MAUI Client
|
||||||
|
run: dotnet build SlipItIn/SlipItIn.csproj --no-restore --configuration Debug
|
||||||
|
|
||||||
|
- name: Run Server Tests
|
||||||
|
run: dotnet test SlipItIn.Server.Tests/SlipItIn.Server.Tests.csproj --no-restore --configuration Release
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
<Solution>
|
|
||||||
<Project Path="SlipItIn.AppHost/SlipItIn.AppHost.csproj" />
|
|
||||||
<Project Path="SlipItIn.Server.Tests/SlipItIn.Server.Tests.csproj" />
|
|
||||||
<Project Path="SlipItIn.Server/SlipItIn.Server.csproj" />
|
|
||||||
<Project Path="SlipItIn.ServiceDefaults/SlipItIn.ServiceDefaults.csproj" />
|
|
||||||
<Project Path="SlipItIn.Shared/SlipItIn.Shared.csproj" />
|
|
||||||
<Project Path="SlipItIn/SlipItIn.csproj">
|
|
||||||
<Deploy Solution="Debug|*" />
|
|
||||||
</Project>
|
|
||||||
</Solution>
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
using SlipItIn.Server.Controllers;
|
using SlipItIn.Server.Controllers;
|
||||||
using SlipItIn.Server.Data;
|
using SlipItIn.Server.Data;
|
||||||
using SlipItIn.Shared.DTOs;
|
using SlipItIn.Shared.DTOs;
|
||||||
@@ -26,7 +27,15 @@ public class AuthControllerTests
|
|||||||
["Jwt:ExpirationMinutes"] = "60"
|
["Jwt:ExpirationMinutes"] = "60"
|
||||||
})
|
})
|
||||||
.Build();
|
.Build();
|
||||||
_sut = new AuthController(_factory, config);
|
_sut = new AuthController(_factory, config, new TestHostEnvironment());
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class TestHostEnvironment : IHostEnvironment
|
||||||
|
{
|
||||||
|
public string EnvironmentName { get; set; } = Environments.Development;
|
||||||
|
public string ApplicationName { get; set; } = "SlipItIn.Server.Tests";
|
||||||
|
public string ContentRootPath { get; set; } = AppContext.BaseDirectory;
|
||||||
|
public Microsoft.Extensions.FileProviders.IFileProvider ContentRootFileProvider { get; set; } = null!;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- Register ----------
|
// ---------- Register ----------
|
||||||
|
|||||||
@@ -117,7 +117,10 @@ public class GameServiceTests
|
|||||||
{
|
{
|
||||||
using var db = _factory.CreateDbContext();
|
using var db = _factory.CreateDbContext();
|
||||||
var host = TestDbHelper.SeedUser(db);
|
var host = TestDbHelper.SeedUser(db);
|
||||||
|
var other = TestDbHelper.SeedUser(db, "other", "other@t.local");
|
||||||
var (game, _) = TestDbHelper.SeedGame(db, host);
|
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);
|
var result = await _sut.StartGameAsync(game.Id);
|
||||||
|
|
||||||
@@ -216,7 +219,7 @@ public class GameServiceTests
|
|||||||
{
|
{
|
||||||
using var db = _factory.CreateDbContext();
|
using var db = _factory.CreateDbContext();
|
||||||
var host = TestDbHelper.SeedUser(db);
|
var host = TestDbHelper.SeedUser(db);
|
||||||
var (game, hostPlayer) = TestDbHelper.SeedGame(db, host);
|
var (game, hostPlayer) = TestDbHelper.SeedGame(db, host, GameStatus.InProgress);
|
||||||
var round = new GameRound { GameId = game.Id, RoundNumber = 1, Status = RoundStatus.Active };
|
var round = new GameRound { GameId = game.Id, RoundNumber = 1, Status = RoundStatus.Active };
|
||||||
db.GameRounds.Add(round);
|
db.GameRounds.Add(round);
|
||||||
var phrase = TestDbHelper.SeedPhrases(db, 1).Single();
|
var phrase = TestDbHelper.SeedPhrases(db, 1).Single();
|
||||||
@@ -234,7 +237,11 @@ public class GameServiceTests
|
|||||||
[Fact]
|
[Fact]
|
||||||
public async Task SubmitSlipAsync_Throws_WhenCardNotFound()
|
public async Task SubmitSlipAsync_Throws_WhenCardNotFound()
|
||||||
{
|
{
|
||||||
await Assert.ThrowsAsync<InvalidOperationException>(() => _sut.SubmitSlipAsync(1, 1, 999));
|
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]
|
[Fact]
|
||||||
@@ -243,7 +250,7 @@ public class GameServiceTests
|
|||||||
using var db = _factory.CreateDbContext();
|
using var db = _factory.CreateDbContext();
|
||||||
var host = TestDbHelper.SeedUser(db, "host", "host@t.local");
|
var host = TestDbHelper.SeedUser(db, "host", "host@t.local");
|
||||||
var other = TestDbHelper.SeedUser(db, "other", "other@t.local");
|
var other = TestDbHelper.SeedUser(db, "other", "other@t.local");
|
||||||
var (game, hostPlayer) = TestDbHelper.SeedGame(db, host);
|
var (game, hostPlayer) = TestDbHelper.SeedGame(db, host, GameStatus.InProgress);
|
||||||
var otherPlayer = new Player { UserId = other.Id, GameId = game.Id };
|
var otherPlayer = new Player { UserId = other.Id, GameId = game.Id };
|
||||||
db.Players.Add(otherPlayer);
|
db.Players.Add(otherPlayer);
|
||||||
var round = new GameRound { GameId = game.Id, RoundNumber = 1, Status = RoundStatus.Active };
|
var round = new GameRound { GameId = game.Id, RoundNumber = 1, Status = RoundStatus.Active };
|
||||||
@@ -271,7 +278,7 @@ public class GameServiceTests
|
|||||||
var round = new GameRound { GameId = game.Id, RoundNumber = 1, Status = RoundStatus.Active };
|
var round = new GameRound { GameId = game.Id, RoundNumber = 1, Status = RoundStatus.Active };
|
||||||
db.GameRounds.Add(round);
|
db.GameRounds.Add(round);
|
||||||
var phrase = TestDbHelper.SeedPhrases(db, 1).Single();
|
var phrase = TestDbHelper.SeedPhrases(db, 1).Single();
|
||||||
var card = new PlayerCard { PlayerId = otherPlayer.Id, PhraseId = phrase.Id, GameRoundId = round.Id };
|
var card = new PlayerCard { PlayerId = otherPlayer.Id, PhraseId = phrase.Id, GameRoundId = round.Id, IsUsed = true };
|
||||||
db.PlayerCards.Add(card);
|
db.PlayerCards.Add(card);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|
||||||
@@ -306,10 +313,55 @@ public class GameServiceTests
|
|||||||
await Assert.ThrowsAsync<InvalidOperationException>(() => _sut.CreateChallengeAsync(game.Id, hostPlayer.Id, card.Id));
|
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 ----------
|
// ---------- ResolveChallengeAsync ----------
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ResolveChallengeAsync_Approved_KeepsCardWithTargetPlayer()
|
public async Task ResolveChallengeAsync_Approved_TransfersCardToChallenger()
|
||||||
{
|
{
|
||||||
using var db = _factory.CreateDbContext();
|
using var db = _factory.CreateDbContext();
|
||||||
var host = TestDbHelper.SeedUser(db, "host", "host@t.local");
|
var host = TestDbHelper.SeedUser(db, "host", "host@t.local");
|
||||||
@@ -320,7 +372,7 @@ public class GameServiceTests
|
|||||||
var round = new GameRound { GameId = game.Id, RoundNumber = 1, Status = RoundStatus.Active };
|
var round = new GameRound { GameId = game.Id, RoundNumber = 1, Status = RoundStatus.Active };
|
||||||
db.GameRounds.Add(round);
|
db.GameRounds.Add(round);
|
||||||
var phrase = TestDbHelper.SeedPhrases(db, 1).Single();
|
var phrase = TestDbHelper.SeedPhrases(db, 1).Single();
|
||||||
var card = new PlayerCard { PlayerId = otherPlayer.Id, PhraseId = phrase.Id, GameRoundId = round.Id };
|
var card = new PlayerCard { PlayerId = otherPlayer.Id, PhraseId = phrase.Id, GameRoundId = round.Id, IsUsed = true };
|
||||||
db.PlayerCards.Add(card);
|
db.PlayerCards.Add(card);
|
||||||
var challenge = new SlipChallenge
|
var challenge = new SlipChallenge
|
||||||
{
|
{
|
||||||
@@ -337,13 +389,13 @@ public class GameServiceTests
|
|||||||
|
|
||||||
Assert.Equal(ChallengeStatus.Approved, result.Status);
|
Assert.Equal(ChallengeStatus.Approved, result.Status);
|
||||||
Assert.NotNull(result.ResolvedAt);
|
Assert.NotNull(result.ResolvedAt);
|
||||||
// Karte bleibt beim Beschuldigten
|
// Berechtigte Beschuldigung: Karte geht an den Beschuldiger
|
||||||
using var verify = _factory.CreateDbContext();
|
using var verify = _factory.CreateDbContext();
|
||||||
Assert.Equal(otherPlayer.Id, verify.PlayerCards.Single(c => c.Id == card.Id).PlayerId);
|
Assert.Equal(hostPlayer.Id, verify.PlayerCards.Single(c => c.Id == card.Id).PlayerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task ResolveChallengeAsync_Rejected_TransfersCardToChallenger()
|
public async Task ResolveChallengeAsync_Rejected_KeepsCardWithTargetPlayer()
|
||||||
{
|
{
|
||||||
using var db = _factory.CreateDbContext();
|
using var db = _factory.CreateDbContext();
|
||||||
var host = TestDbHelper.SeedUser(db, "host", "host@t.local");
|
var host = TestDbHelper.SeedUser(db, "host", "host@t.local");
|
||||||
@@ -354,7 +406,7 @@ public class GameServiceTests
|
|||||||
var round = new GameRound { GameId = game.Id, RoundNumber = 1, Status = RoundStatus.Active };
|
var round = new GameRound { GameId = game.Id, RoundNumber = 1, Status = RoundStatus.Active };
|
||||||
db.GameRounds.Add(round);
|
db.GameRounds.Add(round);
|
||||||
var phrase = TestDbHelper.SeedPhrases(db, 1).Single();
|
var phrase = TestDbHelper.SeedPhrases(db, 1).Single();
|
||||||
var card = new PlayerCard { PlayerId = otherPlayer.Id, PhraseId = phrase.Id, GameRoundId = round.Id };
|
var card = new PlayerCard { PlayerId = otherPlayer.Id, PhraseId = phrase.Id, GameRoundId = round.Id, IsUsed = true };
|
||||||
db.PlayerCards.Add(card);
|
db.PlayerCards.Add(card);
|
||||||
var challenge = new SlipChallenge
|
var challenge = new SlipChallenge
|
||||||
{
|
{
|
||||||
@@ -371,9 +423,9 @@ public class GameServiceTests
|
|||||||
|
|
||||||
Assert.Equal(ChallengeStatus.Rejected, result.Status);
|
Assert.Equal(ChallengeStatus.Rejected, result.Status);
|
||||||
Assert.NotNull(result.ResolvedAt);
|
Assert.NotNull(result.ResolvedAt);
|
||||||
// Karte geht an den Beschuldiger
|
// Falsche Beschuldigung: Karte bleibt beim Beschuldigten
|
||||||
using var verify = _factory.CreateDbContext();
|
using var verify = _factory.CreateDbContext();
|
||||||
Assert.Equal(hostPlayer.Id, verify.PlayerCards.Single(c => c.Id == card.Id).PlayerId);
|
Assert.Equal(otherPlayer.Id, verify.PlayerCards.Single(c => c.Id == card.Id).PlayerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -458,7 +510,7 @@ public class GameServiceTests
|
|||||||
using var db = _factory.CreateDbContext();
|
using var db = _factory.CreateDbContext();
|
||||||
var host = TestDbHelper.SeedUser(db);
|
var host = TestDbHelper.SeedUser(db);
|
||||||
var (game, hostPlayer) = TestDbHelper.SeedGame(db, host);
|
var (game, hostPlayer) = TestDbHelper.SeedGame(db, host);
|
||||||
var round = new GameRound { GameId = game.Id, RoundNumber = 1 };
|
var round = new GameRound { GameId = game.Id, RoundNumber = 1, Status = RoundStatus.Active };
|
||||||
db.GameRounds.Add(round);
|
db.GameRounds.Add(round);
|
||||||
var phrases = TestDbHelper.SeedPhrases(db, 2);
|
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[0].Id, GameRoundId = round.Id });
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="10.0.11" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="10.0.10" />
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.9.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.9.0" />
|
||||||
<PackageReference Include="Moq" Version="4.20.72" />
|
<PackageReference Include="Moq" Version="4.20.72" />
|
||||||
<PackageReference Include="xunit" Version="2.9.3" />
|
<PackageReference Include="xunit" Version="2.9.3" />
|
||||||
|
|||||||
@@ -243,11 +243,6 @@ public class GameHub : Hub
|
|||||||
{
|
{
|
||||||
await Clients.Caller.SendAsync("Error", new { Message = ex.Message });
|
await Clients.Caller.SendAsync("Error", new { Message = ex.Message });
|
||||||
}
|
}
|
||||||
catch (InvalidOperationException ex)
|
|
||||||
{
|
|
||||||
// Geprüfte Fachfehler (z. B. "Round is not active") dürfen an den Client.
|
|
||||||
await Clients.Caller.SendAsync("Error", new { Message = ex.Message });
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error submitting slip");
|
_logger.LogError(ex, "Error submitting slip");
|
||||||
@@ -294,11 +289,6 @@ public class GameHub : Hub
|
|||||||
{
|
{
|
||||||
await Clients.Caller.SendAsync("Error", new { Message = ex.Message });
|
await Clients.Caller.SendAsync("Error", new { Message = ex.Message });
|
||||||
}
|
}
|
||||||
catch (InvalidOperationException ex)
|
|
||||||
{
|
|
||||||
// Geprüfte Fachfehler (z. B. "Card does not belong to the active round") dürfen an den Client.
|
|
||||||
await Clients.Caller.SendAsync("Error", new { Message = ex.Message });
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error challenging slip");
|
_logger.LogError(ex, "Error challenging slip");
|
||||||
|
|||||||
@@ -176,6 +176,11 @@ public class GameService : IGameService
|
|||||||
.Where(gr => gr.GameId == gameId && gr.Status == RoundStatus.Active)
|
.Where(gr => gr.GameId == gameId && gr.Status == RoundStatus.Active)
|
||||||
.FirstOrDefaultAsync() ?? throw new InvalidOperationException("No active round found");
|
.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
|
var targetCard = await context.PlayerCards
|
||||||
.FirstOrDefaultAsync(pc => pc.Id == targetCardId) ?? throw new InvalidOperationException("Target card not found");
|
.FirstOrDefaultAsync(pc => pc.Id == targetCardId) ?? throw new InvalidOperationException("Target card not found");
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user