Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
f232896
feat: add champion tracking to tournaments
Dejmenek Sep 16, 2026
afd6b62
feat: add BracketCompletionService for champion sync
Dejmenek Sep 16, 2026
9d15269
feat: return Match objects from correction methods
Dejmenek Sep 16, 2026
b82a3b8
feat: expand tournament validation conditions
Dejmenek Sep 16, 2026
a7893da
feat: add support for fetching user-won matches
Dejmenek Sep 16, 2026
620e964
refactor: optimize tournament winner data retrieval
Dejmenek Sep 16, 2026
03b6493
feat: add validations for tournament "Completed" status
Dejmenek Sep 16, 2026
e2cb667
feat: add GetChampion resolver to TournamentResolvers
Dejmenek Sep 16, 2026
1323ade
feat: update seeded tournaments and add new fixture
Dejmenek Sep 16, 2026
b811857
test: add validation tests for tournament status changes
Dejmenek Sep 16, 2026
971975d
test: add and update bracket mutation tests
Dejmenek Sep 16, 2026
6e9940e
test: add tests for GetTournamentById champion details query behavior
Dejmenek Sep 16, 2026
2d3a023
test: update TournamentQueryTests for new total count
Dejmenek Sep 16, 2026
1cda48d
test: add tests for match win and replay scenarios in UserQueryTests
Dejmenek Sep 16, 2026
0f148a2
test: add test to ensure completed tournaments are untouched when aut…
Dejmenek Sep 16, 2026
6b0e806
test: add champion and won matches support to response models and que…
Dejmenek Sep 16, 2026
0c6db2e
test: add unit test for ValidateTournamentIsClosed
Dejmenek Sep 16, 2026
7f482b8
test: add validation tests for tournament operations
Dejmenek Sep 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using TournamentAPI.Tournaments;

namespace TournamentAPI.IntegrationTests.GraphQL.Tests.Brackets;

