Skip to content

Commit

Permalink
Align existing tests to docs for linking and unlinking player identit…
Browse files Browse the repository at this point in the history
…ies, and get all tests back to passing #627
  • Loading branch information
sussexrick committed Jan 25, 2025
1 parent 4fcef40 commit b7d35f8
Show file tree
Hide file tree
Showing 25 changed files with 402 additions and 222 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,9 @@ public async Task Read_players_supports_no_filter()
public async Task Read_players_supports_filter_by_player_id()
{
var playerDataSource = new SqlServerPlayerDataSource(_connectionFactory, _routeNormaliser.Object, _statisticsQueryBuilder.Object);
var expectedPlayers = _testData.Players.Where(x => x.PlayerId != _testData.BowlerWithMultipleIdentities!.PlayerId).Take(3).ToList();
expectedPlayers.Add(_testData.BowlerWithMultipleIdentities!);
var playerWithMultipleIdentities = _testData.PlayersWithMultipleIdentities.First();
var expectedPlayers = _testData.Players.Where(x => x.PlayerId != playerWithMultipleIdentities.PlayerId).Take(3).ToList();
expectedPlayers.Add(playerWithMultipleIdentities);

var results = await playerDataSource.ReadPlayers(new PlayerFilter { PlayerIds = expectedPlayers.Select(x => x.PlayerId!.Value).ToList() });

Expand Down Expand Up @@ -236,7 +237,7 @@ public async Task Read_players_supports_exclude_by_player_identity_id()
var players = _testData.PlayersWhoHavePlayedAtLeastOneMatch().ToList();
var playerDataSource = new SqlServerPlayerDataSource(_connectionFactory, _routeNormaliser.Object, _statisticsQueryBuilder.Object);
var playerWithOneIdentity = players.First(x => x.PlayerIdentities.Count == 1);
var playerWithOtherIdentities = _testData.BowlerWithMultipleIdentities!;
var playerWithOtherIdentities = _testData.PlayersWithMultipleIdentities.First();
var identityToExcludeOfSeveral = playerWithOtherIdentities.PlayerIdentities.First().PlayerIdentityId!.Value;

var results = await playerDataSource.ReadPlayers(
Expand Down Expand Up @@ -498,7 +499,7 @@ private async Task Read_player_identities_supports_filter_by_involvement_in_a_se
public async Task Read_player_identities_supports_filter_by_player_identity_id()
{
var playerDataSource = new SqlServerPlayerDataSource(_connectionFactory, _routeNormaliser.Object, _statisticsQueryBuilder.Object);
var identities = _testData.BowlerWithMultipleIdentities!.PlayerIdentities;
var identities = _testData.PlayersWithMultipleIdentities.First().PlayerIdentities;

var results = await playerDataSource.ReadPlayerIdentities(new PlayerFilter { PlayerIdentityIds = identities.Select(x => x.PlayerIdentityId!.Value).ToList() });

Expand Down Expand Up @@ -727,11 +728,12 @@ public async Task Read_player_by_route_returns_all_identities_when_statistics_ar
public async Task ReadPlayerByMemberKey_returns_PlayerRoute_for_matching_player()
{
var playerDataSource = new SqlServerPlayerDataSource(_connectionFactory, _routeNormaliser.Object, _statisticsQueryBuilder.Object);
var player = _testData.PlayersWithMultipleIdentities.First(p => p.MemberKey.HasValue);

var result = await playerDataSource.ReadPlayerByMemberKey(_testData.BowlerWithMultipleIdentities!.MemberKey!.Value);
var result = await playerDataSource.ReadPlayerByMemberKey(player.MemberKey!.Value);

Assert.NotNull(result);
Assert.Equal(_testData.BowlerWithMultipleIdentities.PlayerRoute, result!.PlayerRoute);
Assert.Equal(player.PlayerRoute, result!.PlayerRoute);
}

[Fact]
Expand Down

Large diffs are not rendered by default.

Binary file not shown.
Binary file not shown.
6 changes: 3 additions & 3 deletions Stoolball.Data.SqlServer/SqlServerPlayerDataSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public async Task<List<Player>> ReadPlayers(PlayerFilter? filter, IDbConnection
return rawResults.GroupBy(x => x.PlayerId).Select(group =>
{
var player = group.First();
player.PlayerIdentities = group.Select(x => x.PlayerIdentities.Single()).OfType<PlayerIdentity>().ToList();
player.PlayerIdentities = new PlayerIdentityList(group.Select(x => x.PlayerIdentities.Single()).OfType<PlayerIdentity>());
return player;
}).ToList();
}
Expand Down Expand Up @@ -116,7 +116,7 @@ public async Task<List<PlayerIdentity>> ReadPlayerIdentities(PlayerFilter? filte
// 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();
identity.Player!.PlayerIdentities = new PlayerIdentityList(identities.Where(x => x.Player?.PlayerId == identity.Player.PlayerId));
}

return identities;
Expand Down Expand Up @@ -243,7 +243,7 @@ WHERE LOWER(PlayerRoute) = @Route
var playerToReturn = playerData.GroupBy(x => x.PlayerId).Select(group =>
{
var player = group.First();
player.PlayerIdentities = group.Select(x => x.PlayerIdentities.Single()).OfType<PlayerIdentity>().ToList();
player.PlayerIdentities = new PlayerIdentityList(group.Select(x => x.PlayerIdentities.Single()).OfType<PlayerIdentity>());
return player;
}).FirstOrDefault();

Expand Down
47 changes: 31 additions & 16 deletions Stoolball.Data.SqlServer/SqlServerPlayerRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -313,11 +313,11 @@ public async Task<MovedPlayerIdentity> LinkPlayerIdentity(Guid targetPlayer, Gui
connection.Open();
using (var transaction = connection.BeginTransaction())
{
var targetPlayerBefore = await connection.QuerySingleAsync<(string PlayerRoute, Guid? MemberKey)>($"SELECT PlayerRoute, MemberKey FROM {Tables.Player} WHERE PlayerId = @PlayerId", new Player { PlayerId = targetPlayer }, transaction);
var targetPlayerBefore = await connection.QuerySingleAsync<(string PlayerRoute, Guid? MemberKey)>($"SELECT PlayerRoute, MemberKey FROM {Tables.Player} WHERE PlayerId = @PlayerId", new Player { PlayerId = targetPlayer }, transaction).ConfigureAwait(false);
var identityToLinkBefore = await connection.QuerySingleAsync<(Guid PlayerId, string PlayerRoute, Guid? MemberKey, int Identities, string PlayerIdentityName, Guid TeamId)>(
@$"SELECT p.PlayerId, p.PlayerRoute, p.MemberKey, (SELECT COUNT(*) FROM {Tables.PlayerIdentity} WHERE PlayerId = p.PlayerId) AS Identities, pi.PlayerIdentityName, pi.TeamId
FROM {Tables.PlayerIdentity} pi INNER JOIN {Tables.Player} p ON pi.PlayerId = p.PlayerId
WHERE pi.PlayerIdentityId = @PlayerIdentityId", new { PlayerIdentityId = identityToLinkToTarget }, transaction);
WHERE pi.PlayerIdentityId = @PlayerIdentityId", new { PlayerIdentityId = identityToLinkToTarget }, transaction).ConfigureAwait(false);

// Are the players already linked to each other? If so, abort.
if (targetPlayer == identityToLinkBefore.PlayerId)
Expand Down Expand Up @@ -353,7 +353,7 @@ public async Task<MovedPlayerIdentity> LinkPlayerIdentity(Guid targetPlayer, Gui
// Does the target player have an identity on the same team as the identity to link? If not, abort.
var targetPlayerExistingIdentities = await connection.QueryAsync<(Guid PlayerIdentityId, string PlayerIdentityName, Guid TeamId)>(
$"SELECT PlayerIdentityId, PlayerIdentityName, TeamId FROM {Tables.PlayerIdentity} WHERE PlayerId = @PlayerId",
new { PlayerId = targetPlayer }, transaction);
new { PlayerId = targetPlayer }, transaction).ConfigureAwait(false);
var targetPlayerHasIdentityOnSameTeam = targetPlayerExistingIdentities.Any(id => id.TeamId == identityToLinkBefore.TeamId);

if (!targetPlayerHasIdentityOnSameTeam)
Expand All @@ -367,19 +367,23 @@ public async Task<MovedPlayerIdentity> LinkPlayerIdentity(Guid targetPlayer, Gui
// Move the player identities from the identity to link's current player id to the target player's id.
if (bestRoute != targetPlayerBefore.PlayerRoute)
{
await connection.ExecuteAsync($"UPDATE {Tables.Player} SET PlayerRoute = @PlayerRoute WHERE PlayerId = @PlayerId", new { PlayerRoute = bestRoute, PlayerId = targetPlayer }, transaction);
_ = await connection.ExecuteAsync($"UPDATE {Tables.Player} SET PlayerRoute = @PlayerRoute WHERE PlayerId = @PlayerId", new { PlayerRoute = bestRoute, PlayerId = targetPlayer }, transaction).ConfigureAwait(false);
}
var movePlayerIdentity = new { LinkedBy = linkedBy.ToString(), PlayerId = targetPlayer, PlayerIdentityId = identityToLinkToTarget };
await connection.ExecuteAsync($"UPDATE {Tables.PlayerIdentity} SET LinkedBy = @LinkedBy, PlayerId = @PlayerId WHERE PlayerIdentityId = @PlayerIdentityId", movePlayerIdentity, transaction);
_ = await connection.ExecuteAsync($"UPDATE {Tables.PlayerIdentity} SET LinkedBy = @LinkedBy, PlayerId = @PlayerId WHERE PlayerIdentityId = @PlayerIdentityId", movePlayerIdentity, transaction).ConfigureAwait(false);

// If the target player has and identities with LinkedBy = DefaultIdentity, they should now be linked by this activity
_ = await connection.ExecuteAsync($"UPDATE {Tables.PlayerIdentity} SET LinkedBy = @LinkedBy WHERE PlayerId = @PlayerId AND LinkedBy = '{PlayerIdentityLinkedBy.DefaultIdentity.ToString()}'",
new { LinkedBy = linkedBy.ToString(), PlayerId = targetPlayer }, transaction).ConfigureAwait(false);

// We also need to update statistics, and delete the now-unused player that the identity has been moved away from.
// However this is done asynchronously by ProcessAsyncUpdatesForPlayers, so we just need to mark the player as safe to delete.
await connection.ExecuteAsync($"UPDATE {Tables.Player} SET Deleted = 1 WHERE PlayerId = @PlayerId", new { identityToLinkBefore.PlayerId }, transaction);
_ = await connection.ExecuteAsync($"UPDATE {Tables.Player} SET Deleted = 1 WHERE PlayerId = @PlayerId", new { identityToLinkBefore.PlayerId }, transaction).ConfigureAwait(false);

var deletedPlayer = new Player { PlayerId = identityToLinkBefore.PlayerId, PlayerRoute = identityToLinkBefore.PlayerRoute };
deletedPlayer.PlayerIdentities.Add(new PlayerIdentity { PlayerIdentityId = identityToLinkToTarget });
var serialisedDeletedPlayer = JsonConvert.SerializeObject(deletedPlayer);
await _auditRepository.CreateAudit(new AuditRecord
_ = await _auditRepository.CreateAudit(new AuditRecord
{
Action = AuditAction.Delete,
MemberKey = memberKey,
Expand All @@ -388,7 +392,7 @@ await _auditRepository.CreateAudit(new AuditRecord
State = serialisedDeletedPlayer,
RedactedState = serialisedDeletedPlayer,
AuditDate = DateTime.UtcNow
}, transaction);
}, transaction).ConfigureAwait(false);

_logger.Info(LoggingTemplates.Deleted, serialisedDeletedPlayer, memberName, memberKey, GetType(), nameof(LinkPlayerIdentity));

Expand All @@ -399,7 +403,7 @@ await _auditRepository.CreateAudit(new AuditRecord
updatedTargetPlayer.PlayerIdentities.Add(reassignedPlayerIdentity);

var serialisedUpdatedPlayer = JsonConvert.SerializeObject(updatedTargetPlayer);
await _auditRepository.CreateAudit(new AuditRecord
_ = await _auditRepository.CreateAudit(new AuditRecord
{
Action = AuditAction.Update,
MemberKey = memberKey,
Expand All @@ -408,7 +412,7 @@ await _auditRepository.CreateAudit(new AuditRecord
State = serialisedUpdatedPlayer,
RedactedState = serialisedUpdatedPlayer,
AuditDate = DateTime.UtcNow
}, transaction);
}, transaction).ConfigureAwait(false);

_logger.Info(LoggingTemplates.Updated, serialisedUpdatedPlayer, memberName, memberKey, GetType(), nameof(LinkPlayerIdentity));

Expand Down Expand Up @@ -560,19 +564,30 @@ public async Task UnlinkPlayerIdentity(Guid identityToUnlink, Guid memberKey, st
connection.Open();
using (var transaction = connection.BeginTransaction())
{
var (totalIdentitiesLinkedToPlayer, playerId, playerIdentityName) = await connection.QuerySingleAsync<(int totalIdentitiesLinkedToMember, Guid playerId, string playerIdentityName)>(
$@"SELECT COUNT(*), PlayerId, (SELECT PlayerIdentityName FROM {Views.PlayerIdentity} WHERE PlayerIdentityId = @PlayerIdentityId) AS PlayerIdentityName
var identitiesForPlayer = (await connection.QueryAsync<(Guid PlayerId, Guid PlayerIdentityId, string Name, PlayerIdentityLinkedBy LinkedBy)>(
$@"SELECT PlayerId, PlayerIdentityId, PlayerIdentityName, LinkedBy
FROM {Views.PlayerIdentity}
WHERE PlayerId = (SELECT PlayerId FROM {Views.PlayerIdentity} WHERE PlayerIdentityId = @PlayerIdentityId)
GROUP BY PlayerId", new { PlayerIdentityId = identityToUnlink }, transaction).ConfigureAwait(false);
WHERE PlayerId = (SELECT PlayerId FROM {Views.PlayerIdentity} WHERE PlayerIdentityId = @PlayerIdentityId)",
new { PlayerIdentityId = identityToUnlink }, transaction).ConfigureAwait(false)).ToList();

if (totalIdentitiesLinkedToPlayer == 1)
if (identitiesForPlayer.Count == 1)
{
throw new InvalidOperationException();
}
else
{
await MoveIdentityToNewPlayer(identityToUnlink, playerIdentityName, memberKey, memberName, transaction, nameof(UnlinkPlayerIdentity)).ConfigureAwait(false);
var identityToUnlinkName = identitiesForPlayer.Single(pi => pi.PlayerIdentityId == identityToUnlink).Name;
await MoveIdentityToNewPlayer(identityToUnlink, identityToUnlinkName, memberKey, memberName, transaction, nameof(UnlinkPlayerIdentity)).ConfigureAwait(false);

var remainingIdentities = identitiesForPlayer.Where(pi => pi.PlayerIdentityId != identityToUnlink).ToList();
if (remainingIdentities.Count == 1 &&
remainingIdentities[0].LinkedBy == PlayerIdentityLinkedBy.Team || remainingIdentities[0].LinkedBy == PlayerIdentityLinkedBy.StoolballEngland)
{
_ = await connection.ExecuteAsync($"UPDATE {Tables.PlayerIdentity} SET LinkedBy = @LinkedBy WHERE PlayerIdentityId = @PlayerIdentityId",
new { LinkedBy = PlayerIdentityLinkedBy.DefaultIdentity.ToString(), remainingIdentities[0].PlayerIdentityId },
transaction).ConfigureAwait(false);

}
}

transaction.Commit();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@
using Stoolball.Statistics;
using Stoolball.Teams;
using Stoolball.Testing.Fakers;
using Stoolball.Testing.PlayerDataProviders;

namespace Stoolball.Testing.TeamDataProviders
namespace Stoolball.Testing.PlayerDataProviders
{
internal class PlayersLinkedToMembersProvider(IFakerFactory<Team> teamFakerFactory, IFakerFactory<Player> playerFakerFactory, IFakerFactory<PlayerIdentity> playerIdentityFakerFactory) : BasePlayerDataProvider
{
Expand Down Expand Up @@ -44,15 +43,6 @@ internal override IEnumerable<Player> CreatePlayers(TestData readOnlyTestData)
identity.LinkedBy = PlayerIdentityLinkedBy.Member;
}

for (var i = 0; i < 2; i++)
{
players[i].PlayerIdentities.Add(identities[i]);
identities[i].Player = players[i];
identities[i].Team = team;
identities[i].LinkedBy = PlayerIdentityLinkedBy.Member;
players[i].MemberKey = Guid.NewGuid();
}

// another player on the same team, not linked to a member
var playerWithoutMember = _playerFaker.Generate(1).Single();
playerWithoutMember.PlayerIdentities.Add(identities[2]);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using System.Collections.Generic;
using System.Linq;
using Bogus;
using Stoolball.Statistics;
using Stoolball.Teams;
using Stoolball.Testing.Fakers;

namespace Stoolball.Testing.PlayerDataProviders
{
internal class PlayersNotLinkedToMembersProvider(IFakerFactory<Team> teamFakerFactory, IFakerFactory<Player> playerFakerFactory, IFakerFactory<PlayerIdentity> playerIdentityFakerFactory) : BasePlayerDataProvider
{
private readonly Faker<Team> _teamFaker = teamFakerFactory.Create();
private readonly Faker<Player> _playerFaker = playerFakerFactory.Create();
private readonly Faker<PlayerIdentity> _playerIdentityFaker = playerIdentityFakerFactory.Create();

internal override IEnumerable<Player> CreatePlayers(TestData readOnlyTestData)
{
var team = _teamFaker.Generate(1).Single();

// player with a single identity
var playerWithSingleIdentity = _playerFaker.Generate(1).Single();
playerWithSingleIdentity.PlayerIdentities.Add(_playerIdentityFaker.Generate(1).Single());
playerWithSingleIdentity.PlayerIdentities[0].Player = playerWithSingleIdentity;
playerWithSingleIdentity.PlayerIdentities[0].Team = team;
playerWithSingleIdentity.PlayerIdentities[0].LinkedBy = PlayerIdentityLinkedBy.DefaultIdentity;

// player with two identities both linked by team, on the same team, not linked to member
var playerWithTwoIdentitiesLinkedByTeam = _playerFaker.Generate(1).Single();
playerWithTwoIdentitiesLinkedByTeam.PlayerIdentities.AddRange(_playerIdentityFaker.Generate(2));

foreach (var identity in playerWithTwoIdentitiesLinkedByTeam.PlayerIdentities)
{
identity.Player = playerWithTwoIdentitiesLinkedByTeam;
identity.Team = team;
identity.LinkedBy = PlayerIdentityLinkedBy.Team;
}

return [playerWithSingleIdentity, playerWithTwoIdentitiesLinkedByTeam];
}
}
}
Loading

0 comments on commit b7d35f8

Please sign in to comment.