using System.Data; using Dapper; using JdeScoping.Core.Models; using JdeScoping.Core.Models.Enums; using JdeScoping.DataAccess.Interfaces; using JdeScoping.DataSync.Contracts; using JdeScoping.DataSync.Services; using Microsoft.Extensions.Logging.Abstractions; using NSubstitute; using Shouldly; namespace JdeScoping.DataSync.Tests.Services; /// /// Unit tests for DataUpdateRepository. /// Tests the custom interval functionality for GetSyncStatusAsync. /// public class DataUpdateRepositoryTests { private readonly IDbConnectionFactory _connectionFactory; private readonly DataUpdateRepository _sut; public DataUpdateRepositoryTests() { _connectionFactory = Substitute.For(); _sut = new DataUpdateRepository( _connectionFactory, NullLogger.Instance); } #region GetSyncStatusAsync Custom Intervals [Fact] public void GetSyncStatusAsync_WithoutCustomIntervals_UsesDefaultIntervals() { // This test verifies backward compatibility - method should work without custom intervals // The expected defaults are: // - Hourly: 60 minutes // - Daily: 1440 minutes (24 hours) // - Mass: 10080 minutes (7 days) // Arrange - Get expected intervals from static helper var hourlyInterval = DataUpdateRepository.GetDefaultInterval(UpdateTypes.Hourly); var dailyInterval = DataUpdateRepository.GetDefaultInterval(UpdateTypes.Daily); var massInterval = DataUpdateRepository.GetDefaultInterval(UpdateTypes.Mass); // Assert expected defaults hourlyInterval.ShouldBe(60); dailyInterval.ShouldBe(1440); massInterval.ShouldBe(10080); } [Fact] public void GetExpectedInterval_WithCustomIntervals_ReturnsCustomValue() { // Arrange - UpdateTypes.Mass = 3, UpdateTypes.Daily = 2 var customIntervals = new Dictionary { { "MisData_3", 100800 }, // Mass = 3 { "WorkOrder_2", 120 } // Daily = 2 }; // Act var misDataMassInterval = DataUpdateRepository.GetExpectedInterval( "MisData", UpdateTypes.Mass, customIntervals); var workOrderDailyInterval = DataUpdateRepository.GetExpectedInterval( "WorkOrder", UpdateTypes.Daily, customIntervals); // Assert misDataMassInterval.ShouldBe(100800); workOrderDailyInterval.ShouldBe(120); } [Fact] public void GetExpectedInterval_WithNoMatchingCustomInterval_ReturnsDefault() { // Arrange - UpdateTypes.Mass = 3 var customIntervals = new Dictionary { { "MisData_3", 100800 } // Only MisData_Mass has custom interval }; // Act - WorkOrder_Daily should fall back to default var workOrderDailyInterval = DataUpdateRepository.GetExpectedInterval( "WorkOrder", UpdateTypes.Daily, customIntervals); // Assert - Should return default 1440 for Daily workOrderDailyInterval.ShouldBe(1440); } [Fact] public void GetExpectedInterval_WithNullCustomIntervals_ReturnsDefault() { // Act var massInterval = DataUpdateRepository.GetExpectedInterval( "MisData", UpdateTypes.Mass, null); // Assert - Should return default 10080 for Mass massInterval.ShouldBe(10080); } [Fact] public void GetExpectedInterval_WithEmptyCustomIntervals_ReturnsDefault() { // Arrange var customIntervals = new Dictionary(); // Act var hourlyInterval = DataUpdateRepository.GetExpectedInterval( "WorkOrder", UpdateTypes.Hourly, customIntervals); // Assert - Should return default 60 for Hourly hourlyInterval.ShouldBe(60); } [Fact] public void IsOverdue_WithCustomInterval_UsesCustomValue() { // Arrange - Custom interval of 100800 minutes (70 days) // UpdateTypes.Mass = 3 var customIntervals = new Dictionary { { "MisData_3", 100800 } }; // Default Mass interval is 10080 min (7 days) + 50% grace = 10.5 days // A sync 15 days ago would be overdue with default (15 > 10.5) // but NOT overdue with custom 70-day interval + 50% grace = 105 days (15 < 105) var lastSync = DateTime.UtcNow.AddDays(-15); // Act var isOverdueWithCustom = DataUpdateRepository.IsOverdue( lastSync, "MisData", UpdateTypes.Mass, customIntervals); var isOverdueWithDefault = DataUpdateRepository.IsOverdue( lastSync, "MisData", UpdateTypes.Mass, null); // Assert isOverdueWithCustom.ShouldBeFalse(); // 15 days < 70 days + 50% grace = 105 days isOverdueWithDefault.ShouldBeTrue(); // 15 days > 7 days + 50% grace = 10.5 days } [Fact] public void IsOverdue_WithNoLastSync_ReturnsTrue() { // Act var isOverdue = DataUpdateRepository.IsOverdue( null, "WorkOrder", UpdateTypes.Daily, null); // Assert isOverdue.ShouldBeTrue(); } [Fact] public void IsOverdue_WithRecentSync_ReturnsFalse() { // Arrange - Sync completed 5 minutes ago for Hourly (60 min interval) var lastSync = DateTime.UtcNow.AddMinutes(-5); // Act var isOverdue = DataUpdateRepository.IsOverdue( lastSync, "WorkOrder", UpdateTypes.Hourly, null); // Assert isOverdue.ShouldBeFalse(); } [Fact] public void IsOverdue_WithOldSync_ReturnsTrue() { // Arrange - Sync completed 3 hours ago for Hourly (60 min + 50% grace = 90 min) var lastSync = DateTime.UtcNow.AddHours(-3); // Act var isOverdue = DataUpdateRepository.IsOverdue( lastSync, "WorkOrder", UpdateTypes.Hourly, null); // Assert isOverdue.ShouldBeTrue(); } #endregion #region Constructor Tests [Fact] public void Constructor_WithNullConnectionFactory_ThrowsArgumentNullException() { // Act & Assert Should.Throw(() => new DataUpdateRepository(null!, NullLogger.Instance)); } [Fact] public void Constructor_WithNullLogger_ThrowsArgumentNullException() { // Arrange var connectionFactory = Substitute.For(); // Act & Assert Should.Throw(() => new DataUpdateRepository(connectionFactory, null!)); } #endregion }