using JdeScoping.DataAccess.Interfaces; using JdeScoping.DataAccess.Services; using JdeScoping.Domain.Models; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using NSubstitute; using Shouldly; using Xunit; namespace JdeScoping.DataAccess.Tests.Services; /// /// Unit tests for ManualSyncRequestService. /// Tests constructor validation, interface contract compliance, and static helper methods. /// Note: Since this service uses Dapper with raw SQL, full integration tests with /// an actual database are required for complete coverage of the SQL operations. /// public class ManualSyncRequestServiceTests { private readonly IDbConnectionFactory _connectionFactory; private readonly ILogger _logger; public ManualSyncRequestServiceTests() { _connectionFactory = Substitute.For(); _logger = NullLogger.Instance; } #region Constructor Tests [Fact] public void Constructor_WithNullConnectionFactory_ThrowsArgumentNullException() { // Act & Assert var exception = Should.Throw(() => new ManualSyncRequestService(null!, _logger)); exception.ParamName.ShouldBe("connectionFactory"); } [Fact] public void Constructor_WithNullLogger_ThrowsArgumentNullException() { // Act & Assert var exception = Should.Throw(() => new ManualSyncRequestService(_connectionFactory, null!)); exception.ParamName.ShouldBe("logger"); } [Fact] public void Constructor_WithValidDependencies_CreatesInstance() { // Act var service = new ManualSyncRequestService(_connectionFactory, _logger); // Assert service.ShouldNotBeNull(); } #endregion #region Interface Contract Tests [Fact] public void ManualSyncRequestService_ImplementsIManualSyncRequestService() { // Arrange & Act var service = new ManualSyncRequestService(_connectionFactory, _logger); // Assert service.ShouldBeAssignableTo(); } [Fact] public void GetRequestsAsync_HasCorrectSignature() { // Arrange var service = new ManualSyncRequestService(_connectionFactory, _logger); // Act - Verify method exists with correct return type var methodInfo = typeof(ManualSyncRequestService).GetMethod(nameof(ManualSyncRequestService.GetRequestsAsync)); // Assert methodInfo.ShouldNotBeNull(); methodInfo.ReturnType.ShouldBe(typeof(Task>)); } [Fact] public void GetNextPendingRequestAsync_HasCorrectSignature() { // Arrange var service = new ManualSyncRequestService(_connectionFactory, _logger); // Act - Verify method exists with correct return type var methodInfo = typeof(ManualSyncRequestService).GetMethod(nameof(ManualSyncRequestService.GetNextPendingRequestAsync)); // Assert methodInfo.ShouldNotBeNull(); methodInfo.ReturnType.ShouldBe(typeof(Task)); } [Fact] public void CreateRequestAsync_HasCorrectSignature() { // Arrange var service = new ManualSyncRequestService(_connectionFactory, _logger); // Act - Verify method exists with correct return type var methodInfo = typeof(ManualSyncRequestService).GetMethod(nameof(ManualSyncRequestService.CreateRequestAsync)); // Assert methodInfo.ShouldNotBeNull(); methodInfo.ReturnType.ShouldBe(typeof(Task)); } [Fact] public void CancelRequestAsync_HasCorrectSignature() { // Arrange var service = new ManualSyncRequestService(_connectionFactory, _logger); // Act - Verify method exists with correct return type var methodInfo = typeof(ManualSyncRequestService).GetMethod(nameof(ManualSyncRequestService.CancelRequestAsync)); // Assert methodInfo.ShouldNotBeNull(); methodInfo.ReturnType.ShouldBe(typeof(Task)); } [Fact] public void CompleteRequestAsync_HasCorrectSignature() { // Arrange var service = new ManualSyncRequestService(_connectionFactory, _logger); // Act - Verify method exists with correct return type var methodInfo = typeof(ManualSyncRequestService).GetMethod(nameof(ManualSyncRequestService.CompleteRequestAsync)); // Assert methodInfo.ShouldNotBeNull(); methodInfo.ReturnType.ShouldBe(typeof(Task)); } #endregion #region Domain Model Tests [Fact] public void ManualSyncRequest_Status_WhenPending_ReturnsPending() { // Arrange var request = new ManualSyncRequest { Id = 1, PipelineName = "TestPipeline", SyncType = "mass", RequestedBy = "testuser", RequestDT = DateTime.UtcNow, CompletedDT = null, CancelDT = null }; // Act & Assert request.Status.ShouldBe("Pending"); } [Fact] public void ManualSyncRequest_Status_WhenCompleted_ReturnsCompleted() { // Arrange var request = new ManualSyncRequest { Id = 1, PipelineName = "TestPipeline", SyncType = "mass", RequestedBy = "testuser", RequestDT = DateTime.UtcNow.AddHours(-1), CompletedDT = DateTime.UtcNow, CancelDT = null }; // Act & Assert request.Status.ShouldBe("Completed"); } [Fact] public void ManualSyncRequest_Status_WhenCancelled_ReturnsCancelled() { // Arrange var request = new ManualSyncRequest { Id = 1, PipelineName = "TestPipeline", SyncType = "mass", RequestedBy = "testuser", RequestDT = DateTime.UtcNow.AddHours(-1), CompletedDT = null, CancelDT = DateTime.UtcNow, CancelledBy = "admin" }; // Act & Assert request.Status.ShouldBe("Cancelled"); } [Fact] public void ManualSyncRequest_Status_WhenCancelledAndCompleted_ReturnsCancelled() { // Arrange - Edge case: both CancelDT and CompletedDT are set // Based on the implementation, CancelDT takes precedence var request = new ManualSyncRequest { Id = 1, PipelineName = "TestPipeline", SyncType = "mass", RequestedBy = "testuser", RequestDT = DateTime.UtcNow.AddHours(-2), CompletedDT = DateTime.UtcNow.AddHours(-1), CancelDT = DateTime.UtcNow, CancelledBy = "admin" }; // Act & Assert // CancelDT is checked first in the Status property, so it should return "Cancelled" request.Status.ShouldBe("Cancelled"); } [Fact] public void ManualSyncRequest_DefaultRowVersion_IsEmptyArray() { // Arrange var request = new ManualSyncRequest(); // Act & Assert request.RowVersion.ShouldBeEmpty(); } [Fact] public void ManualSyncRequest_DefaultPipelineName_IsEmptyString() { // Arrange var request = new ManualSyncRequest(); // Act & Assert request.PipelineName.ShouldBe(string.Empty); } [Fact] public void ManualSyncRequest_DefaultSyncType_IsEmptyString() { // Arrange var request = new ManualSyncRequest(); // Act & Assert request.SyncType.ShouldBe(string.Empty); } [Fact] public void ManualSyncRequest_DefaultRequestedBy_IsEmptyString() { // Arrange var request = new ManualSyncRequest(); // Act & Assert request.RequestedBy.ShouldBe(string.Empty); } [Fact] public void ManualSyncRequest_CancelledBy_IsNullableAndDefaultsToNull() { // Arrange var request = new ManualSyncRequest(); // Act & Assert request.CancelledBy.ShouldBeNull(); } #endregion #region Method Parameter Tests [Fact] public void GetRequestsAsync_PendingOnlyParameter_DefaultsToFalse() { // Verify the interface defines correct default parameter var methodInfo = typeof(IManualSyncRequestService).GetMethod(nameof(IManualSyncRequestService.GetRequestsAsync)); var parameters = methodInfo!.GetParameters(); // The pendingOnly parameter should have a default value of false var pendingOnlyParam = parameters.FirstOrDefault(p => p.Name == "pendingOnly"); pendingOnlyParam.ShouldNotBeNull(); pendingOnlyParam.HasDefaultValue.ShouldBeTrue(); pendingOnlyParam.DefaultValue.ShouldBe(false); } [Fact] public void CreateRequestAsync_RequiresPipelineName() { // Verify the method has a pipelineName parameter var methodInfo = typeof(IManualSyncRequestService).GetMethod(nameof(IManualSyncRequestService.CreateRequestAsync)); var parameters = methodInfo!.GetParameters(); var param = parameters.FirstOrDefault(p => p.Name == "pipelineName"); param.ShouldNotBeNull(); param.ParameterType.ShouldBe(typeof(string)); } [Fact] public void CreateRequestAsync_RequiresSyncType() { // Verify the method has a syncType parameter var methodInfo = typeof(IManualSyncRequestService).GetMethod(nameof(IManualSyncRequestService.CreateRequestAsync)); var parameters = methodInfo!.GetParameters(); var param = parameters.FirstOrDefault(p => p.Name == "syncType"); param.ShouldNotBeNull(); param.ParameterType.ShouldBe(typeof(string)); } [Fact] public void CreateRequestAsync_RequiresRequestedBy() { // Verify the method has a requestedBy parameter var methodInfo = typeof(IManualSyncRequestService).GetMethod(nameof(IManualSyncRequestService.CreateRequestAsync)); var parameters = methodInfo!.GetParameters(); var param = parameters.FirstOrDefault(p => p.Name == "requestedBy"); param.ShouldNotBeNull(); param.ParameterType.ShouldBe(typeof(string)); } [Fact] public void CancelRequestAsync_RequiresId() { // Verify the method has an id parameter var methodInfo = typeof(IManualSyncRequestService).GetMethod(nameof(IManualSyncRequestService.CancelRequestAsync)); var parameters = methodInfo!.GetParameters(); var param = parameters.FirstOrDefault(p => p.Name == "id"); param.ShouldNotBeNull(); param.ParameterType.ShouldBe(typeof(int)); } [Fact] public void CancelRequestAsync_RequiresRowVersion() { // Verify the method has a rowVersion parameter for optimistic concurrency var methodInfo = typeof(IManualSyncRequestService).GetMethod(nameof(IManualSyncRequestService.CancelRequestAsync)); var parameters = methodInfo!.GetParameters(); var param = parameters.FirstOrDefault(p => p.Name == "rowVersion"); param.ShouldNotBeNull(); param.ParameterType.ShouldBe(typeof(byte[])); } [Fact] public void CompleteRequestAsync_RequiresId() { // Verify the method has an id parameter var methodInfo = typeof(IManualSyncRequestService).GetMethod(nameof(IManualSyncRequestService.CompleteRequestAsync)); var parameters = methodInfo!.GetParameters(); var param = parameters.FirstOrDefault(p => p.Name == "id"); param.ShouldNotBeNull(); param.ParameterType.ShouldBe(typeof(int)); } [Fact] public void CompleteRequestAsync_RequiresRowVersion() { // Verify the method has a rowVersion parameter for optimistic concurrency var methodInfo = typeof(IManualSyncRequestService).GetMethod(nameof(IManualSyncRequestService.CompleteRequestAsync)); var parameters = methodInfo!.GetParameters(); var param = parameters.FirstOrDefault(p => p.Name == "rowVersion"); param.ShouldNotBeNull(); param.ParameterType.ShouldBe(typeof(byte[])); } #endregion }