From 494eee6eb7f80a3c187e111159f5b4187295626b Mon Sep 17 00:00:00 2001 From: Ben Stein <115497763+sei-bstein@users.noreply.github.com> Date: Mon, 6 Jan 2025 09:45:36 -0500 Subject: [PATCH] Test for #522 and fixes to pending names in Game Center -> Teams. (#578) --- .../Features/Teams/StartTeamSessionTests.cs | 41 +++++++++++++++++-- .../GetGameCenterTeams/GetGameCenterTeams.cs | 29 +++++++------ .../Certificates/CertificatesService.cs | 3 +- .../Features/Player/Services/PlayerService.cs | 18 +++++++- 4 files changed, 74 insertions(+), 17 deletions(-) diff --git a/src/Gameboard.Api.Tests.Integration/Tests/Features/Teams/StartTeamSessionTests.cs b/src/Gameboard.Api.Tests.Integration/Tests/Features/Teams/StartTeamSessionTests.cs index 8adc5b6a..e73c15b2 100644 --- a/src/Gameboard.Api.Tests.Integration/Tests/Features/Teams/StartTeamSessionTests.cs +++ b/src/Gameboard.Api.Tests.Integration/Tests/Features/Teams/StartTeamSessionTests.cs @@ -1,4 +1,5 @@ using Gameboard.Api.Common; +using Gameboard.Api.Data; using Gameboard.Api.Features.Teams; using Gameboard.Api.Structure; @@ -157,8 +158,6 @@ await httpClient result.SessionBegin.ShouldBeGreaterThan(DateTimeOffset.MinValue); } - // Users can team up, leave the team, join a different team, then start sessions on the original and the new team - // Admins can start sessions for non-admins // Non-admins can't start sessions for other teams [Theory, GbIntegrationAutoData] public async Task Team_WhenStartingOtherTeamSession_FailsValidation @@ -188,7 +187,7 @@ await _testContext.WithDataState(state => { p.Id = targetPlayerId; p.Role = PlayerRole.Manager; - p.TeamId = actingTeamId; + p.TeamId = fixture.Create(); p.User = state.Build(fixture); }) ]; @@ -203,4 +202,40 @@ await _testContext.WithDataState(state => // then the response should have a failure code response.IsSuccessStatusCode.ShouldBeFalse(); } + + // Users can team up, leave the team, join a different team, then start sessions on the original and the new team + // Admins can start sessions for non-admins + [Theory, GbIntegrationAutoData] + public async Task Team_WhenAdminStartingOtherTeamSession_Starts + ( + string targetPlayerId, + IFixture fixture + ) + { + // given two players registered for the same game + await _testContext.WithDataState(state => + { + state.Add(fixture, game => + { + game.Players = + [ + state.Build(fixture, p => + { + p.Id = targetPlayerId; + p.Role = PlayerRole.Manager; + p.TeamId = fixture.Create(); + p.User = state.Build(fixture); + }) + ]; + }); + }); + + // when the first player tries to start the second's session + var response = await _testContext + .CreateHttpClientWithAuthRole(UserRoleKey.Admin) + .PutAsync($"api/player/{targetPlayerId}/start", null); + + // then the response should have a failure code + response.IsSuccessStatusCode.ShouldBeTrue(); + } } diff --git a/src/Gameboard.Api/Features/Admin/Requests/GetGameCenterTeams/GetGameCenterTeams.cs b/src/Gameboard.Api/Features/Admin/Requests/GetGameCenterTeams/GetGameCenterTeams.cs index dc3d7444..d0a0bc5e 100644 --- a/src/Gameboard.Api/Features/Admin/Requests/GetGameCenterTeams/GetGameCenterTeams.cs +++ b/src/Gameboard.Api/Features/Admin/Requests/GetGameCenterTeams/GetGameCenterTeams.cs @@ -10,6 +10,7 @@ using Gameboard.Api.Services; using MediatR; using Microsoft.EntityFrameworkCore; +using ServiceStack; namespace Gameboard.Api.Features.Admin; @@ -19,6 +20,7 @@ internal class GetGameCenterTeamsHandler ( INowService nowService, IPagingService pagingService, + PlayerService playerService, IStore store, ITeamService teamService, TicketService ticketService @@ -26,6 +28,7 @@ TicketService ticketService { private readonly INowService _nowService = nowService; private readonly IPagingService _pagingService = pagingService; + private readonly PlayerService _playerService = playerService; private readonly IStore _store = store; private readonly ITeamService _teamService = teamService; private readonly TicketService _ticketService = ticketService; @@ -92,6 +95,18 @@ public async Task Handle(GetGameCenterTeamsQuery request } } + if (request.Args.HasPendingNames is not null) + { + if (request.Args.HasPendingNames.Value) + { + query = query.Where(_playerService.GetWhereHasPendingNamePredicate()); + } + else + { + query = query.Where(_playerService.GetWhereDoesntHavePendingNamePredicate()); + } + } + var matchingTeams = await query .Select(p => new { @@ -124,15 +139,7 @@ public async Task Handle(GetGameCenterTeamsQuery request p.WhenCreated }) .GroupBy(p => p.TeamId) - // have to do pending names filter here because we need all players - .ToDictionaryAsync(gr => gr.Key, gr => gr.ToArray(), cancellationToken); - - if (request.Args.HasPendingNames is not null) - { - matchingTeams = matchingTeams - .Where(kv => request.Args.HasPendingNames.Value == kv.Value.Any(p => p.PendingName != p.Name && (p.NameStatus == string.Empty || p.NameStatus == null || p.NameStatus == AppConstants.NameStatusPending))) - .ToDictionary(kv => kv.Key, kv => kv.Value); - } + .ToDictionaryAsync(gr => gr.Key, gr => gr.ToArray(), cancellationToken); ; var matchingTeamIds = matchingTeams.Keys.ToArray(); var captains = matchingTeams.ToDictionary(kv => kv.Key, kv => @@ -257,9 +264,7 @@ public async Task Handle(GetGameCenterTeamsQuery request // last, we check to see if the game has any pending approvals, as we need them for the screen var pendingNameCount = await _store .WithNoTracking() - .Where(p => p.GameId == request.GameId) - .Where(p => p.Name != null && p.Name != string.Empty && p.Name != p.ApprovedName) - .Where(p => p.NameStatus == AppConstants.NameStatusPending || p.NameStatus == null || p.NameStatus == string.Empty) + .Where(_playerService.GetWhereHasPendingNamePredicate()) .CountAsync(cancellationToken); return new GameCenterTeamsResults diff --git a/src/Gameboard.Api/Features/Certificates/CertificatesService.cs b/src/Gameboard.Api/Features/Certificates/CertificatesService.cs index 88e0dcb2..4eb1cbdb 100644 --- a/src/Gameboard.Api/Features/Certificates/CertificatesService.cs +++ b/src/Gameboard.Api/Features/Certificates/CertificatesService.cs @@ -246,7 +246,8 @@ public IQueryable GetTemplatesQuery() CreatedByUser = new SimpleEntity { Id = t.CreatedByUserId, Name = t.CreatedByUser.ApprovedName }, UseAsPracticeTemplateForGames = t.UseAsPracticeTemplateForGames.Select(g => new SimpleEntity { Id = g.Id, Name = g.Name }), UseAsTemplateForGames = t.UseAsTemplateForGames.Select(g => new SimpleEntity { Id = g.Id, Name = g.Name }) - }); + }) + .OrderBy(t => t.Name); private string BuildCertificateHtml(string templateHtml, CertificateHtmlContext htmlContext) { diff --git a/src/Gameboard.Api/Features/Player/Services/PlayerService.cs b/src/Gameboard.Api/Features/Player/Services/PlayerService.cs index eb48a1a6..8340761b 100644 --- a/src/Gameboard.Api/Features/Player/Services/PlayerService.cs +++ b/src/Gameboard.Api/Features/Player/Services/PlayerService.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Linq.Expressions; using System.Threading; using System.Threading.Tasks; using AutoMapper; @@ -133,6 +134,16 @@ await _store ); } + public Expression> GetWhereHasPendingNamePredicate() + { + return p => p.Name != p.ApprovedName && p.Name != null && p.Name != string.Empty && (p.NameStatus == null || p.NameStatus == string.Empty || p.NameStatus == AppConstants.NameStatusPending); + } + + public Expression> GetWhereDoesntHavePendingNamePredicate() + { + return p => !(p.Name != p.ApprovedName && p.Name != null && p.Name != string.Empty && (p.NameStatus == null || p.NameStatus == string.Empty || p.NameStatus == AppConstants.NameStatusPending)); + } + /// /// Maps a PlayerId to its UserId /// @@ -182,7 +193,9 @@ public async Task Update(ChangedPlayer model, User actor) // if manipulation of the names has caused Name to equal ApprovedName, clear any pending status if (player.Name == player.ApprovedName && player.NameStatus == AppConstants.NameStatusPending) + { player.NameStatus = string.Empty; + } if (prev.Name != player.Name) { @@ -308,7 +321,10 @@ public async Task List(PlayerDataFilter model, bool sudo = false) q = q.Where(p => !p.Advanced); if (model.WantsPending) - q = q.Where(p => p.Name != null && p.Name != string.Empty && p.Name != p.ApprovedName); + { + var pendingNamePredicate = GetWhereHasPendingNamePredicate(); + q = q.Where(pendingNamePredicate); + } if (model.WantsDisallowed) q = q.Where(u => !string.IsNullOrEmpty(u.NameStatus));