public class BracketMutationTests : BaseIntegrationTest
{
public BracketMutationTests(IntegrationTestWebAppFactory factory) : base(factory)
Expand Down Expand Up @@ -258,11 +259,11 @@ public async Task GenerateBracket_ReturnsBracketGenerationNotAllowedError_WhenTo
[Fact]
public async Task GenerateBracket_ReturnsBracketAlreadyExistsError_WhenBracketAlreadyExists()
{
// Arrange
var email = "alice@example.com";
// Arrange: tournament 17 (bracket 7) is Closed and already has a bracket, but its final
// hasn't been played yet, so it stays Closed rather than Completed.
var email = "david@example.com";
var password = "Password123!";
var tournamentCreateBracketId = 3;
var bracketId = 1;
var tournamentCreateBracketId = 17;
using var client = CreateClient();

var tokenResponse = await client.ExecuteMutationAsync<LoginResponse>(
Expand Down Expand Up @@ -811,11 +812,11 @@ public async Task UpdateRound_ReturnsRoundUpdateNotAllowedError_WhenTournamentIs
[Fact]
public async Task UpdateRound_ReturnsNoMatchesInRoundError_WhenNoMatchesInRound()
{
// Arrange
var email = "alice@example.com";
// Arrange: tournament 17 (bracket 7) only has rounds 1 and 2 generated, so round 3 has no matches.
var email = "david@example.com";
var password = "Password123!";
var bracketId = 1;
var roundNumber = 4;
var bracketId = 7;
var roundNumber = 3;
using var client = CreateClient();

var tokenResponse = await client.ExecuteMutationAsync<LoginResponse>(
Expand Down Expand Up @@ -1037,10 +1038,10 @@ public async Task UpdateRound_CorrectlyAdvancesWinners_WhenOddNumberOfWinners()
[Fact]
public async Task UpdateRound_ReturnsNextRoundAlreadyExistsError_WhenNextRoundAlreadyExists()
{
// Arrange
var email = "alice@example.com";
// Arrange: tournament 17 (bracket 7) already has round 2 generated from round 1's winners.
var email = "david@example.com";
var password = "Password123!";
var bracketId = 1;
var bracketId = 7;
using var client = CreateClient();

var tokenResponse = await client.ExecuteMutationAsync<LoginResponse>(
Expand Down Expand Up @@ -1092,17 +1093,17 @@ public async Task UpdateRound_ReturnsNextRoundAlreadyExistsError_WhenNextRoundAl
.Where(m => m.BracketId == bracketId && m.Round == 2)
.ToListAsync();

Assert.NotEmpty(matchesForNextRound);
Assert.Equal(2, matchesForNextRound.Count);
Assert.Single(matchesForNextRound);
}

[Fact]
public async Task UpdateRound_ReturnsBracketAlreadyHasWinnerError_WhenBracketAlreadyHasWinner()
public async Task UpdateRound_ReturnsRoundUpdateNotAllowedError_WhenTournamentIsAlreadyCompleted()
{
// Arrange
var email = "alice@example.com";
var password = "Password123!";
var bracketId = 1;
var tournamentId = 3;
using var client = CreateClient();

var tokenResponse = await client.ExecuteMutationAsync<LoginResponse>(
Expand Down Expand Up @@ -1144,10 +1145,10 @@ public async Task UpdateRound_ReturnsBracketAlreadyHasWinnerError_WhenBracketAlr
Assert.True(error.Extensions.ContainsKey("code"));
Assert.NotNull(error.Message);

var expectedError = BracketErrors.BracketAlreadyHasWinner(bracketId);
var expectedError = BracketErrors.RoundUpdateNotAllowed(tournamentId);
Assert.Equal(expectedError.Code, error.Extensions["code"]?.ToString());
Assert.Equal(expectedError.Message, error.Message);
Assert.Equal(expectedError.Extensions!["BracketId"]?.ToString(), error.Extensions["BracketId"]?.ToString());
Assert.Equal(expectedError.Extensions!["TournamentId"]?.ToString(), error.Extensions["TournamentId"]?.ToString());

var matchesForNextRound = await DbContext.Matches
.AsNoTracking()
Expand All @@ -1156,4 +1157,112 @@ public async Task UpdateRound_ReturnsBracketAlreadyHasWinnerError_WhenBracketAlr

Assert.Empty(matchesForNextRound);
}

[Fact]
public async Task Play_MarksTournamentCompletedAndSetsChampion_WhenFinalMatchIsPlayed()
{
// Arrange: tournament 8 ("Event") has exactly 2 participants and no bracket yet, so generating
// it produces a single round-1 match, which is by definition the final.
var email = "bob@example.com";
var password = "Password123!";
var tournamentId = 8;
using var client = CreateClient();

var tokenResponse = await client.ExecuteMutationAsync<LoginResponse>(
Shared.MutationExamples.Mutations.Users.LoginUser,
new
{
input = new
{
email = email,
password = password
}
});
client.SetAuthToken(tokenResponse.Data.LoginUser.String);

await client.ExecuteMutationAsync<GenerateBracketResponse>(
Shared.MutationExamples.Mutations.Bracket.GenerateBracket,
new { input = new { tournamentId } });

var finalMatch = await DbContext.Matches.AsNoTracking().SingleAsync(m => m.Bracket.TournamentId == tournamentId);

var variables = new
{
input = new
{
matchId = finalMatch.Id,
winnerId = finalMatch.Player1Id,
player1Score = 3,
player2Score = 1
}
};

// Act
var response = await client.ExecuteMutationAsync<PlayMatchResponse>(
Shared.MutationExamples.Mutations.Match.Play,
variables);

// Assert
Assert.False(response.HasErrors);

var tournamentAfter = await DbContext.Tournaments.AsNoTracking().SingleAsync(t => t.Id == tournamentId);

Assert.Equal(TournamentStatus.Completed, tournamentAfter.Status);
Assert.Equal(finalMatch.Player1Id, tournamentAfter.ChampionId);
}

[Fact]
public async Task Play_DoesNotChangeTournamentCompletionState_WhenMatchIsNotTheFinal()
{
// Arrange: tournament 10 ("Trio Tournament") has 3 participants, so generating it produces a
// round of 2 matches (one real, one auto-resolved bye) - neither one is the final on its own.
var email = "bob@example.com";
var password = "Password123!";
var tournamentId = 10;
using var client = CreateClient();

var tokenResponse = await client.ExecuteMutationAsync<LoginResponse>(
Shared.MutationExamples.Mutations.Users.LoginUser,
new
{
input = new
{
email = email,
password = password
}
});
client.SetAuthToken(tokenResponse.Data.LoginUser.String);

await client.ExecuteMutationAsync<GenerateBracketResponse>(
Shared.MutationExamples.Mutations.Bracket.GenerateBracket,
new { input = new { tournamentId } });

var realMatch = await DbContext.Matches
.AsNoTracking()
.SingleAsync(m => m.Bracket.TournamentId == tournamentId && m.Status == MatchStatus.Scheduled);

var variables = new
{
input = new
{
matchId = realMatch.Id,
winnerId = realMatch.Player1Id,
player1Score = 3,
player2Score = 1
}
};

// Act
var response = await client.ExecuteMutationAsync<PlayMatchResponse>(
Shared.MutationExamples.Mutations.Match.Play,
variables);

// Assert
Assert.False(response.HasErrors);

var tournamentAfter = await DbContext.Tournaments.AsNoTracking().SingleAsync(t => t.Id == tournamentId);

Assert.Equal(TournamentStatus.Closed, tournamentAfter.Status);
Assert.Null(tournamentAfter.ChampionId);
}
}
Loading
Loading