From 014257996adc6b4f81059146f4baebd3c89115ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franti=C5=A1ek=20Boh=C3=A1=C4=8Dek?= Date: Tue, 1 Mar 2022 20:10:25 +0100 Subject: [PATCH] feat(tests): add tests for command processor, walk command handler, stateful injector --- .../Commands/CommandProcessorTests.cs | 515 ++++++++++++++++++ .../Walking/WalkCommandHandlerTests.cs | 213 ++++++++ .../Fakes/Commands/Events/CommandEvent.cs | 54 ++ .../Commands/Events/ErrorCommandEvent.cs | 38 ++ .../Commands/Events/SuccessfulCommandEvent.cs | 41 ++ .../Fakes/Commands/FakeCommand.cs | 11 + .../Fakes/Commands/FakeCommandHandler.cs | 33 ++ .../Fakes/FakeEmptyNostaleClient.cs | 55 ++ .../NosSmooth.Core.Tests/Fakes/FakeEntity.cs | 16 + Tests/NosSmooth.Core.Tests/Fakes/FakeError.cs | 16 + .../Fakes/FakeNostaleClient.cs | 66 +++ .../Fakes/Packets/FakePacket.cs | 15 + .../Fakes/Packets/FakePacketHandler.cs | 37 ++ Tests/NosSmooth.Core.Tests/IsExternalInit.cs | 16 + .../NosSmooth.Core.Tests.csproj | 1 + .../Stateful/StatefulInjectorTests.cs | 171 ++++++ 16 files changed, 1298 insertions(+) create mode 100644 Tests/NosSmooth.Core.Tests/Commands/CommandProcessorTests.cs create mode 100644 Tests/NosSmooth.Core.Tests/Commands/Walking/WalkCommandHandlerTests.cs create mode 100644 Tests/NosSmooth.Core.Tests/Fakes/Commands/Events/CommandEvent.cs create mode 100644 Tests/NosSmooth.Core.Tests/Fakes/Commands/Events/ErrorCommandEvent.cs create mode 100644 Tests/NosSmooth.Core.Tests/Fakes/Commands/Events/SuccessfulCommandEvent.cs create mode 100644 Tests/NosSmooth.Core.Tests/Fakes/Commands/FakeCommand.cs create mode 100644 Tests/NosSmooth.Core.Tests/Fakes/Commands/FakeCommandHandler.cs create mode 100644 Tests/NosSmooth.Core.Tests/Fakes/FakeEmptyNostaleClient.cs create mode 100644 Tests/NosSmooth.Core.Tests/Fakes/FakeEntity.cs create mode 100644 Tests/NosSmooth.Core.Tests/Fakes/FakeError.cs create mode 100644 Tests/NosSmooth.Core.Tests/Fakes/FakeNostaleClient.cs create mode 100644 Tests/NosSmooth.Core.Tests/Fakes/Packets/FakePacket.cs create mode 100644 Tests/NosSmooth.Core.Tests/Fakes/Packets/FakePacketHandler.cs create mode 100644 Tests/NosSmooth.Core.Tests/IsExternalInit.cs create mode 100644 Tests/NosSmooth.Core.Tests/Stateful/StatefulInjectorTests.cs diff --git a/Tests/NosSmooth.Core.Tests/Commands/CommandProcessorTests.cs b/Tests/NosSmooth.Core.Tests/Commands/CommandProcessorTests.cs new file mode 100644 index 0000000..66e71f6 --- /dev/null +++ b/Tests/NosSmooth.Core.Tests/Commands/CommandProcessorTests.cs @@ -0,0 +1,515 @@ +// +// CommandProcessorTests.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using NosSmooth.Core.Commands; +using NosSmooth.Core.Errors; +using NosSmooth.Core.Extensions; +using NosSmooth.Core.Tests.Fakes; +using NosSmooth.Core.Tests.Fakes.Commands; +using NosSmooth.Core.Tests.Fakes.Commands.Events; +using Remora.Results; +using Xunit; + +namespace NosSmooth.Core.Tests.Commands; + +/// +/// Test for . +/// +public class CommandProcessorTests +{ + /// + /// Tests that unknown not registered command should return a . + /// + /// A representing the asynchronous operation. + [Fact] + public async Task ProcessCommand_UnknownCommand_ShouldReturnError() + { + var provider = new ServiceCollection() + .AddSingleton() + .BuildServiceProvider(); + + var processResult = await provider.GetRequiredService().ProcessCommand + (new FakeEmptyNostaleClient(), new FakeCommand("asdf")); + Assert.False(processResult.IsSuccess); + Assert.IsType(processResult.Error); + } + + /// + /// Tests that known command has its handler called. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task ProcessCommand_KnownCommand_ShouldCallHandler() + { + bool called = false; + var fakeCommand = new FakeCommand("asdf"); + var provider = new ServiceCollection() + .AddSingleton() + .AddScoped, FakeCommandHandler> + ( + _ => new FakeCommandHandler + ( + fc => + { + Assert.Equal(fakeCommand, fc); + called = true; + return Result.FromSuccess(); + } + ) + ) + .BuildServiceProvider(); + + var processResult = await provider.GetRequiredService().ProcessCommand + (new FakeEmptyNostaleClient(), fakeCommand); + Assert.True(processResult.IsSuccess); + Assert.True(called); + } + + /// + /// Tests that if there are pre events they will be called. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task ProcessCommand_HavingPreEvents_ShouldCallPreEvents() + { + bool called = false; + var fakeCommand = new FakeCommand("asdf"); + var provider = new ServiceCollection() + .AddSingleton() + .AddPreCommandExecutionEvent() + .AddScoped + ( + _ => new CommandEvent + ( + c => + { + Assert.Equal(fakeCommand, c); + called = true; + return Result.FromSuccess(); + }, + (_, _) => throw new NotImplementedException() + ) + ) + .AddScoped, FakeCommandHandler> + ( + _ => new FakeCommandHandler + ( + fc => Result.FromSuccess() + ) + ) + .BuildServiceProvider(); + + var processResult = await provider.GetRequiredService().ProcessCommand + (new FakeEmptyNostaleClient(), fakeCommand); + Assert.True(processResult.IsSuccess); + Assert.True(called); + } + + /// + /// Tests that if there are pre events that return an error, the handler of the command won't be called. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task ProcessCommand_HavingErrorfulPreEvents_ShouldNotCallHandler() + { + bool called = false; + var fakeCommand = new FakeCommand("asdf"); + var provider = new ServiceCollection() + .AddSingleton() + .AddPreCommandExecutionEvent() + .AddScoped, FakeCommandHandler> + ( + _ => new FakeCommandHandler + ( + fc => + { + called = true; + return Result.FromSuccess(); + } + ) + ) + .BuildServiceProvider(); + + var processResult = await provider.GetRequiredService().ProcessCommand + (new FakeEmptyNostaleClient(), fakeCommand); + Assert.False(processResult.IsSuccess); + Assert.False(called); + } + + /// + /// Tests that if there are pre events that return successful result, the handler should be called. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task ProcessCommand_HavingSuccessfulPreEvents_ShouldCallHandler() + { + bool called = false; + var fakeCommand = new FakeCommand("asdf"); + var provider = new ServiceCollection() + .AddSingleton() + .AddPreCommandExecutionEvent() + .AddScoped, FakeCommandHandler> + ( + _ => new FakeCommandHandler + ( + fc => + { + Assert.Equal(fakeCommand, fc); + called = true; + return Result.FromSuccess(); + } + ) + ) + .BuildServiceProvider(); + + var processResult = await provider.GetRequiredService().ProcessCommand + (new FakeEmptyNostaleClient(), fakeCommand); + Assert.True(processResult.IsSuccess); + Assert.True(called); + } + + /// + /// Tests if there are post events they will be called. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task ProcessCommand_HavingPostEvents_ShouldCallPostEvents() + { + bool called = false; + var fakeCommand = new FakeCommand("asdf"); + var provider = new ServiceCollection() + .AddSingleton() + .AddScoped + ( + _ => new CommandEvent + ( + _ => throw new NotImplementedException(), + (fc, res) => + { + Assert.Equal(fakeCommand, fc); + Assert.True(res.IsSuccess); + called = true; + return Result.FromSuccess(); + } + ) + ) + .AddScoped, FakeCommandHandler> + ( + _ => new FakeCommandHandler + ( + fc => { return Result.FromSuccess(); } + ) + ) + .BuildServiceProvider(); + + var processResult = await provider.GetRequiredService().ProcessCommand + (new FakeEmptyNostaleClient(), fakeCommand); + Assert.True(processResult.IsSuccess); + Assert.True(called); + } + + /// + /// Tests if there are post events, the successful result from the handler should be passed to them. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task ProcessCommand_HavingPostEvents_ShouldPassSuccessfulResultToPostEvents() + { + bool called = false; + var fakeCommand = new FakeCommand("asdf"); + var provider = new ServiceCollection() + .AddSingleton() + .AddScoped + ( + _ => new CommandEvent + ( + _ => throw new NotImplementedException(), + (fc, res) => + { + Assert.Equal(fakeCommand, fc); + Assert.True(res.IsSuccess); + called = true; + return Result.FromSuccess(); + } + ) + ) + .AddScoped, FakeCommandHandler> + ( + _ => new FakeCommandHandler + ( + fc => { return Result.FromSuccess(); } + ) + ) + .BuildServiceProvider(); + + var processResult = await provider.GetRequiredService().ProcessCommand + (new FakeEmptyNostaleClient(), fakeCommand); + Assert.True(processResult.IsSuccess); + Assert.True(called); + } + + /// + /// Tests if there are post events, the error from the handler should be passed to them. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task ProcessCommand_HavingPostEvents_ShouldPassErrorfulResultToPostEvents() + { + bool called = false; + var fakeCommand = new FakeCommand("asdf"); + var provider = new ServiceCollection() + .AddSingleton() + .AddScoped + ( + _ => new CommandEvent + ( + _ => throw new NotImplementedException(), + (fc, res) => + { + Assert.Equal(fakeCommand, fc); + Assert.False(res.IsSuccess); + Assert.IsType(res.Error); + called = true; + return Result.FromSuccess(); + } + ) + ) + .AddScoped, FakeCommandHandler> + ( + _ => new FakeCommandHandler + ( + fc => { return new FakeError("Error"); } + ) + ) + .BuildServiceProvider(); + + var processResult = await provider.GetRequiredService().ProcessCommand + (new FakeEmptyNostaleClient(), fakeCommand); + Assert.False(processResult.IsSuccess); + Assert.True(called); + } + + /// + /// Tests that error from post events is returned. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task ProcessCommand_HavingErrorfulPostEvents_ShouldReturnPostExecutionError() + { + var fakeCommand = new FakeCommand("asdf"); + var error = new FakeError("Error"); + var provider = new ServiceCollection() + .AddSingleton() + .AddScoped + ( + _ => new CommandEvent + ( + _ => throw new NotImplementedException(), + (fc, _) => + { + Assert.Equal(fakeCommand, fc); + return error; + } + ) + ) + .AddScoped, FakeCommandHandler> + ( + _ => new FakeCommandHandler + ( + fc => Result.FromSuccess() + ) + ) + .BuildServiceProvider(); + + var processResult = await provider.GetRequiredService().ProcessCommand + (new FakeEmptyNostaleClient(), fakeCommand); + Assert.False(processResult.IsSuccess); + Assert.Equal(error, processResult.Error); + } + + /// + /// Tests that error from pre events is returned. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task ProcessCommand_HavingErrorfulPreEvents_ShouldReturnPreExecutionError() + { + var fakeCommand = new FakeCommand("asdf"); + var error = new FakeError("Error"); + var provider = new ServiceCollection() + .AddSingleton() + .AddScoped + ( + _ => new CommandEvent + ( + (fc) => + { + Assert.Equal(fakeCommand, fc); + return error; + }, + (_, _) => throw new NotImplementedException() + ) + ) + .AddScoped, FakeCommandHandler> + ( + _ => new FakeCommandHandler + ( + fc => Result.FromSuccess() + ) + ) + .BuildServiceProvider(); + + var processResult = await provider.GetRequiredService().ProcessCommand + (new FakeEmptyNostaleClient(), fakeCommand); + Assert.False(processResult.IsSuccess); + Assert.Equal(error, processResult.Error); + } + + /// + /// Tests that error from post event and handler is returned. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task ProcessCommand_HavingErrorfulPostEventsAndHandler_ShouldReturnHandlerAndPostExecutionError() + { + var fakeCommand = new FakeCommand("asdf"); + var error1 = new FakeError(); + var error2 = new FakeError(); + var provider = new ServiceCollection() + .AddSingleton() + .AddScoped + ( + _ => new CommandEvent + ( + _ => throw new NotImplementedException(), + (fc, _) => + { + Assert.Equal(fakeCommand, fc); + return error1; + } + ) + ) + .AddScoped, FakeCommandHandler> + ( + _ => new FakeCommandHandler + ( + fc => error2 + ) + ) + .BuildServiceProvider(); + + var processResult = await provider.GetRequiredService().ProcessCommand + (new FakeEmptyNostaleClient(), fakeCommand); + Assert.False(processResult.IsSuccess); + Assert.IsType(processResult.Error); + var aggregateError = processResult.Error as AggregateError; + Assert.NotNull(aggregateError); + if (aggregateError is not null) + { + Assert.True(aggregateError.Errors.Any(x => x.Error == error1)); + + Assert.True(aggregateError.Errors.Any(x => x.Error == error2)); + Assert.Equal(2, aggregateError.Errors.Count); + } + } + + /// + /// Tests that exceptions are handled. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task ProcessCommand_HavingExceptionInHandler_ShouldNotThrow() + { + var fakeCommand = new FakeCommand("asdf"); + var provider = new ServiceCollection() + .AddSingleton() + .AddScoped + ( + _ => new CommandEvent + ( + _ => throw new Exception(), + (_, _) => throw new Exception() + ) + ) + .AddScoped, FakeCommandHandler> + ( + _ => new FakeCommandHandler + ( + fc => throw new Exception() + ) + ) + .BuildServiceProvider(); + + await provider.GetRequiredService().ProcessCommand + (new FakeEmptyNostaleClient(), fakeCommand); + } + + /// + /// Tests that exceptions are handled. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task ProcessCommand_HavingExceptionInPreEvent_ShouldNotThrow() + { + var fakeCommand = new FakeCommand("asdf"); + var provider = new ServiceCollection() + .AddSingleton() + .AddScoped + ( + _ => new CommandEvent + ( + _ => throw new Exception(), + (_, _) => throw new Exception() + ) + ) + .AddScoped, FakeCommandHandler> + ( + _ => new FakeCommandHandler + ( + fc => throw new Exception() + ) + ) + .BuildServiceProvider(); + + await provider.GetRequiredService().ProcessCommand + (new FakeEmptyNostaleClient(), fakeCommand); + } + + /// + /// Tests that exceptions are handled. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task ProcessCommand_HavingExceptionInPostEvent_ShouldNotThrow() + { + var fakeCommand = new FakeCommand("asdf"); + var provider = new ServiceCollection() + .AddSingleton() + .AddScoped + ( + _ => new CommandEvent + ( + _ => Result.FromSuccess(), + (_, _) => throw new Exception() + ) + ) + .AddScoped, FakeCommandHandler> + ( + _ => new FakeCommandHandler + ( + fc => Result.FromSuccess() + ) + ) + .BuildServiceProvider(); + + await provider.GetRequiredService().ProcessCommand + (new FakeEmptyNostaleClient(), fakeCommand); + } +} \ No newline at end of file diff --git a/Tests/NosSmooth.Core.Tests/Commands/Walking/WalkCommandHandlerTests.cs b/Tests/NosSmooth.Core.Tests/Commands/Walking/WalkCommandHandlerTests.cs new file mode 100644 index 0000000..e0f2403 --- /dev/null +++ b/Tests/NosSmooth.Core.Tests/Commands/Walking/WalkCommandHandlerTests.cs @@ -0,0 +1,213 @@ +// +// WalkCommandHandlerTests.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.ComponentModel; +using System.Linq; +using System.Threading.Tasks; +using NosSmooth.Core.Commands.Control; +using NosSmooth.Core.Commands.Walking; +using NosSmooth.Core.Tests.Fakes; +using Remora.Results; +using Xunit; + +namespace NosSmooth.Core.Tests.Commands.Walking; + +/// +/// Tests handling walk command. +/// +public class WalkCommandHandlerTests +{ + /// + /// Tests that pet and player walk commands will be called. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task Handle_CallsPetAndPlayerWalkCommands() + { + var calledPetWalk = false; + var calledPlayerWalk = false; + var command = new WalkCommand(0, 0, new[] { 1, 2 }, 0); + var walkHandler = new WalkCommandHandler + ( + new FakeNostaleClient + ( + (c, _) => + { + if (c is PlayerWalkCommand) + { + calledPlayerWalk = true; + } + if (c is PetWalkCommand) + { + calledPetWalk = true; + } + return Result.FromSuccess(); + } + ) + ); + + await walkHandler.HandleCommand(command); + Assert.True(calledPetWalk); + Assert.True(calledPlayerWalk); + } + + /// + /// Tests that handling will preserve the properties. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task Handle_PreservesTakeHandlerCommandProperties() + { + var command = new WalkCommand + ( + 0, + 0, + new[] { 2, 5, 7, 9 }, + 0, + true, + false, + false + ); + var walkHandler = new WalkCommandHandler + ( + new FakeNostaleClient + ( + (c, _) => + { + if (c is ITakeControlCommand takeControl) + { + Assert.Equal(command.AllowUserCancel, takeControl.AllowUserCancel); + Assert.Equal(command.WaitForCancellation, takeControl.WaitForCancellation); + Assert.Equal(command.CancelOnMapChange, takeControl.CancelOnMapChange); + Assert.Equal(command.CanBeCancelledByAnother, takeControl.CanBeCancelledByAnother); + } + return Result.FromSuccess(); + } + ) + ); + + await walkHandler.HandleCommand(command); + } + + /// + /// Tests that handler preserves the position to player walk command. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task Handle_PreservesPlayerWalkPosition() + { + var command = new WalkCommand + ( + 10, + 15, + Array.Empty(), + 0, + true, + false, + false + ); + var walkHandler = new WalkCommandHandler + ( + new FakeNostaleClient + ( + (c, _) => + { + if (c is PlayerWalkCommand playerWalkCommand) + { + Assert.Equal(command.TargetX, playerWalkCommand.TargetX); + Assert.Equal(command.TargetY, playerWalkCommand.TargetY); + Assert.Equal(command.ReturnDistanceTolerance, playerWalkCommand.ReturnDistanceTolerance); + } + return Result.FromSuccess(); + } + ) + ); + + await walkHandler.HandleCommand(command); + } + + /// + /// Tests that the handler will be called for every pet. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task Handle_WithPets_IsCalledForEveryPet() + { + var calledCount = 0; + var command = new WalkCommand + ( + 10, + 15, + new[] { 1, 2, 5, 7, 8 }, + 0, + true, + false, + false + ); + var walkHandler = new WalkCommandHandler + ( + new FakeNostaleClient + ( + (c, _) => + { + if (c is PetWalkCommand petWalkCommand) + { + if (command.PetSelectors.Contains(petWalkCommand.PetSelector)) + { + calledCount++; + } + else + { + throw new ArgumentException("Pet command was called for non-selected pet."); + } + } + return Result.FromSuccess(); + } + ) + ); + + await walkHandler.HandleCommand(command); + Assert.Equal(command.PetSelectors.Length, calledCount); + } + + /// + /// Tests that pet commands will have correct position set. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task Handle_WithPets_UsesNearbyPositionForPetCommands() + { + var command = new WalkCommand + ( + 10, + 15, + new[] { 1, 2, 5, 7, 8 }, + 0, + true, + false, + false + ); + var walkHandler = new WalkCommandHandler + ( + new FakeNostaleClient + ( + (c, _) => + { + if (c is PetWalkCommand petWalkCommand) + { + Assert.True((command.TargetX - petWalkCommand.TargetX) <= 3); + Assert.True((command.TargetY - petWalkCommand.TargetY) <= 3); + Assert.Equal(command.ReturnDistanceTolerance, petWalkCommand.ReturnDistanceTolerance); + } + return Result.FromSuccess(); + } + ) + ); + + await walkHandler.HandleCommand(command); + } +} \ No newline at end of file diff --git a/Tests/NosSmooth.Core.Tests/Fakes/Commands/Events/CommandEvent.cs b/Tests/NosSmooth.Core.Tests/Fakes/Commands/Events/CommandEvent.cs new file mode 100644 index 0000000..34bc506 --- /dev/null +++ b/Tests/NosSmooth.Core.Tests/Fakes/Commands/Events/CommandEvent.cs @@ -0,0 +1,54 @@ +// +// CommandEvent.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Threading; +using System.Threading.Tasks; +using NosSmooth.Core.Client; +using NosSmooth.Core.Commands; +using Remora.Results; + +namespace NosSmooth.Core.Tests.Fakes.Commands.Events; + +/// +public class CommandEvent : IPreCommandExecutionEvent, IPostCommandExecutionEvent + where TInCommand : ICommand +{ + private readonly Func _preExecutionHandler; + private readonly Func _postExecutionHandler; + + /// + /// Initializes a new instance of the class. + /// + /// The pre execution handler. + /// The post execution handler. + public CommandEvent(Func preExecutionHandler, Func postExecutionHandler) + { + _preExecutionHandler = preExecutionHandler; + _postExecutionHandler = postExecutionHandler; + } + + /// + public Task ExecuteBeforeCommandAsync + (INostaleClient client, TCommand command, CancellationToken ct = default) + where TCommand : ICommand + { + return Task.FromResult(_preExecutionHandler((TInCommand)(object)command)); + } + + /// + public Task ExecuteAfterCommandAsync + ( + INostaleClient client, + TCommand command, + Result handlerResult, + CancellationToken ct = default + ) + where TCommand : ICommand + { + return Task.FromResult(_postExecutionHandler((TInCommand)(object)command, handlerResult)); + } +} \ No newline at end of file diff --git a/Tests/NosSmooth.Core.Tests/Fakes/Commands/Events/ErrorCommandEvent.cs b/Tests/NosSmooth.Core.Tests/Fakes/Commands/Events/ErrorCommandEvent.cs new file mode 100644 index 0000000..b5f6899 --- /dev/null +++ b/Tests/NosSmooth.Core.Tests/Fakes/Commands/Events/ErrorCommandEvent.cs @@ -0,0 +1,38 @@ +// +// ErrorCommandEvent.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Threading; +using System.Threading.Tasks; +using NosSmooth.Core.Client; +using NosSmooth.Core.Commands; +using Remora.Results; + +namespace NosSmooth.Core.Tests.Fakes.Commands.Events; + +/// +public class ErrorCommandEvent : IPreCommandExecutionEvent, IPostCommandExecutionEvent +{ + /// + public Task ExecuteBeforeCommandAsync + (INostaleClient client, TCommand command, CancellationToken ct = default) + where TCommand : ICommand + { + return Task.FromResult(new FakeError("Error pre command execution")); + } + + /// + public Task ExecuteAfterCommandAsync + ( + INostaleClient client, + TCommand command, + Result handlerResult, + CancellationToken ct = default + ) + where TCommand : ICommand + { + return Task.FromResult(new FakeError("Erro post command execution")); + } +} \ No newline at end of file diff --git a/Tests/NosSmooth.Core.Tests/Fakes/Commands/Events/SuccessfulCommandEvent.cs b/Tests/NosSmooth.Core.Tests/Fakes/Commands/Events/SuccessfulCommandEvent.cs new file mode 100644 index 0000000..4301067 --- /dev/null +++ b/Tests/NosSmooth.Core.Tests/Fakes/Commands/Events/SuccessfulCommandEvent.cs @@ -0,0 +1,41 @@ +// +// SuccessfulCommandEvent.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using NosSmooth.Core.Client; +using NosSmooth.Core.Commands; +using NosSmooth.Core.Packets; +using NosSmooth.Packets; +using Remora.Results; + +namespace NosSmooth.Core.Tests.Fakes.Commands.Events; + +/// +public class SuccessfulCommandEvent : IPreCommandExecutionEvent, IPostCommandExecutionEvent +{ + /// + public Task ExecuteBeforeCommandAsync + (INostaleClient client, TCommand command, CancellationToken ct = default) + where TCommand : ICommand + { + return Task.FromResult(Result.FromSuccess()); + } + + /// + public Task ExecuteAfterCommandAsync + ( + INostaleClient client, + TCommand command, + Result handlerResult, + CancellationToken ct = default + ) + where TCommand : ICommand + { + return Task.FromResult(Result.FromSuccess()); + } +} \ No newline at end of file diff --git a/Tests/NosSmooth.Core.Tests/Fakes/Commands/FakeCommand.cs b/Tests/NosSmooth.Core.Tests/Fakes/Commands/FakeCommand.cs new file mode 100644 index 0000000..be1a028 --- /dev/null +++ b/Tests/NosSmooth.Core.Tests/Fakes/Commands/FakeCommand.cs @@ -0,0 +1,11 @@ +// +// FakeCommand.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using NosSmooth.Core.Commands; + +namespace NosSmooth.Core.Tests.Fakes.Commands; + +public record FakeCommand(string Input) : ICommand; \ No newline at end of file diff --git a/Tests/NosSmooth.Core.Tests/Fakes/Commands/FakeCommandHandler.cs b/Tests/NosSmooth.Core.Tests/Fakes/Commands/FakeCommandHandler.cs new file mode 100644 index 0000000..b1f6c1f --- /dev/null +++ b/Tests/NosSmooth.Core.Tests/Fakes/Commands/FakeCommandHandler.cs @@ -0,0 +1,33 @@ +// +// FakeCommandHandler.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Threading; +using System.Threading.Tasks; +using NosSmooth.Core.Commands; +using Remora.Results; + +namespace NosSmooth.Core.Tests.Fakes.Commands; + +/// +public class FakeCommandHandler : ICommandHandler +{ + private readonly Func _handler; + + /// + /// Initializes a new instance of the class. + /// + /// The handler. + public FakeCommandHandler(Func handler) + { + _handler = handler; + + } + + /// + public Task HandleCommand(FakeCommand command, CancellationToken ct = default) + => Task.FromResult(_handler(command)); +} \ No newline at end of file diff --git a/Tests/NosSmooth.Core.Tests/Fakes/FakeEmptyNostaleClient.cs b/Tests/NosSmooth.Core.Tests/Fakes/FakeEmptyNostaleClient.cs new file mode 100644 index 0000000..7cc2e82 --- /dev/null +++ b/Tests/NosSmooth.Core.Tests/Fakes/FakeEmptyNostaleClient.cs @@ -0,0 +1,55 @@ +// +// FakeEmptyNostaleClient.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Threading; +using System.Threading.Tasks; +using NosSmooth.Core.Client; +using NosSmooth.Core.Commands; +using NosSmooth.Packets; +using Remora.Results; + +namespace NosSmooth.Core.Tests.Fakes; + +/// +public class FakeEmptyNostaleClient : INostaleClient +{ + /// + public Task RunAsync(CancellationToken stopRequested = default) + { + throw new NotImplementedException(); + } + + /// + public Task SendPacketAsync(IPacket packet, CancellationToken ct = default) + { + throw new NotImplementedException(); + } + + /// + public Task SendPacketAsync(string packetString, CancellationToken ct = default) + { + throw new NotImplementedException(); + } + + /// + public Task ReceivePacketAsync(string packetString, CancellationToken ct = default) + { + throw new NotImplementedException(); + } + + /// + public Task ReceivePacketAsync(IPacket packet, CancellationToken ct = default) + { + throw new NotImplementedException(); + } + + /// + public Task SendCommandAsync(ICommand command, CancellationToken ct = default) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/Tests/NosSmooth.Core.Tests/Fakes/FakeEntity.cs b/Tests/NosSmooth.Core.Tests/Fakes/FakeEntity.cs new file mode 100644 index 0000000..d4f6d40 --- /dev/null +++ b/Tests/NosSmooth.Core.Tests/Fakes/FakeEntity.cs @@ -0,0 +1,16 @@ +// +// FakeEntity.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using NosSmooth.Core.Stateful; + +namespace NosSmooth.Core.Tests.Fakes; + +/// +/// A fake stateful entity. +/// +public class FakeEntity : IStatefulEntity +{ +} \ No newline at end of file diff --git a/Tests/NosSmooth.Core.Tests/Fakes/FakeError.cs b/Tests/NosSmooth.Core.Tests/Fakes/FakeError.cs new file mode 100644 index 0000000..2181400 --- /dev/null +++ b/Tests/NosSmooth.Core.Tests/Fakes/FakeError.cs @@ -0,0 +1,16 @@ +// +// FakeError.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Text; +using Remora.Results; + +namespace NosSmooth.Core.Tests.Fakes; + +/// +/// A fake error. +/// +/// The text. +public record FakeError(string Text = "Fake") : ResultError($"Fake error: {Text}"); \ No newline at end of file diff --git a/Tests/NosSmooth.Core.Tests/Fakes/FakeNostaleClient.cs b/Tests/NosSmooth.Core.Tests/Fakes/FakeNostaleClient.cs new file mode 100644 index 0000000..d750cde --- /dev/null +++ b/Tests/NosSmooth.Core.Tests/Fakes/FakeNostaleClient.cs @@ -0,0 +1,66 @@ +// +// FakeNostaleClient.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Threading; +using System.Threading.Tasks; +using NosSmooth.Core.Client; +using NosSmooth.Core.Commands; +using NosSmooth.Packets; +using Remora.Results; + +namespace NosSmooth.Core.Tests.Fakes; + +/// +/// Fake NosTale client. +/// +public class FakeNostaleClient : INostaleClient +{ + private readonly Func _handleCommand; + + /// + /// Initializes a new instance of the class. + /// + /// The handler for . + public FakeNostaleClient(Func handleCommand) + { + _handleCommand = handleCommand; + } + + /// + public Task RunAsync(CancellationToken stopRequested = default) + { + throw new System.NotImplementedException(); + } + + /// + public Task SendPacketAsync(IPacket packet, CancellationToken ct = default) + { + throw new System.NotImplementedException(); + } + + /// + public Task SendPacketAsync(string packetString, CancellationToken ct = default) + { + throw new System.NotImplementedException(); + } + + /// + public Task ReceivePacketAsync(string packetString, CancellationToken ct = default) + { + throw new System.NotImplementedException(); + } + + /// + public Task ReceivePacketAsync(IPacket packet, CancellationToken ct = default) + { + throw new System.NotImplementedException(); + } + + /// + public Task SendCommandAsync(ICommand command, CancellationToken ct = default) + => Task.FromResult(_handleCommand(command, ct)); +} \ No newline at end of file diff --git a/Tests/NosSmooth.Core.Tests/Fakes/Packets/FakePacket.cs b/Tests/NosSmooth.Core.Tests/Fakes/Packets/FakePacket.cs new file mode 100644 index 0000000..c9354ed --- /dev/null +++ b/Tests/NosSmooth.Core.Tests/Fakes/Packets/FakePacket.cs @@ -0,0 +1,15 @@ +// +// FakePacket.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using NosSmooth.Packets; + +namespace NosSmooth.Core.Tests.Fakes.Packets; + +/// +/// A fake packet. +/// +/// The input. +public record FakePacket(string Input) : IPacket; \ No newline at end of file diff --git a/Tests/NosSmooth.Core.Tests/Fakes/Packets/FakePacketHandler.cs b/Tests/NosSmooth.Core.Tests/Fakes/Packets/FakePacketHandler.cs new file mode 100644 index 0000000..6c1c04e --- /dev/null +++ b/Tests/NosSmooth.Core.Tests/Fakes/Packets/FakePacketHandler.cs @@ -0,0 +1,37 @@ +// +// FakePacketHandler.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Threading; +using System.Threading.Tasks; +using NosSmooth.Core.Packets; +using NosSmooth.Packets; +using Remora.Results; + +namespace NosSmooth.Core.Tests.Fakes.Packets; + +/// +/// Fake Responder of a packet. +/// +/// The packet to respond to. +public class FakePacketResponder : IPacketResponder + where TPacket : IPacket +{ + private readonly Func, Result> _handler; + + /// + /// Initializes a new instance of the class. + /// + /// The function respond handler. + public FakePacketResponder(Func, Result> handler) + { + _handler = handler; + } + + /// + public Task Respond(PacketEventArgs packetArgs, CancellationToken ct = default) + => Task.FromResult(_handler(packetArgs)); +} \ No newline at end of file diff --git a/Tests/NosSmooth.Core.Tests/IsExternalInit.cs b/Tests/NosSmooth.Core.Tests/IsExternalInit.cs new file mode 100644 index 0000000..e7be764 --- /dev/null +++ b/Tests/NosSmooth.Core.Tests/IsExternalInit.cs @@ -0,0 +1,16 @@ +// +// IsExternalInit.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +// ReSharper disable once CheckNamespace +namespace System.Runtime.CompilerServices +{ + /// + /// Dummy. + /// + public class IsExternalInit + { + } +} \ No newline at end of file diff --git a/Tests/NosSmooth.Core.Tests/NosSmooth.Core.Tests.csproj b/Tests/NosSmooth.Core.Tests/NosSmooth.Core.Tests.csproj index e02138b..f02be11 100644 --- a/Tests/NosSmooth.Core.Tests/NosSmooth.Core.Tests.csproj +++ b/Tests/NosSmooth.Core.Tests/NosSmooth.Core.Tests.csproj @@ -8,6 +8,7 @@ + diff --git a/Tests/NosSmooth.Core.Tests/Stateful/StatefulInjectorTests.cs b/Tests/NosSmooth.Core.Tests/Stateful/StatefulInjectorTests.cs new file mode 100644 index 0000000..39bd96c --- /dev/null +++ b/Tests/NosSmooth.Core.Tests/Stateful/StatefulInjectorTests.cs @@ -0,0 +1,171 @@ +// +// StatefulInjectorTests.cs +// +// Copyright (c) František Boháček. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using NosSmooth.Core.Client; +using NosSmooth.Core.Commands; +using NosSmooth.Core.Extensions; +using NosSmooth.Core.Packets; +using NosSmooth.Core.Stateful; +using NosSmooth.Core.Tests.Fakes; +using NosSmooth.Core.Tests.Fakes.Commands; +using NosSmooth.Core.Tests.Fakes.Packets; +using NosSmooth.Core.Tests.Packets; +using NosSmooth.Packets.Server.Maps; +using Remora.Results; +using Xunit; + +namespace NosSmooth.Core.Tests.Stateful; + +/// +/// Tests injecting stateful entities. +/// +public class StatefulInjectorTests +{ + /// + /// Tests that get entity returns the same instance for the same INostaleClient. + /// + [Fact] + public void GetEntity_ReturnsSameEntityForSameClient() + { + var services = new ServiceCollection() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .BuildServiceProvider(); + var injector = new StatefulInjector(new StatefulRepository()); + var client = services.GetRequiredService(); + injector.Client = client; + var entity = injector.GetEntity(services, typeof(FakeEntity)); + var entity2 = injector.GetEntity(services, typeof(FakeEntity)); + Assert.Equal(entity, entity2); + } + + /// + /// Tests that get entity returns different instance for different INostaleClient. + /// + [Fact] + public void GetEntity_ReturnsDifferentEntityForDifferentClient() + { + var services = new ServiceCollection() + .AddSingleton() + .AddSingleton() + .BuildServiceProvider(); + var repository = new StatefulRepository(); + var injector = new StatefulInjector(repository); + var injector2 = new StatefulInjector(repository); + var client = new FakeEmptyNostaleClient(); + var client2 = new FakeEmptyNostaleClient(); + injector.Client = client; + injector2.Client = client2; + var entity = injector.GetEntity(services, typeof(FakeEntity)); + var entity2 = injector2.GetEntity(services, typeof(FakeEntity)); + Assert.NotEqual(entity, entity2); + } + + /// + /// Tests that extension methods for service provider work correctly with injectable entities, correctly adding pre event to command processor. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task CommandProcessor_PreEvent_InjectsClient() + { + var client1 = new FakeEmptyNostaleClient(); + var client2 = new FakeEmptyNostaleClient(); + FakeEntity? entity1 = null; + FakeEntity? entity2 = null; + var services = + new ServiceCollection() + .AddSingleton() + .AddStatefulInjector() + .AddStatefulEntity() + .AddSingleton() + .AddScoped> + (p => + { + var client = p.GetRequiredService(); + var entity = p.GetRequiredService(); + return new FakeCommandHandler((c) => + { + if (c.Input == "1") + { + Assert.Equal(client1, client); + entity1 = entity; + } + else + { + Assert.Equal(client2, client); + entity2 = entity; + } + return Result.FromSuccess(); + } + ); + } + ) + .BuildServiceProvider(); + + var processor = services.GetRequiredService(); + + Assert.True((await processor.ProcessCommand(client1, new FakeCommand("1"), default)).IsSuccess); + Assert.True((await processor.ProcessCommand(client2, new FakeCommand("2"), default)).IsSuccess); + Assert.NotNull(entity1); + Assert.NotNull(entity2); + Assert.NotEqual(entity1, entity2); + } + + /// + /// Tests that extension methods for service provider work correctly with injectable entities, correctly adding pre event to packet handler. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task PacketHandler_PreEvent_InjectsClient() + { + var client1 = new FakeEmptyNostaleClient(); + var client2 = new FakeEmptyNostaleClient(); + FakeEntity? entity1 = null; + FakeEntity? entity2 = null; + var services = + new ServiceCollection() + .AddSingleton() + .AddStatefulInjector() + .AddStatefulEntity() + .AddSingleton() + .AddSingleton() + .AddScoped> + (p => + { + var client = p.GetRequiredService(); + var entity = p.GetRequiredService(); + return new FakePacketResponder + ((c) => + { + if (c.Packet.Input == "1") + { + Assert.Equal(client1, client); + entity1 = entity; + } + else + { + Assert.Equal(client2, client); + entity2 = entity; + } + return Result.FromSuccess(); + } + ); + } + ) + .BuildServiceProvider(); + + var handler = services.GetRequiredService(); + + Assert.True((await handler.HandleReceivedPacketAsync(client1, new FakePacket("1"), "fake 1")).IsSuccess); + Assert.True((await handler.HandleReceivedPacketAsync(client2, new FakePacket("2"), "fake 2")).IsSuccess); + Assert.NotNull(entity1); + Assert.NotNull(entity2); + Assert.NotEqual(entity1, entity2); + } +} \ No newline at end of file -- 2.49.0