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 FunctionCode development ETL. /// public class FunctionCodeDevEtlTests : IAsyncLifetime { private readonly string _connectionString; private readonly string _cacheDirectory; private readonly IDbConnectionFactory _connectionFactory; public FunctionCodeDevEtlTests() { 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.FunctionCode"); } public Task DisposeAsync() => Task.CompletedTask; [Fact] public void Create_ReturnsValidPipeline() { var cacheFilePath = Path.Combine(_cacheDirectory, FunctionCodeDevEtl.CacheFileName); if (!File.Exists(cacheFilePath)) return; var pipeline = FunctionCodeDevEtl.Create(_connectionFactory, cacheFilePath); pipeline.ShouldNotBeNull(); pipeline.PipelineName.ShouldBe("FunctionCode_Dev"); } [Fact] public async Task Execute_LoadsData() { var cacheFilePath = Path.Combine(_cacheDirectory, FunctionCodeDevEtl.CacheFileName); if (!File.Exists(cacheFilePath)) return; var pipeline = FunctionCodeDevEtl.Create(_connectionFactory, cacheFilePath); var result = await pipeline.ExecuteAsync(); result.Success.ShouldBeTrue(result.Error?.Message ?? "Pipeline should succeed"); result.TotalRows.ShouldBeGreaterThan(0); await using var connection = new SqlConnection(_connectionString); await connection.OpenAsync(); var count = await connection.ExecuteScalarAsync("SELECT COUNT(*) FROM dbo.FunctionCode"); count.ShouldBe((int)result.TotalRows); } [Fact] public async Task Registry_RunAsync_LoadsTable() { if (!Directory.Exists(_cacheDirectory)) return; var registry = new DevEtlRegistry(_connectionFactory, _cacheDirectory); var result = await registry.RunAsync("FunctionCode"); result.Success.ShouldBeTrue(result.Error?.Message ?? "Pipeline should succeed"); result.TotalRows.ShouldBeGreaterThan(0); } [Fact] public void Create_WithNullConnectionFactory_ThrowsArgumentNullException() { var cacheFilePath = Path.Combine(_cacheDirectory, FunctionCodeDevEtl.CacheFileName); Should.Throw(() => FunctionCodeDevEtl.Create(null!, cacheFilePath)); } }