// // ContractTests.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.Contracts; using Remora.Results; using Shouldly; using Xunit; namespace NosSmooth.Core.Tests.Contracts; /// /// Tests basics of contract system. /// public class ContractTests { /// /// Tests that the contract is executed. /// /// A representing the asynchronous operation. [Fact] public async Task Test_GetsExecuted() { var contractor = new Contractor(); var mock = new MockClass(); var contract = new ContractBuilder(contractor, DefaultStates.None) .SetMoveAction(DefaultStates.None, mock.Setup, DefaultStates.Requested) .SetMoveFilter>(DefaultStates.Requested, DefaultStates.ResponseObtained) .SetFillData>(DefaultStates.ResponseObtained, d => d.Data) .Build(); contract.CurrentState.ShouldBe(DefaultStates.None); await contract.OnlyExecuteAsync(); contract.CurrentState.ShouldBe(DefaultStates.Requested); mock.Executed.ShouldBeTrue(); mock.ExecutedTimes.ShouldBe(1); } /// /// Tests that the contract response is obtained. /// /// A representing the asynchronous operation. [Fact] public async Task Test_ResponseObtained() { var contractor = new Contractor(); var mock = new MockClass(); var contract = new ContractBuilder(contractor, DefaultStates.None) .SetMoveAction(DefaultStates.None, mock.Setup, DefaultStates.Requested) .SetMoveFilter>(DefaultStates.Requested, DefaultStates.ResponseObtained) .SetFillData>(DefaultStates.ResponseObtained, d => d.Data) .Build(); await contract.OnlyExecuteAsync(); contract.Register(); await contractor.Update(new ContractData(5)); contract.CurrentState.ShouldBe(DefaultStates.ResponseObtained); contract.Data.ShouldBe(5); await contractor.Update(new ContractData(10)); contract.Data.ShouldBe(5); contract.Unregister(); mock.ExecutedTimes.ShouldBe(1); } /// /// Tests that the contract response is obtained. /// /// A representing the asynchronous operation. [Fact] public async Task Test_WaitFor() { var contractor = new Contractor(); var mock = new MockClass(); var contract = new ContractBuilder(contractor, DefaultStates.None) .SetMoveAction(DefaultStates.None, mock.Setup, DefaultStates.Requested) .SetMoveFilter>(DefaultStates.Requested, DefaultStates.ResponseObtained) .SetFillData>(DefaultStates.ResponseObtained, d => d.Data) .Build(); Task.Run ( async () => { await Task.Delay(500); await contractor.Update(new ContractData(15)); } ); var result = await contract.WaitForAsync(DefaultStates.ResponseObtained); result.IsSuccess.ShouldBeTrue(); result.Entity.ShouldBe(15); contract.IsRegistered.ShouldBeFalse(); // trust the contract for now. contract.CurrentState.ShouldBe(DefaultStates.ResponseObtained); mock.ExecutedTimes.ShouldBe(1); } /// /// Tests that the contract response is obtained. /// /// A representing the asynchronous operation. [Fact] public async Task Test_WaitForMoreStates() { var contractor = new Contractor(); var mock = new MockClass(); var contract = new ContractBuilder (contractor, ContractMultipleStates.None) .SetMoveAction(ContractMultipleStates.None, mock.Setup, ContractMultipleStates.Requested) .SetMoveFilter> (ContractMultipleStates.Requested, ContractMultipleStates.ResponseObtained) .SetFillData>(ContractMultipleStates.ResponseObtained, d => d.Data) .SetMoveFilter> (ContractMultipleStates.ResponseObtained, c => c.Data, ContractMultipleStates.AfterResponseObtained) .Build(); Task.Run ( async () => { await Task.Delay(500); await contractor.Update(new ContractData(15)); await Task.Delay(200); await contractor.Update(new ContractData(true)); } ); var result = await contract.WaitForAsync(ContractMultipleStates.AfterResponseObtained); result.IsSuccess.ShouldBeTrue(); result.Entity.ShouldBe(15); contract.IsRegistered.ShouldBeFalse(); // trust the contract for now. contract.CurrentState.ShouldBe(ContractMultipleStates.AfterResponseObtained); mock.ExecutedTimes.ShouldBe(1); } /// /// Tests that the contract response is obtained. /// /// A representing the asynchronous operation. [Fact] public async Task Test_MoreStatesFollowed() { var contractor = new Contractor(); var mock = new MockClass(); var contract = new ContractBuilder (contractor, ContractMultipleStates.None) .SetMoveAction(ContractMultipleStates.None, mock.Setup, ContractMultipleStates.Requested) .SetMoveFilter> (ContractMultipleStates.Requested, ContractMultipleStates.ResponseObtained) .SetFillData>(ContractMultipleStates.ResponseObtained, d => d.Data) .SetMoveFilter> (ContractMultipleStates.ResponseObtained, c => c.Data, ContractMultipleStates.AfterResponseObtained) .Build(); await contract.OnlyExecuteAsync(); contract.Register(); await contractor.Update(new ContractData(15)); await contractor.Update(new ContractData(true)); contract.Unregister(); var result = await contract.WaitForAsync(ContractMultipleStates.AfterResponseObtained); result.IsSuccess.ShouldBeTrue(); result.Entity.ShouldBe(15); contract.IsRegistered.ShouldBeFalse(); // trust the contract for now. contract.CurrentState.ShouldBe(ContractMultipleStates.AfterResponseObtained); mock.ExecutedTimes.ShouldBe(1); } /// /// Tests that the contract response is obtained. /// /// A representing the asynchronous operation. [Fact] public async Task Test_WaitForTimeout() { var contractor = new Contractor(); var mock = new MockClass(); var contract = new ContractBuilder (contractor, ContractMultipleStates.None) .SetMoveAction(ContractMultipleStates.None, mock.Setup, ContractMultipleStates.Requested) .SetMoveFilter> (ContractMultipleStates.Requested, ContractMultipleStates.ResponseObtained) .SetFillData>(ContractMultipleStates.ResponseObtained, d => d.Data) .SetTimeout ( ContractMultipleStates.ResponseObtained, TimeSpan.FromMilliseconds(100), ContractMultipleStates.AfterResponseObtained ) .Build(); Task.Run ( async () => { await Task.Delay(500); await contractor.Update(new ContractData(15)); } ); var result = await contract.WaitForAsync(ContractMultipleStates.AfterResponseObtained); result.IsSuccess.ShouldBeTrue(); result.Entity.ShouldBe(15); contract.IsRegistered.ShouldBeFalse(); // trust the contract for now. contract.CurrentState.ShouldBe(ContractMultipleStates.AfterResponseObtained); mock.ExecutedTimes.ShouldBe(1); } /// /// Tests that the contract response is obtained. /// /// A representing the asynchronous operation. [Fact] public async Task Test_MultipleContracts() { var contractor = new Contractor(); var mock = new MockClass(); var contract1 = new ContractBuilder (contractor, ContractMultipleStates.None) .SetMoveAction(ContractMultipleStates.None, mock.Setup, ContractMultipleStates.Requested) .SetMoveFilter> ( ContractMultipleStates.Requested, d => d.Data > 10 && d.Data < 20, ContractMultipleStates.ResponseObtained ) .SetFillData>(ContractMultipleStates.ResponseObtained, d => d.Data) .Build(); var contract2 = new ContractBuilder (contractor, ContractMultipleStates.None) .SetMoveAction(ContractMultipleStates.None, mock.Setup, ContractMultipleStates.Requested) .SetMoveFilter> (ContractMultipleStates.Requested, d => d.Data > 20, ContractMultipleStates.ResponseObtained) .SetFillData>(ContractMultipleStates.ResponseObtained, d => d.Data) .Build(); await contract1.OnlyExecuteAsync(); await contract2.OnlyExecuteAsync(); Task.Run ( async () => { await Task.Delay(500); await contractor.Update(new ContractData(15)); await Task.Delay(500); await contractor.Update(new ContractData(25)); } ); var results = await Task.WhenAll ( contract1.WaitForAsync(ContractMultipleStates.ResponseObtained), contract2.WaitForAsync(ContractMultipleStates.ResponseObtained) ); results[0].IsSuccess.ShouldBeTrue(); results[0].Entity.ShouldBe(15); results[1].IsSuccess.ShouldBeTrue(); results[1].Entity.ShouldBe(25); contract1.CurrentState.ShouldBe(ContractMultipleStates.ResponseObtained); contract2.CurrentState.ShouldBe(ContractMultipleStates.ResponseObtained); mock.ExecutedTimes.ShouldBe(2); } /// /// Tests that the contract response is obtained. /// /// A representing the asynchronous operation. [Fact] public async Task Test_ErrorsFired() { var contractor = new Contractor(); var mock = new MockClass(); var contract = new ContractBuilder (contractor, ContractMultipleStates.None) .SetMoveAction(ContractMultipleStates.None, mock.Setup, ContractMultipleStates.Requested) .SetMoveFilter> (ContractMultipleStates.Requested, ContractMultipleStates.ResponseObtained) .SetFillData>(ContractMultipleStates.ResponseObtained, d => d.Data) .SetError> ( ContractMultipleStates.Requested, d => { if (d.Data == 15) { return ContractError.Error1; } return null; } ) .Build(); Task.Run ( async () => { await Task.Delay(500); await contractor.Update(new ContractData(15)); } ); var result = await contract.WaitForAsync(ContractMultipleStates.AfterResponseObtained); result.IsSuccess.ShouldBeFalse(); result.Error.ShouldBeOfType>(); ((ContractError)result.Error).Error.ShouldBe(ContractError.Error1); contract.CurrentState.ShouldBe(ContractMultipleStates.Requested); contract.IsRegistered.ShouldBeFalse(); mock.ExecutedTimes.ShouldBe(1); } } /// /// A class for verifying setup was called. /// public class MockClass { /// /// Gets the number of times was called. /// public int ExecutedTimes { get; private set; } /// /// Gets whether was executed. /// public bool Executed { get; private set; } /// /// Sets Executed to true.. /// /// The data. should be null. /// The cancellation token used for cancelling the operation. /// A representing the asynchronous operation. public Task> Setup(object? data, CancellationToken ct) { if (data is not null) { throw new ArgumentException("Should be null.", nameof(data)); } Executed = true; ExecutedTimes++; return Task.FromResult(Result.FromSuccess(true)); } }