diff --git a/EvoSC.sln b/EvoSC.sln index 8949be641..e68f57b82 100644 --- a/EvoSC.sln +++ b/EvoSC.sln @@ -124,6 +124,11 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TeamSettingsModule.Tests", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ForceTeamModule.Tests", "tests\Modules\ForceTeamModule.Tests\ForceTeamModule.Tests.csproj", "{DE9532E8-F0ED-400B-9592-AF8F74BC83EB}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServerManagementModule", "src/Modules/ServerManagementModule/ServerManagementModule.csproj", "{AAE1AC39-4C4B-48A9-8B7B-08D2CD461FC1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServerManagementModule.Tests", "tests\Modules\ServerManagementModule.Tests\ServerManagementModule.Tests.csproj", "{F8C7FE5E-B389-4BA8-B0DF-6D9D0A9B0949}" +EndProject + @@ -366,6 +371,14 @@ Global {DE9532E8-F0ED-400B-9592-AF8F74BC83EB}.Debug|Any CPU.Build.0 = Debug|Any CPU {DE9532E8-F0ED-400B-9592-AF8F74BC83EB}.Release|Any CPU.ActiveCfg = Release|Any CPU {DE9532E8-F0ED-400B-9592-AF8F74BC83EB}.Release|Any CPU.Build.0 = Release|Any CPU + {AAE1AC39-4C4B-48A9-8B7B-08D2CD461FC1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AAE1AC39-4C4B-48A9-8B7B-08D2CD461FC1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AAE1AC39-4C4B-48A9-8B7B-08D2CD461FC1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AAE1AC39-4C4B-48A9-8B7B-08D2CD461FC1}.Release|Any CPU.Build.0 = Release|Any CPU + {F8C7FE5E-B389-4BA8-B0DF-6D9D0A9B0949}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F8C7FE5E-B389-4BA8-B0DF-6D9D0A9B0949}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F8C7FE5E-B389-4BA8-B0DF-6D9D0A9B0949}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F8C7FE5E-B389-4BA8-B0DF-6D9D0A9B0949}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -424,5 +437,7 @@ Global {C10A11E5-4AD8-4229-81F1-57D7DC364E27} = {DC47658A-F421-4BA4-B617-090A7DFB3900} {B1745099-0081-4443-BF79-26A888765E16} = {6D75D6A2-6ECD-4DE4-96C5-CAD7D134407A} {DE9532E8-F0ED-400B-9592-AF8F74BC83EB} = {6D75D6A2-6ECD-4DE4-96C5-CAD7D134407A} + {AAE1AC39-4C4B-48A9-8B7B-08D2CD461FC1} = {DC47658A-F421-4BA4-B617-090A7DFB3900} + {F8C7FE5E-B389-4BA8-B0DF-6D9D0A9B0949} = {6D75D6A2-6ECD-4DE4-96C5-CAD7D134407A} EndGlobalSection EndGlobal diff --git a/src/EvoSC.Testing/Controllers/CommandInteractionControllerTestBase.cs b/src/EvoSC.Testing/Controllers/CommandInteractionControllerTestBase.cs index addd9f206..58fb2231f 100644 --- a/src/EvoSC.Testing/Controllers/CommandInteractionControllerTestBase.cs +++ b/src/EvoSC.Testing/Controllers/CommandInteractionControllerTestBase.cs @@ -1,12 +1,18 @@ using EvoSC.Commands.Interfaces; +using EvoSC.Common.Interfaces; using EvoSC.Common.Interfaces.Controllers; using EvoSC.Common.Interfaces.Models; +using GbxRemoteNet.Interfaces; +using Moq; namespace EvoSC.Testing.Controllers; public class CommandInteractionControllerTestBase : ControllerMock where TController : class, IController { + protected readonly (Mock Client, Mock Remote) Server = + Mocking.NewServerClientMock(); + /// /// Initialize this controller mock. /// @@ -15,7 +21,7 @@ public class CommandInteractionControllerTestBase : ControllerMock< protected void InitMock(IOnlinePlayer actor, params object[] services) { base.InitMock(services); - this.SetupMock(actor); + this.SetupMock(Server.Client, actor); } } diff --git a/src/EvoSC.Testing/Mocking.cs b/src/EvoSC.Testing/Mocking.cs index 97f782eee..da38f7462 100644 --- a/src/EvoSC.Testing/Mocking.cs +++ b/src/EvoSC.Testing/Mocking.cs @@ -36,9 +36,10 @@ public static ControllerContextMock NewControllerContextMock /// The mocked actor that triggered this action. /// public static ControllerContextMock SetupMock( - this ControllerContextMock mock, IOnlinePlayer actor) + this ControllerContextMock mock, Mock serverClient, IOnlinePlayer actor) { mock.Context.Setup(c => c.Player).Returns(actor); + mock.Context.Setup(c => c.Server).Returns(serverClient.Object); mock.Context.Object.AuditEvent.CausedBy(actor); return mock; @@ -50,8 +51,8 @@ public static ControllerContextMock SetupMock( /// The mocked actor that triggered this action. /// public static ControllerContextMock - NewPlayerInteractionContextMock(IOnlinePlayer actor) => - new ControllerContextMock().SetupMock(actor); + NewPlayerInteractionContextMock(Mock serverClient, IOnlinePlayer actor) => + new ControllerContextMock().SetupMock(serverClient, actor); /// /// Set up a command interaction context mock. @@ -60,9 +61,10 @@ public static ControllerContextMock /// The mocked actor that triggered this command. /// public static ControllerContextMock SetupMock( - this ControllerContextMock mock, IOnlinePlayer actor) + this ControllerContextMock mock, Mock serverClient, IOnlinePlayer actor) { mock.Context.Setup(c => c.Player).Returns(actor); + mock.Context.Setup(c => c.Server).Returns(serverClient.Object); mock.Context.Object.AuditEvent.CausedBy(actor); return mock; @@ -74,8 +76,8 @@ public static ControllerContextMock SetupMock( /// The mocked actor that triggered this command. /// public static ControllerContextMock - NewCommandInteractionContextMock(IOnlinePlayer actor) => - new ControllerContextMock().SetupMock(actor); + NewCommandInteractionContextMock(Mock serverClient, IOnlinePlayer actor) => + new ControllerContextMock().SetupMock(serverClient, actor); /// /// Set up a new Manialink context mock. diff --git a/src/EvoSC/EvoSC.csproj b/src/EvoSC/EvoSC.csproj index 2fd3e1ba0..f805432f5 100644 --- a/src/EvoSC/EvoSC.csproj +++ b/src/EvoSC/EvoSC.csproj @@ -48,6 +48,7 @@ + diff --git a/src/EvoSC/InternalModules.cs b/src/EvoSC/InternalModules.cs index 58bbe4458..524043756 100644 --- a/src/EvoSC/InternalModules.cs +++ b/src/EvoSC/InternalModules.cs @@ -20,6 +20,7 @@ using EvoSC.Modules.Official.Player; using EvoSC.Modules.Official.PlayerRecords; using EvoSC.Modules.Official.Scoreboard; +using EvoSC.Modules.Official.ServerManagementModule; using EvoSC.Modules.Official.SetName; using EvoSC.Modules.Official.SpectatorTargetInfoModule; using EvoSC.Modules.Official.TeamSettingsModule; @@ -55,7 +56,8 @@ public static class InternalModules typeof(MapListModule), typeof(LocalRecordsModule), typeof(ForceTeamModule), - typeof(TeamSettingsModule) + typeof(TeamSettingsModule), + typeof(ServerManagementModule) ]; /// diff --git a/src/Modules/ServerManagementModule/Controllers/ServerCommandsController.cs b/src/Modules/ServerManagementModule/Controllers/ServerCommandsController.cs new file mode 100644 index 000000000..e4192f5a2 --- /dev/null +++ b/src/Modules/ServerManagementModule/Controllers/ServerCommandsController.cs @@ -0,0 +1,73 @@ +using EvoSC.Commands.Attributes; +using EvoSC.Commands.Interfaces; +using EvoSC.Common.Controllers; +using EvoSC.Common.Controllers.Attributes; +using EvoSC.Modules.Official.ServerManagementModule.Events; +using EvoSC.Modules.Official.ServerManagementModule.Interfaces; +using EvoSC.Modules.Official.ServerManagementModule.Permissions; + +namespace EvoSC.Modules.Official.ServerManagementModule.Controllers; + +[Controller] +public class ServerCommandsController(IServerManagementService serverManagementService) : EvoScController +{ + [ChatCommand("setpassword", "Set the player and spectator password for joining the server.", + ServerManagementPermissions.SetPassword)] + [CommandAlias("/setpw", true)] + public async Task SetServerPasswordAsync(string password) + { + await serverManagementService.SetPasswordAsync(password); + + Context.AuditEvent + .WithEventName(ServerManagementAuditEvents.PasswordSet) + .HavingProperties(new { password }) + .Success(); + + await Context.Server.SuccessMessageAsync(Context.Player, "The password was changed."); + } + + [ChatCommand("clearpassword", "Clear the server password.", + ServerManagementPermissions.SetPassword)] + [CommandAlias("/clearpw", true)] + public async Task ClearServerPasswordAsync() + { + await serverManagementService.SetPasswordAsync(""); + + Context.AuditEvent + .WithEventName(ServerManagementAuditEvents.PasswordSet) + .HavingProperties(new { password = "", cleared = true }) + .Success(); + + await Context.Server.SuccessMessageAsync(Context.Player, "The password was cleared and removed."); + } + + [ChatCommand("setmaxplayers", "Set maximum number of players that can join the server.", + ServerManagementPermissions.SetMaxSlots)] + [CommandAlias("/maxplayers", true)] + public async Task SetMaxPlayersAsync(int maxPlayers) + { + await serverManagementService.SetMaxPlayersAsync(maxPlayers); + + Context.AuditEvent + .WithEventName(ServerManagementAuditEvents.MaxPlayersSet) + .HavingProperties(new { maxPlayers }) + .Success(); + + await Context.Server.SuccessMessageAsync(Context.Player, $"Max players set to {maxPlayers}"); + } + + [ChatCommand("setmaxspectators", "Set the maximum number of spectators that can join the server.", + ServerManagementPermissions.SetMaxSlots)] + [CommandAlias("/maxspectators", true)] + public async Task SetMaxSpectatorsAsync(int maxSpectators) + { + await serverManagementService.SetMaxSpectatorsAsync(maxSpectators); + + Context.AuditEvent + .WithEventName(ServerManagementAuditEvents.MaxSpectatorsSet) + .HavingProperties(new { maxSpectators }) + .Success(); + + await Context.Server.SuccessMessageAsync(Context.Player, $"Max spectators set to {maxSpectators}"); + } +} diff --git a/src/Modules/ServerManagementModule/Events/Args/PasswordChangedEventArgs.cs b/src/Modules/ServerManagementModule/Events/Args/PasswordChangedEventArgs.cs new file mode 100644 index 000000000..2b0eeebb9 --- /dev/null +++ b/src/Modules/ServerManagementModule/Events/Args/PasswordChangedEventArgs.cs @@ -0,0 +1,8 @@ +using EvoSC.Common.Events.Arguments; + +namespace EvoSC.Modules.Official.ServerManagementModule.Events.Args; + +public class PasswordChangedEventArgs : EvoScEventArgs +{ + public string NewPassword { get; init; } +} diff --git a/src/Modules/ServerManagementModule/Events/Args/PlayerSlotsChangedEventArgs.cs b/src/Modules/ServerManagementModule/Events/Args/PlayerSlotsChangedEventArgs.cs new file mode 100644 index 000000000..722329a40 --- /dev/null +++ b/src/Modules/ServerManagementModule/Events/Args/PlayerSlotsChangedEventArgs.cs @@ -0,0 +1,8 @@ +using EvoSC.Common.Events.Arguments; + +namespace EvoSC.Modules.Official.ServerManagementModule.Events.Args; + +public class PlayerSlotsChangedEventArgs : EvoScEventArgs +{ + public int NewSlots { get; init; } +} diff --git a/src/Modules/ServerManagementModule/Events/ServerManagementAuditEvents.cs b/src/Modules/ServerManagementModule/Events/ServerManagementAuditEvents.cs new file mode 100644 index 000000000..ce30dff3c --- /dev/null +++ b/src/Modules/ServerManagementModule/Events/ServerManagementAuditEvents.cs @@ -0,0 +1,8 @@ +namespace EvoSC.Modules.Official.ServerManagementModule.Events; + +public enum ServerManagementAuditEvents +{ + PasswordSet, + MaxPlayersSet, + MaxSpectatorsSet +} diff --git a/src/Modules/ServerManagementModule/Events/ServerManagementEvents.cs b/src/Modules/ServerManagementModule/Events/ServerManagementEvents.cs new file mode 100644 index 000000000..816de406c --- /dev/null +++ b/src/Modules/ServerManagementModule/Events/ServerManagementEvents.cs @@ -0,0 +1,8 @@ +namespace EvoSC.Modules.Official.ServerManagementModule.Events; + +public enum ServerManagementEvents +{ + PasswordChanged, + MaxPlayersChanged, + MaxSpectatorsChanged +} diff --git a/src/Modules/ServerManagementModule/Interfaces/IServerManagementService.cs b/src/Modules/ServerManagementModule/Interfaces/IServerManagementService.cs new file mode 100644 index 000000000..3b7da6c46 --- /dev/null +++ b/src/Modules/ServerManagementModule/Interfaces/IServerManagementService.cs @@ -0,0 +1,8 @@ +namespace EvoSC.Modules.Official.ServerManagementModule.Interfaces; + +public interface IServerManagementService +{ + public Task SetPasswordAsync(string password); + public Task SetMaxPlayersAsync(int maxPlayers); + public Task SetMaxSpectatorsAsync(int maxSpectators); +} diff --git a/src/Modules/ServerManagementModule/Localization.resx b/src/Modules/ServerManagementModule/Localization.resx new file mode 100644 index 000000000..10e3157c7 --- /dev/null +++ b/src/Modules/ServerManagementModule/Localization.resx @@ -0,0 +1,19 @@ + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + diff --git a/src/Modules/ServerManagementModule/Permissions/ServerManagementPermissions.cs b/src/Modules/ServerManagementModule/Permissions/ServerManagementPermissions.cs new file mode 100644 index 000000000..9f54cb82e --- /dev/null +++ b/src/Modules/ServerManagementModule/Permissions/ServerManagementPermissions.cs @@ -0,0 +1,10 @@ +using EvoSC.Common.Permissions.Attributes; + +namespace EvoSC.Modules.Official.ServerManagementModule.Permissions; + +[PermissionGroup] +public enum ServerManagementPermissions +{ + SetPassword, + SetMaxSlots +} diff --git a/src/Modules/ServerManagementModule/ServerManagementModule.cs b/src/Modules/ServerManagementModule/ServerManagementModule.cs new file mode 100644 index 000000000..bf93e8e4f --- /dev/null +++ b/src/Modules/ServerManagementModule/ServerManagementModule.cs @@ -0,0 +1,6 @@ +using EvoSC.Modules.Attributes; + +namespace EvoSC.Modules.Official.ServerManagementModule; + +[Module(IsInternal = true)] +public class ServerManagementModule : EvoScModule; diff --git a/src/Modules/ServerManagementModule/ServerManagementModule.csproj b/src/Modules/ServerManagementModule/ServerManagementModule.csproj new file mode 100644 index 000000000..a3044cae9 --- /dev/null +++ b/src/Modules/ServerManagementModule/ServerManagementModule.csproj @@ -0,0 +1,25 @@ + + + net8.0 + enable + enable + EvoSC.Modules.Official.ServerManagementModule + false + ServerManagementModule + Server Management + Commands and functions to manage server configuration such as password, player slots etc. + 1.0.0 + Evo + + + + + + + + + + + + + diff --git a/src/Modules/ServerManagementModule/Services/ServerManagementService.cs b/src/Modules/ServerManagementModule/Services/ServerManagementService.cs new file mode 100644 index 000000000..984c778bc --- /dev/null +++ b/src/Modules/ServerManagementModule/Services/ServerManagementService.cs @@ -0,0 +1,34 @@ +using EvoSC.Common.Interfaces; +using EvoSC.Common.Services.Attributes; +using EvoSC.Modules.Official.ServerManagementModule.Events; +using EvoSC.Modules.Official.ServerManagementModule.Events.Args; +using EvoSC.Modules.Official.ServerManagementModule.Interfaces; + +namespace EvoSC.Modules.Official.ServerManagementModule.Services; + +[Service] +public class ServerManagementService(IServerClient serverClient, IEventManager events) : IServerManagementService +{ + public async Task SetPasswordAsync(string password) + { + await serverClient.Remote.SetServerPasswordAsync(password); + await serverClient.Remote.SetServerPasswordForSpectatorAsync(password); + + await events.RaiseAsync(ServerManagementEvents.PasswordChanged, + new PasswordChangedEventArgs { NewPassword = password }); + } + + public async Task SetMaxPlayersAsync(int maxPlayers) + { + await serverClient.Remote.SetMaxPlayersAsync(maxPlayers); + await events.RaiseAsync(ServerManagementEvents.MaxPlayersChanged, + new PlayerSlotsChangedEventArgs() { NewSlots = maxPlayers }); + } + + public async Task SetMaxSpectatorsAsync(int maxSpectators) + { + await serverClient.Remote.SetMaxSpectatorsAsync(maxSpectators); + await events.RaiseAsync(ServerManagementEvents.MaxSpectatorsChanged, + new PlayerSlotsChangedEventArgs() { NewSlots = maxSpectators }); + } +} diff --git a/src/Modules/ServerManagementModule/info.toml b/src/Modules/ServerManagementModule/info.toml new file mode 100644 index 000000000..4a0cb781d --- /dev/null +++ b/src/Modules/ServerManagementModule/info.toml @@ -0,0 +1,11 @@ +[info] +# A unique name for this module, this is used as a identifier +name = "ServerManagementModule" +# The title of the module +title = "Server Management" +# A short description of what the module is and does +summary = "Commands and functions to manage server configuration such as password, player slots etc." +# The current version of this module, using SEMVER +version = "1.0.0" +# The name of the author that created this module +author = "Evo" diff --git a/tests/EvoSC.Testing.Tests/MockingTests.cs b/tests/EvoSC.Testing.Tests/MockingTests.cs index 31546cacf..62aaa5abb 100644 --- a/tests/EvoSC.Testing.Tests/MockingTests.cs +++ b/tests/EvoSC.Testing.Tests/MockingTests.cs @@ -29,8 +29,9 @@ public void NewControllerContextMock_Returns_Correct_Mock() public void SetupMock_For_PlayerInteraction_Sets_Up_Correctly() { var mock = Mocking.NewControllerContextMock(); + var serverClient = Mocking.NewServerClientMock(); var player = new Mock(); - mock.SetupMock(player.Object); + mock.SetupMock(serverClient.Client, player.Object); Assert.Equal(player.Object, mock.Context.Object.Player); mock.AuditEventBuilder.Verify(m => m.CausedBy(player.Object), Times.Once); @@ -39,8 +40,9 @@ public void SetupMock_For_PlayerInteraction_Sets_Up_Correctly() [Fact] public void NewPlayerInteractionContextMock_Returns_Correct_Mock() { + var serverClient = Mocking.NewServerClientMock(); var player = new Mock(); - var mock = Mocking.NewPlayerInteractionContextMock(player.Object); + var mock = Mocking.NewPlayerInteractionContextMock(serverClient.Client, player.Object); Assert.Equal(player.Object, mock.Context.Object.Player); mock.AuditEventBuilder.Verify(m => m.CausedBy(player.Object), Times.Once); @@ -50,8 +52,9 @@ public void NewPlayerInteractionContextMock_Returns_Correct_Mock() public void SetupMock_For_CommandInteraction_Sets_Up_Correctly() { var mock = Mocking.NewControllerContextMock(); + var serverClient = Mocking.NewServerClientMock(); var player = new Mock(); - mock.SetupMock(player.Object); + mock.SetupMock(serverClient.Client, player.Object); Assert.Equal(player.Object, mock.Context.Object.Player); mock.AuditEventBuilder.Verify(m => m.CausedBy(player.Object), Times.Once); @@ -60,14 +63,14 @@ public void SetupMock_For_CommandInteraction_Sets_Up_Correctly() [Fact] public void NewCommandInteractionContextMock_Returns_Correct_Mock() { + var server = Mocking.NewServerClientMock(); var player = new Mock(); - var mock = Mocking.NewCommandInteractionContextMock(player.Object); + var mock = Mocking.NewCommandInteractionContextMock(server.Client, player.Object); Assert.Equal(player.Object, mock.Context.Object.Player); mock.AuditEventBuilder.Verify(m => m.CausedBy(player.Object), Times.Once); } - [Fact] public void SetupMock_For_ManialinkInteraction_Sets_Up_Correctly() { diff --git a/tests/Modules/ASayModule.Tests/ASayServiceTest.cs b/tests/Modules/ASayModule.Tests/ASayServiceTest.cs index 6aafc10e0..9e7507a92 100644 --- a/tests/Modules/ASayModule.Tests/ASayServiceTest.cs +++ b/tests/Modules/ASayModule.Tests/ASayServiceTest.cs @@ -4,6 +4,7 @@ using EvoSC.Common.Interfaces.Models; using EvoSC.Common.Interfaces.Services; using EvoSC.Common.Interfaces.Util.Auditing; +using EvoSC.Common.Remote; using EvoSC.Manialinks.Interfaces; using EvoSC.Modules.Official.ASayModule.Interfaces; using EvoSC.Modules.Official.ASayModule.Services; @@ -24,9 +25,12 @@ public class ASayServiceTest private readonly Mock _manialinkManager = new(); private ControllerContextMock _commandContext; + private readonly (Mock Client, Mock Remote) _serverClient = + Mocking.NewServerClientMock(); + public ASayServiceTest() { - _commandContext = Mocking.NewCommandInteractionContextMock(_actor.Object); + _commandContext = Mocking.NewCommandInteractionContextMock(_serverClient.Client, _actor.Object); _actor.Setup(m => m.NickName).Returns(PlayerNickName); _actor.Setup(m => m.AccountId).Returns(PlayerAccountId); diff --git a/tests/Modules/ForceTeamModule.Tests/Services/ForceTeamServiceTests.cs b/tests/Modules/ForceTeamModule.Tests/Services/ForceTeamServiceTests.cs index 03981fae3..b57f50052 100644 --- a/tests/Modules/ForceTeamModule.Tests/Services/ForceTeamServiceTests.cs +++ b/tests/Modules/ForceTeamModule.Tests/Services/ForceTeamServiceTests.cs @@ -24,8 +24,9 @@ Locale locale var server = Mocking.NewServerClientMock(); var playerManager = new Mock(); + var serverClient = Mocking.NewServerClientMock(); var player = new Mock(); - var context = Mocking.NewPlayerInteractionContextMock(player.Object); + var context = Mocking.NewPlayerInteractionContextMock(serverClient.Client, player.Object); var contextServiceMock = Mocking.NewContextServiceMock(context.Context.Object, player.Object); var locale = Mocking.NewLocaleMock(contextServiceMock.Object); diff --git a/tests/Modules/Player.Tests/PlayerServiceTests.cs b/tests/Modules/Player.Tests/PlayerServiceTests.cs index 6e00babb8..3a312e94f 100644 --- a/tests/Modules/Player.Tests/PlayerServiceTests.cs +++ b/tests/Modules/Player.Tests/PlayerServiceTests.cs @@ -25,11 +25,13 @@ public class PlayerServiceTests private Mock _actor = new(); private ControllerContextMock _eventContext; private ControllerContextMock _commandContext; + private readonly (Mock Client, Mock Remote) _serverClient = + Mocking.NewServerClientMock(); public PlayerServiceTests() { _eventContext = Mocking.NewControllerContextMock(); - _commandContext = Mocking.NewCommandInteractionContextMock(_actor.Object); + _commandContext = Mocking.NewCommandInteractionContextMock(_serverClient.Client, _actor.Object); _actor.Setup(m => m.NickName).Returns(PlayerNickName); _actor.Setup(m => m.AccountId).Returns(PlayerAccountId); diff --git a/tests/Modules/ServerManagementModule.Tests/Controllers/ServerCommandsControllerTests.cs b/tests/Modules/ServerManagementModule.Tests/Controllers/ServerCommandsControllerTests.cs new file mode 100644 index 000000000..7fc2c8c92 --- /dev/null +++ b/tests/Modules/ServerManagementModule.Tests/Controllers/ServerCommandsControllerTests.cs @@ -0,0 +1,62 @@ +using EvoSC.Common.Interfaces.Models; +using EvoSC.Modules.Official.ServerManagementModule.Controllers; +using EvoSC.Modules.Official.ServerManagementModule.Events; +using EvoSC.Modules.Official.ServerManagementModule.Interfaces; +using EvoSC.Testing.Controllers; +using Moq; + +namespace ServerManagementModule.Tests.Controllers; + +public class ServerCommandsControllerTests : CommandInteractionControllerTestBase +{ + private readonly Mock _serverManagementMock = new(); + private readonly Mock _player = new(); + + public ServerCommandsControllerTests() + { + InitMock(_player.Object, _serverManagementMock); + } + + [Fact] + public async Task SetServerPassword_Calls_Service_And_Audits() + { + const string Password = "MyPassword123"; + await Controller.SetServerPasswordAsync(Password); + + _serverManagementMock.Verify(m => m.SetPasswordAsync(Password)); + AuditEventBuilder.Verify(m => m.WithEventName(ServerManagementAuditEvents.PasswordSet)); + AuditEventBuilder.Verify(m => m.Success()); + } + + [Fact] + public async Task ClearPassword_Sets_Empty_Password_And_Audits() + { + await Controller.ClearServerPasswordAsync(); + + _serverManagementMock.Verify(m => m.SetPasswordAsync("")); + AuditEventBuilder.Verify(m => m.WithEventName(ServerManagementAuditEvents.PasswordSet)); + AuditEventBuilder.Verify(m => m.Success()); + } + + [Fact] + public async Task SetMaxPlayers_Calls_Service_And_Audits() + { + const int MaxPlayers = 123; + await Controller.SetMaxPlayersAsync(MaxPlayers); + + _serverManagementMock.Verify(m => m.SetMaxPlayersAsync(MaxPlayers)); + AuditEventBuilder.Verify(m => m.WithEventName(ServerManagementAuditEvents.MaxPlayersSet)); + AuditEventBuilder.Verify(m => m.Success()); + } + + [Fact] + public async Task SetMaxSpectators_Calls_Service_And_Audits() + { + const int MaxPlayers = 123; + await Controller.SetMaxSpectatorsAsync(MaxPlayers); + + _serverManagementMock.Verify(m => m.SetMaxSpectatorsAsync(MaxPlayers)); + AuditEventBuilder.Verify(m => m.WithEventName(ServerManagementAuditEvents.MaxSpectatorsSet)); + AuditEventBuilder.Verify(m => m.Success()); + } +} diff --git a/tests/Modules/ServerManagementModule.Tests/GlobalUsings.cs b/tests/Modules/ServerManagementModule.Tests/GlobalUsings.cs new file mode 100644 index 000000000..c802f4480 --- /dev/null +++ b/tests/Modules/ServerManagementModule.Tests/GlobalUsings.cs @@ -0,0 +1 @@ +global using Xunit; diff --git a/tests/Modules/ServerManagementModule.Tests/ServerManagementModule.Tests.csproj b/tests/Modules/ServerManagementModule.Tests/ServerManagementModule.Tests.csproj new file mode 100644 index 000000000..240bb89cc --- /dev/null +++ b/tests/Modules/ServerManagementModule.Tests/ServerManagementModule.Tests.csproj @@ -0,0 +1,30 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + diff --git a/tests/Modules/ServerManagementModule.Tests/Services/ServerManagementServiceTests.cs b/tests/Modules/ServerManagementModule.Tests/Services/ServerManagementServiceTests.cs new file mode 100644 index 000000000..8640ad96b --- /dev/null +++ b/tests/Modules/ServerManagementModule.Tests/Services/ServerManagementServiceTests.cs @@ -0,0 +1,63 @@ +using EvoSC.Common.Interfaces; +using EvoSC.Modules.Official.ServerManagementModule.Interfaces; +using EvoSC.Modules.Official.ServerManagementModule.Services; +using EvoSC.Testing; +using GbxRemoteNet.Interfaces; +using Moq; + +namespace ServerManagementModule.Tests.Services; + +public class ServerManagementServiceTests +{ + public ( + IServerManagementService ServerManagementService, + (Mock Client, Mock Remote) Server, + Mock EventManager + ) NewServiceMock() + { + var server = Mocking.NewServerClientMock(); + var events = new Mock(); + + var serverManagementService = new ServerManagementService(server.Client.Object, events.Object); + + return ( + serverManagementService, + server, + events + ); + } + + [Fact] + public async Task SetPassword_Sets_Both_Player_And_Spectator_Password() + { + var mock = NewServiceMock(); + const string Password = "MyPassword123"; + + await mock.ServerManagementService.SetPasswordAsync(Password); + + mock.Server.Remote.Verify(m => m.SetServerPasswordAsync(Password)); + mock.Server.Remote.Verify(m => m.SetServerPasswordForSpectatorAsync(Password)); + } + + [Fact] + public async Task SetMaxPlayers_Sets_MaxPlayers() + { + var mock = NewServiceMock(); + const int MaxPlayers = 123; + + await mock.ServerManagementService.SetMaxPlayersAsync(MaxPlayers); + + mock.Server.Remote.Verify(m => m.SetMaxPlayersAsync(MaxPlayers)); + } + + [Fact] + public async Task SetMaxSpectators_Sets_MaxSpectators() + { + var mock = NewServiceMock(); + const int MaxSpectators = 123; + + await mock.ServerManagementService.SetMaxSpectatorsAsync(MaxSpectators); + + mock.Server.Remote.Verify(m => m.SetMaxSpectatorsAsync(MaxSpectators)); + } +}