Skip to content

Commit

Permalink
Create an 'edit players for team' page #626
Browse files Browse the repository at this point in the history
  • Loading branch information
sussexrick committed Dec 8, 2022
1 parent b56a881 commit cc4d06b
Show file tree
Hide file tree
Showing 22 changed files with 466 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -251,11 +251,19 @@ public async Task Read_player_identities_returns_player()

foreach (var identity in _databaseFixture.TestData.PlayerIdentities)
{
var result = results.SingleOrDefault(x => x.PlayerIdentityId == identity.PlayerIdentityId);
Assert.NotNull(result);
var identityFromResults = results.SingleOrDefault(x => x.PlayerIdentityId == identity.PlayerIdentityId);
Assert.NotNull(identityFromResults?.Player);

Assert.Equal(identity.Player!.PlayerId, identityFromResults!.Player!.PlayerId);
Assert.Equal(identity.Player.PlayerRoute, identityFromResults.Player.PlayerRoute);

Assert.Equal(identity.Player.PlayerId, result!.Player.PlayerId);
Assert.Equal(identity.Player.PlayerRoute, result.Player.PlayerRoute);
var allIdentitiesMatchingFilterForThisPlayer = results.Where(x => x.Player?.PlayerId == identity.Player.PlayerId).ToList();
Assert.Equal(allIdentitiesMatchingFilterForThisPlayer.Count, identityFromResults.Player.PlayerIdentities.Count);
foreach (var identityForPlayer in allIdentitiesMatchingFilterForThisPlayer)
{
var resultIdentityForPlayer = identityFromResults.Player.PlayerIdentities.SingleOrDefault(x => x.PlayerIdentityId == identityForPlayer.PlayerIdentityId);
Assert.NotNull(resultIdentityForPlayer);
}
}
}

