//
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
//
namespace MUnique.OpenMU.Tests;
using Microsoft.Extensions.Logging.Abstractions;
using MUnique.OpenMU.DataModel.Configuration;
using MUnique.OpenMU.GameLogic;
using MUnique.OpenMU.Persistence.InMemory;
using MUnique.OpenMU.PlugIns;
///
/// Tests for .
///
[TestFixture]
public class PartyManagerTest
{
private const byte MaxPartySize = 5;
private PartyManager _partyManager = null!;
///
/// Sets up a fresh before each test.
///
[SetUp]
public void SetUp()
{
this._partyManager = new PartyManager(MaxPartySize, new NullLogger());
}
///
/// Tests that returns a party with the configured max size.
///
[Test]
public void CreateParty_RespectsMaxPartySize()
{
var party = this._partyManager.CreateParty();
Assert.That(party.MaxPartySize, Is.EqualTo(MaxPartySize));
}
///
/// Tests that a member added to the party is present in the party list.
///
[Test]
public async ValueTask CreateParty_AddedMemberIsInPartyList()
{
var member = await this.CreatePartyMemberAsync().ConfigureAwait(false);
var party = this._partyManager.CreateParty();
await party.AddAsync(member).ConfigureAwait(false);
Assert.That(party.PartyList, Contains.Item(member));
}
///
/// Tests that does nothing
/// when the member has no cached party.
///
[Test]
public async ValueTask OnMemberReconnected_DoesNothingWhenNoCachedParty()
{
var member = await this.CreatePartyMemberAsync().ConfigureAwait(false);
await this._partyManager.OnMemberReconnectedAsync(member).ConfigureAwait(false);
Assert.That(member.Party, Is.Null);
}
///
/// Tests that when a member is kicked the party list no longer contains them.
///
[Test]
public async ValueTask KickedMember_IsRemovedFromPartyList()
{
var (party, _, member2) = await this.CreatePartyWithTwoMembersAsync().ConfigureAwait(false);
await party.KickPlayerAsync(GetPartyMemberIndex(party, member2)).ConfigureAwait(false);
Assert.That(party.PartyList, Is.Not.Contains(member2));
Assert.That(member2.Party, Is.Null);
}
///
/// Tests that replaces a live member
/// with an snapshot.
///
[Test]
public async ValueTask LeaveTemporarily_ReplacesWithOfflineSnapshot()
{
var (party, member1, _) = await this.CreatePartyWithTwoMembersAsync().ConfigureAwait(false);
await party.LeaveTemporarilyAsync(member1).ConfigureAwait(false);
Assert.That(party.PartyList[0], Is.InstanceOf());
Assert.That(party.PartyList[0].Name, Is.EqualTo(member1.Name));
}
///
/// Tests that restores the live member
/// from an snapshot.
///
[Test]
public async ValueTask OnMemberReconnected_RestoresLiveMember()
{
var (party, member1, _) = await this.CreatePartyWithTwoMembersAsync().ConfigureAwait(false);
var originalName = member1.Name;
// Member disconnects - replaced with offline snapshot
await party.LeaveTemporarilyAsync(member1).ConfigureAwait(false);
// Member reconnects - restored from snapshot
await this._partyManager.OnMemberReconnectedAsync(member1).ConfigureAwait(false);
Assert.That(party.PartyList[0], Is.SameAs(member1));
Assert.That(party.PartyList[0].Name, Is.EqualTo(originalName));
}
///
/// Tests that after reconnect, the live member's Party reference is restored.
///
[Test]
public async ValueTask OnMemberReconnected_RestoresPartyReference()
{
var (party, member1, _) = await this.CreatePartyWithTwoMembersAsync().ConfigureAwait(false);
await party.LeaveTemporarilyAsync(member1).ConfigureAwait(false);
await this._partyManager.OnMemberReconnectedAsync(member1).ConfigureAwait(false);
Assert.That(member1.Party, Is.SameAs(party));
}
///
/// Tests that the offline snapshot preserves the disconnected member's name.
///
[Test]
public async ValueTask LeaveTemporarily_SnapshotPreservesName()
{
var (party, member1, _) = await this.CreatePartyWithTwoMembersAsync().ConfigureAwait(false);
await party.LeaveTemporarilyAsync(member1).ConfigureAwait(false);
Assert.That(party.PartyList[0].Name, Is.EqualTo(member1.Name));
}
///
/// Tests that preserves the party master status
/// when replacing the master.
///
[Test]
public async ValueTask ReplaceMember_PreservesMasterStatus()
{
var (party, member1, _) = await this.CreatePartyWithTwoMembersAsync().ConfigureAwait(false);
// member1 is master (first member added)
var snapshot = new OfflinePartyMember(member1);
await party.ReplaceMemberAsync(member1, snapshot).ConfigureAwait(false);
Assert.That(party.PartyMaster, Is.SameAs(snapshot));
}
///
/// Tests that clears the old member's party reference.
///
[Test]
public async ValueTask ReplaceMember_ClearsOldMemberPartyReference()
{
var (party, member1, _) = await this.CreatePartyWithTwoMembersAsync().ConfigureAwait(false);
var snapshot = new OfflinePartyMember(member1);
await party.ReplaceMemberAsync(member1, snapshot).ConfigureAwait(false);
Assert.That(member1.Party, Is.Null);
}
private async ValueTask<(Party Party, Player Member1, Player Member2)> CreatePartyWithTwoMembersAsync()
{
var member1 = await this.CreatePartyMemberAsync().ConfigureAwait(false);
var member2 = await this.CreatePartyMemberAsync().ConfigureAwait(false);
var party = this._partyManager.CreateParty();
await party.AddAsync(member1).ConfigureAwait(false);
await party.AddAsync(member2).ConfigureAwait(false);
return (party, member1, member2);
}
private static byte GetPartyMemberIndex(Party party, IPartyMember member)
{
for (byte index = 0; index < party.PartyList.Count; index++)
{
if (party.PartyList[index] == member)
{
return index;
}
}
throw new ArgumentException("The member is not part of the party.", nameof(member));
}
private async ValueTask CreatePartyMemberAsync()
{
var result = await PlayerTestHelper.CreatePlayerAsync(GameContextTestHelper.CreateGameContext()).ConfigureAwait(false);
await result.PlayerState.TryAdvanceToAsync(PlayerState.EnteredWorld).ConfigureAwait(false);
return result;
}
}