using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Junction.Domain; using Junction.Domain.Models; using Junction.Domain.Persistence; using Moq; using Xunit; namespace Junction.Tests.Unit { /// /// Shape/contract tests for using a Moq mock. /// No real database: these assert the async signatures and Result shapes only. /// public class MachineRepositoryContractTests { private static readonly OperationError SampleError = new OperationError("repo.error", "repository", "boom"); private static Machine SampleMachine(Guid id) => new Machine(id, "Mill", "mtconnect", null, TimeSpan.FromSeconds(2)); private static MachineSnapshot SampleSnapshot(Guid machineId) => new MachineSnapshot(machineId, DateTimeOffset.UtcNow, ConnectionState.Connected, null); [Fact] public async Task GetAllAsync_Ok_WrapsReadOnlyListOfMachines() { var machines = new List { SampleMachine(Guid.NewGuid()) }; var mock = new Mock(); mock.Setup(r => r.GetAllAsync(It.IsAny())) .ReturnsAsync(Result>.Ok(machines)); Result> result = await mock.Object.GetAllAsync(CancellationToken.None); Assert.True(result.IsSuccess); Assert.False(result.WasCancelled); Assert.Single(result.Value); } [Fact] public async Task GetAllAsync_Fail_HasErrorsNoValue() { var mock = new Mock(); mock.Setup(r => r.GetAllAsync(It.IsAny())) .ReturnsAsync(Result>.Fail(SampleError)); var result = await mock.Object.GetAllAsync(CancellationToken.None); Assert.False(result.IsSuccess); Assert.Single(result.Errors); Assert.Equal("repo.error", result.Errors[0].Code); } [Fact] public async Task GetByIdAsync_Ok_WrapsMachine() { var id = Guid.NewGuid(); var mock = new Mock(); mock.Setup(r => r.GetByIdAsync(id, It.IsAny())) .ReturnsAsync(Result.Ok(SampleMachine(id))); var result = await mock.Object.GetByIdAsync(id, CancellationToken.None); Assert.True(result.IsSuccess); Assert.Equal(id, result.Value.Id); } [Fact] public async Task GetByIdAsync_Fail_NotFoundShape() { var mock = new Mock(); mock.Setup(r => r.GetByIdAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(Result.Fail(SampleError)); var result = await mock.Object.GetByIdAsync(Guid.NewGuid(), CancellationToken.None); Assert.False(result.IsSuccess); Assert.NotEmpty(result.Errors); } [Fact] public async Task GetByIdAsync_Cancelled_SetsWasCancelled() { var mock = new Mock(); mock.Setup(r => r.GetByIdAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(Result.Cancelled()); var result = await mock.Object.GetByIdAsync(Guid.NewGuid(), CancellationToken.None); Assert.False(result.IsSuccess); Assert.True(result.WasCancelled); } [Fact] public async Task UpsertAsync_ReturnsNonGenericResult_Ok() { var mock = new Mock(); mock.Setup(r => r.UpsertAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(Result.Ok()); Result result = await mock.Object.UpsertAsync(SampleMachine(Guid.NewGuid()), CancellationToken.None); Assert.IsType(result); Assert.True(result.IsSuccess); } [Fact] public async Task UpsertAsync_Fail_CarriesErrors() { var mock = new Mock(); mock.Setup(r => r.UpsertAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(Result.Fail(SampleError)); var result = await mock.Object.UpsertAsync(SampleMachine(Guid.NewGuid()), CancellationToken.None); Assert.False(result.IsSuccess); Assert.Single(result.Errors); } [Fact] public async Task DeleteAsync_ReturnsNonGenericResult_Ok() { var mock = new Mock(); mock.Setup(r => r.DeleteAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(Result.Ok()); Result result = await mock.Object.DeleteAsync(Guid.NewGuid(), CancellationToken.None); Assert.IsType(result); Assert.True(result.IsSuccess); } [Fact] public async Task DeleteAsync_Cancelled_SetsWasCancelled() { var mock = new Mock(); mock.Setup(r => r.DeleteAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(Result.Cancelled()); var result = await mock.Object.DeleteAsync(Guid.NewGuid(), CancellationToken.None); Assert.True(result.WasCancelled); Assert.False(result.IsSuccess); } [Fact] public async Task SaveSnapshotAsync_ReturnsNonGenericResult_Ok() { var machineId = Guid.NewGuid(); var mock = new Mock(); mock.Setup(r => r.SaveSnapshotAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(Result.Ok()); Result result = await mock.Object.SaveSnapshotAsync(SampleSnapshot(machineId), CancellationToken.None); Assert.IsType(result); Assert.True(result.IsSuccess); } [Fact] public async Task SaveSnapshotAsync_Fail_CarriesErrors() { var mock = new Mock(); mock.Setup(r => r.SaveSnapshotAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(Result.Fail(SampleError)); var result = await mock.Object.SaveSnapshotAsync(SampleSnapshot(Guid.NewGuid()), CancellationToken.None); Assert.False(result.IsSuccess); Assert.NotEmpty(result.Errors); } [Fact] public async Task GetLatestSnapshotAsync_Ok_WrapsSnapshot() { var machineId = Guid.NewGuid(); var mock = new Mock(); mock.Setup(r => r.GetLatestSnapshotAsync(machineId, It.IsAny())) .ReturnsAsync(Result.Ok(SampleSnapshot(machineId))); Result result = await mock.Object.GetLatestSnapshotAsync(machineId, CancellationToken.None); Assert.True(result.IsSuccess); Assert.Equal(machineId, result.Value.MachineId); Assert.Equal(ConnectionState.Connected, result.Value.ConnectionState); } [Fact] public async Task GetLatestSnapshotAsync_Fail_NoneStored() { var mock = new Mock(); mock.Setup(r => r.GetLatestSnapshotAsync(It.IsAny(), It.IsAny())) .ReturnsAsync(Result.Fail(SampleError)); var result = await mock.Object.GetLatestSnapshotAsync(Guid.NewGuid(), CancellationToken.None); Assert.False(result.IsSuccess); Assert.Single(result.Errors); } [Fact] public async Task AllMethods_HonorPassedCancellationToken() { using var cts = new CancellationTokenSource(); var token = cts.Token; var mock = new Mock(); mock.Setup(r => r.GetAllAsync(token)) .ReturnsAsync(Result>.Ok(new List())); mock.Setup(r => r.GetByIdAsync(It.IsAny(), token)) .ReturnsAsync(Result.Ok(SampleMachine(Guid.NewGuid()))); mock.Setup(r => r.UpsertAsync(It.IsAny(), token)).ReturnsAsync(Result.Ok()); mock.Setup(r => r.DeleteAsync(It.IsAny(), token)).ReturnsAsync(Result.Ok()); mock.Setup(r => r.SaveSnapshotAsync(It.IsAny(), token)).ReturnsAsync(Result.Ok()); mock.Setup(r => r.GetLatestSnapshotAsync(It.IsAny(), token)) .ReturnsAsync(Result.Ok(SampleSnapshot(Guid.NewGuid()))); await mock.Object.GetAllAsync(token); await mock.Object.GetByIdAsync(Guid.NewGuid(), token); await mock.Object.UpsertAsync(SampleMachine(Guid.NewGuid()), token); await mock.Object.DeleteAsync(Guid.NewGuid(), token); await mock.Object.SaveSnapshotAsync(SampleSnapshot(Guid.NewGuid()), token); await mock.Object.GetLatestSnapshotAsync(Guid.NewGuid(), token); mock.Verify(r => r.GetAllAsync(token), Times.Once); mock.Verify(r => r.GetByIdAsync(It.IsAny(), token), Times.Once); mock.Verify(r => r.UpsertAsync(It.IsAny(), token), Times.Once); mock.Verify(r => r.DeleteAsync(It.IsAny(), token), Times.Once); mock.Verify(r => r.SaveSnapshotAsync(It.IsAny(), token), Times.Once); mock.Verify(r => r.GetLatestSnapshotAsync(It.IsAny(), token), Times.Once); } } }