Expand Down
10 changes: 9 additions & 1 deletion Stoolball.Data.SqlServer/SqlServerPlayerDataSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ public async Task<List<PlayerIdentity>> ReadPlayerIdentities(PlayerFilter? filte
var (where, parameters) = BuildWhereClause(filter);
sql = sql.Replace("<<WHERE>>", $"WHERE 1=1 {where}");

return (await connection.QueryAsync<PlayerIdentity, Player, Team, PlayerIdentity>(sql,
var identities = (await connection.QueryAsync<PlayerIdentity, Player, Team, PlayerIdentity>(sql,
(identity, player, team) =>
{
identity.Team = team;
Expand All @@ -110,6 +110,14 @@ public async Task<List<PlayerIdentity>> ReadPlayerIdentities(PlayerFilter? filte
},
new DynamicParameters(parameters),
splitOn: "PlayerId, TeamId").ConfigureAwait(false)).ToList();

// Populate the PlayerIdentities collections of the players with the data that we have
foreach (var identity in identities)
{
identity.Player!.PlayerIdentities = identities.Where(x => x.Player?.PlayerId == identity.Player.PlayerId).ToList();
}

return identities;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public class StoolballRouteParserTests
[InlineData("https://example.org/teams/example123", StoolballRouteType.Team)]
[InlineData("https://example.org/teams/example123/edit", StoolballRouteType.TeamActions)]
[InlineData("https://example.org/teams/example123/edit/team", StoolballRouteType.EditTeam)]
[InlineData("https://example.org/teams/example123/edit/players", StoolballRouteType.EditPlayersForTeam)]
[InlineData("https://example.org/teams/example123/delete", StoolballRouteType.DeleteTeam)]
[InlineData("https://example.org/teams/example123/players", StoolballRouteType.PlayersForTeam)]
[InlineData("https://example.org/teams/example123/matches", StoolballRouteType.MatchesForTeam)]
Expand Down
156 changes: 156 additions & 0 deletions Stoolball.Web.UnitTests/Teams/EditPlayersForTeamControllerTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Moq;
using Stoolball.Clubs;
using Stoolball.Data.Abstractions;
using Stoolball.Security;
using Stoolball.Statistics;
using Stoolball.Teams;
using Stoolball.Web.Teams;
using Stoolball.Web.Teams.Models;
using Xunit;

namespace Stoolball.Web.UnitTests.Teams
{
public class EditPlayersForTeamControllerTests : UmbracoBaseTest
{
private readonly Mock<ITeamDataSource> _teamDataSource = new();
private readonly Mock<IPlayerDataSource> _playerDataSource = new();
private readonly Mock<IAuthorizationPolicy<Team>> _authorizationPolicy = new();

private EditPlayersForTeamController CreateController()
{
return new EditPlayersForTeamController(
Mock.Of<ILogger<EditPlayersForTeamController>>(),
CompositeViewEngine.Object,
UmbracoContextAccessor.Object,
_teamDataSource.Object,
_authorizationPolicy.Object,
_playerDataSource.Object)
{
ControllerContext = ControllerContext
};
}

private static Team CreateTeam()
{
return new Team
{
TeamId = Guid.NewGuid(),
TeamName = "Example team",
TeamRoute = "/teams/example-team"
};
}

private void SetupMocks(Team team)
{
var players = new List<PlayerIdentity> {
new PlayerIdentity
{
PlayerIdentityId = Guid.NewGuid(),
PlayerIdentityName = "John Smith",
Player = new Player
{
PlayerId = Guid.NewGuid()
}
}
};
_teamDataSource.Setup(x => x.ReadTeamByRoute(Request.Object.Path, true)).ReturnsAsync(team);
_playerDataSource.Setup(x => x.ReadPlayerIdentities(It.Is<PlayerFilter>(x => x.TeamIds.Count == 1 && x.TeamIds.Contains(team.TeamId!.Value)))).Returns(Task.FromResult(players));
_authorizationPolicy.Setup(x => x.IsAuthorized(team)).Returns(Task.FromResult(new Dictionary<AuthorizedAction, bool> { { AuthorizedAction.EditTeam, true } }));
}

[Fact]
public async Task Route_not_matching_team_returns_404()
{
_teamDataSource.Setup(x => x.ReadTeamByRoute(Request.Object.Path, true)).Returns(Task.FromResult<Team?>(null));

using (var controller = CreateController())
{
var result = await controller.Index();

Assert.IsType<NotFoundResult>(result);
}
}

[Fact]
public async Task Route_matching_team_sets_authorization()
{
var team = CreateTeam();
SetupMocks(team);

using (var controller = CreateController())
{
var result = await controller.Index();

var model = (TeamViewModel)((ViewResult)result).Model;

Assert.True(model.Authorization.CurrentMemberIsAuthorized[AuthorizedAction.EditTeam]);
}
}

[Fact]
public async Task Route_matching_team_returns_player_identities()
{
var team = CreateTeam();
SetupMocks(team);

using (var controller = CreateController())
{
var result = await controller.Index();

var model = (TeamViewModel)((ViewResult)result).Model;

Assert.Single(model.PlayerIdentities);
}
}

[Fact]
public async Task Route_matching_team_sets_page_title()
{
var team = CreateTeam();
SetupMocks(team);

using (var controller = CreateController())
{
var result = await controller.Index();

var model = (TeamViewModel)((ViewResult)result).Model;

Assert.Equal($"Edit players for {team.TeamName} stoolball team", model.Metadata.PageTitle);
}
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task Route_matching_team_sets_breadcrumbs_including_club(bool hasClub)
{
var team = CreateTeam();

if (hasClub)
{
team.Club = new Club
{
ClubId = Guid.NewGuid(),
ClubName = "Example club",
ClubRoute = "/clubs/example-club"
};
}
SetupMocks(team);

using (var controller = CreateController())
{
var result = await controller.Index();

var model = (TeamViewModel)((ViewResult)result).Model;

Assert.Equal(hasClub ? 5 : 4, model.Breadcrumbs.Count);
Assert.Equal(team.TeamName, model.Breadcrumbs[^1].Name);
}
}
}
}
99 changes: 93 additions & 6 deletions Stoolball.Web.UnitTests/Teams/PlayersForTeamControllerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Moq;
using Stoolball.Clubs;
using Stoolball.Data.Abstractions;
using Stoolball.Security;
using Stoolball.Statistics;
Expand All @@ -18,6 +19,7 @@ public class PlayersForTeamControllerTests : UmbracoBaseTest
{
private readonly Mock<ITeamDataSource> _teamDataSource = new();
private readonly Mock<IPlayerDataSource> _playerDataSource = new();
private readonly Mock<IAuthorizationPolicy<Team>> _authorizationPolicy = new();

private PlayersForTeamController CreateController()
{
Expand All @@ -26,17 +28,35 @@ private PlayersForTeamController CreateController()
CompositeViewEngine.Object,
UmbracoContextAccessor.Object,
_teamDataSource.Object,
Mock.Of<IAuthorizationPolicy<Team>>(),
_authorizationPolicy.Object,
_playerDataSource.Object)
{
ControllerContext = ControllerContext
};
}

private void SetupMocks(Team team)
{
var players = new List<PlayerIdentity> {
new PlayerIdentity
{
PlayerIdentityId = Guid.NewGuid(),
PlayerIdentityName = "John Smith",
Player = new Player
{
PlayerId = Guid.NewGuid()
}
}
};
_teamDataSource.Setup(x => x.ReadTeamByRoute(Request.Object.Path, true)).ReturnsAsync(team);
_playerDataSource.Setup(x => x.ReadPlayerIdentities(It.Is<PlayerFilter>(x => x.TeamIds.Count == 1 && x.TeamIds.Contains(team.TeamId!.Value)))).Returns(Task.FromResult(players));
_authorizationPolicy.Setup(x => x.IsAuthorized(team)).Returns(Task.FromResult(new Dictionary<AuthorizedAction, bool> { { AuthorizedAction.EditTeam, true } }));
}

[Fact]
public async Task Route_not_matching_team_returns_404()
{
_teamDataSource.Setup(x => x.ReadTeamByRoute(It.IsAny<string>(), true)).Returns(Task.FromResult<Team?>(null));
_teamDataSource.Setup(x => x.ReadTeamByRoute(Request.Object.Path, true)).Returns(Task.FromResult<Team?>(null));

using (var controller = CreateController())
{
Expand All @@ -47,17 +67,84 @@ public async Task Route_not_matching_team_returns_404()
}

[Fact]
public async Task Route_matching_team_returns_TeamViewModel()
public async Task Route_matching_team_sets_authorization()
{
_teamDataSource.Setup(x => x.ReadTeamByRoute(It.IsAny<string>(), true)).ReturnsAsync(new Team { TeamId = Guid.NewGuid() });
var team = new Team { TeamId = Guid.NewGuid() };
SetupMocks(team);

_playerDataSource.Setup(x => x.ReadPlayerIdentities(It.IsAny<PlayerFilter>())).Returns(Task.FromResult(new List<PlayerIdentity>()));
using (var controller = CreateController())
{
var result = await controller.Index();

var model = (TeamViewModel)((ViewResult)result).Model;

Assert.True(model.Authorization.CurrentMemberIsAuthorized[AuthorizedAction.EditTeam]);
}
}

[Fact]
public async Task Route_matching_team_returns_players()
{
var team = new Team { TeamId = Guid.NewGuid() };
SetupMocks(team);

using (var controller = CreateController())
{
var result = await controller.Index();

Assert.IsType<TeamViewModel>(((ViewResult)result).Model);
var model = (TeamViewModel)((ViewResult)result).Model;

Assert.Single(model.Players);
}
}

[Fact]
public async Task Route_matching_team_sets_page_title_and_description()
{
var team = new Team { TeamId = Guid.NewGuid(), TeamName = "Example team" };
SetupMocks(team);

using (var controller = CreateController())
{
var result = await controller.Index();

var model = (TeamViewModel)((ViewResult)result).Model;

Assert.Equal($"Players for {team.TeamName} stoolball team", model.Metadata.PageTitle);
Assert.Equal(team.Description(), model.Metadata.Description);
}
}

[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task Route_matching_team_sets_breadcrumbs_including_club(bool hasClub)
{
var team = new Team
{
TeamId = Guid.NewGuid(),
TeamName = "Example team",
TeamRoute = "/teams/example-team"
};

if (hasClub)
{
team.Club = new Club
{
ClubId = Guid.NewGuid(),
ClubName = "Example club",
ClubRoute = "/clubs/example-club"
};
}
SetupMocks(team);

using (var controller = CreateController())
{
var result = await controller.Index();

var model = (TeamViewModel)((ViewResult)result).Model;

Assert.Equal(hasClub ? 4 : 3, model.Breadcrumbs.Count);
}
}
}
Expand Down
1 change: 1 addition & 0 deletions Stoolball.Web/Routing/StoolballRouteParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ public class StoolballRouteParser : IStoolballRouteParser
{ $"teams{SLASH}{ANY_VALID_ROUTE}{SLASH}matches{SLASH}add{SLASH}tournament{OPTIONAL_SLASH}", StoolballRouteType.CreateTournament },
{ $"teams{SLASH}{ANY_VALID_ROUTE}{SLASH}edit{OPTIONAL_SLASH}", StoolballRouteType.TeamActions },
{ $"teams{SLASH}{ANY_VALID_ROUTE}{SLASH}edit{SLASH}team{OPTIONAL_SLASH}", StoolballRouteType.EditTeam },
{ $"teams{SLASH}{ANY_VALID_ROUTE}{SLASH}edit{SLASH}players{OPTIONAL_SLASH}", StoolballRouteType.EditPlayersForTeam },
{ $"teams{SLASH}{ANY_VALID_ROUTE}{SLASH}delete{OPTIONAL_SLASH}", StoolballRouteType.DeleteTeam },
{ $"locations{SLASH}{ANY_VALID_ROUTE}{SLASH}matches{OPTIONAL_SLASH}", StoolballRouteType.MatchesForMatchLocation },
{ $"locations{SLASH}{ANY_VALID_ROUTE}{SLASH}matches{SLASH}ics{OPTIONAL_SLASH}", StoolballRouteType.MatchesCalendar },
Expand Down
1 change: 1 addition & 0 deletions Stoolball.Web/Routing/StoolballRouteType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ public enum StoolballRouteType
LinkPlayerToMember,
LinkedPlayersForMember,
PlayersForTeam,
EditPlayersForTeam,
ClubStatistics,
TeamStatistics,
MatchLocationStatistics,
Expand Down
1 change: 1 addition & 0 deletions Stoolball.Web/Routing/StoolballRouteTypeMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ public class StoolballRouteTypeMapper : IStoolballRouteTypeMapper
{ StoolballRouteType.LinkPlayerToMember, typeof(LinkPlayerToMemberController) },
{ StoolballRouteType.LinkedPlayersForMember, typeof(LinkedPlayersForMemberController) },
{ StoolballRouteType.PlayersForTeam, typeof(PlayersForTeamController) },
{ StoolballRouteType.EditPlayersForTeam, typeof(EditPlayersForTeamController) },
{ StoolballRouteType.ClubStatistics, typeof(ClubStatisticsController) },
{ StoolballRouteType.TeamStatistics, typeof(TeamStatisticsController) },
{ StoolballRouteType.MatchLocationStatistics, typeof(MatchLocationStatisticsController) },
Expand Down
1 change: 1 addition & 0 deletions Stoolball.Web/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ public void ConfigureServices(IServiceCollection services)
services.AddTransient<CreateTeamController>();
services.AddTransient<EditTeamController>();
services.AddTransient<EditTransientTeamController>();
services.AddTransient<EditPlayersForTeamController>();
services.AddTransient<DeleteTeamController>();

services.AddTransient<CompetitionsController>();
Expand Down
Loading

0 comments on commit cc4d06b

Please sign in to comment.