using Dapper; using JdeScoping.DataAccess; using JdeScoping.DataAccess.Interfaces; using JdeScoping.DataSync.Dev; using Microsoft.Data.SqlClient; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging.Abstractions; using NSubstitute; using Shouldly; namespace JdeScoping.DataSync.Dev.Tests; /// /// Integration tests for ProfitCenter development ETL. /// Requires: Local SQL Server, CACHED_DB_FILES directory with profitcenter.json.zstd /// public class ProfitCenterDevEtlTests : IAsyncLifetime { private readonly string _connectionString; private readonly string _cacheDirectory; private readonly IDbConnectionFactory _connectionFactory; public ProfitCenterDevEtlTests() { var config = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json", optional: true) .AddEnvironmentVariables() .Build(); _connectionString = config.GetConnectionString("LotFinderDB") ?? throw new InvalidOperationException("LotFinderDB connection string not configured."); _cacheDirectory = config["DevEtl:CacheDirectory"] ?? Path.Combine(Directory.GetCurrentDirectory(), "..", "..", "..", "..", "..", "CACHED_DB_FILES"); _connectionFactory = new DbConnectionFactory(config, NullLogger.Instance); } public async Task InitializeAsync() { await using var connection = new SqlConnection(_connectionString); await connection.OpenAsync(); await connection.ExecuteAsync("TRUNCATE TABLE dbo.ProfitCenter"); } public Task DisposeAsync() => Task.CompletedTask; [Fact] public void Create_ReturnsValidPipeline() { var cacheFilePath = Path.Combine(_cacheDirectory, ProfitCenterDevEtl.CacheFileName); if (!File.Exists(cacheFilePath)) return; var pipeline = ProfitCenterDevEtl.Create(_connectionFactory, cacheFilePath); pipeline.ShouldNotBeNull(); pipeline.PipelineName.ShouldBe("ProfitCenter_Dev"); } [Fact] public async Task Execute_LoadsProfitCenterData() { var cacheFilePath = Path.Combine(_cacheDirectory, ProfitCenterDevEtl.CacheFileName); if (!File.Exists(cacheFilePath)) return; var pipeline = ProfitCenterDevEtl.Create(_connectionFactory, cacheFilePath); var result = await pipeline.ExecuteAsync(); result.Success.ShouldBeTrue(result.Error?.Message ?? "Pipeline should succeed"); result.TotalRows.ShouldBeGreaterThan(0, "Should load at least one row"); await using var connection = new SqlConnection(_connectionString); await connection.OpenAsync(); var count = await connection.ExecuteScalarAsync("SELECT COUNT(*) FROM dbo.ProfitCenter"); count.ShouldBe((int)result.TotalRows); } [Fact] public async Task Registry_RunAsync_LoadsProfitCenter() { if (!Directory.Exists(_cacheDirectory)) return; var registry = new DevEtlRegistry(_connectionFactory, _cacheDirectory); var result = await registry.RunAsync("ProfitCenter"); result.Success.ShouldBeTrue(result.Error?.Message ?? "Pipeline should succeed"); result.TotalRows.ShouldBeGreaterThan(0); } [Fact] public void Create_WithNullConnectionFactory_ThrowsArgumentNullException() { var cacheFilePath = Path.Combine(_cacheDirectory, ProfitCenterDevEtl.CacheFileName); Should.Throw(() => ProfitCenterDevEtl.Create(null!, cacheFilePath)); } [Fact] public void Create_WithEmptyCacheFilePath_ThrowsArgumentException() { var mockFactory = Substitute.For(); Should.Throw(() => ProfitCenterDevEtl.Create(mockFactory, string.Empty)); } [Fact] public void DevEtlRegistry_GetAvailableTables_IncludesProfitCenter() { if (!Directory.Exists(_cacheDirectory)) return; var registry = new DevEtlRegistry(_connectionFactory, _cacheDirectory); var tables = registry.GetAvailableTables().ToList(); tables.ShouldContain("ProfitCenter"); } }