refactor: remove unused classes and consolidate ViewModels in Core
Remove 9 unused types from Core (duplicate extension classes, TableSpec, ColumnSpec, LotLocation), move ComponentLotViewModel and OperatorViewModel from Client to Core, and refactor DataSync.Dev to use pipeline-based configuration. Fix Login.razor to use UserInfoDto directly.
This commit is contained in:
@@ -1,177 +0,0 @@
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Integration tests for Branch development ETL.
|
||||
/// Requires: Local SQL Server, CACHED_DB_FILES directory with branch.json.zstd
|
||||
/// </summary>
|
||||
public class BranchDevEtlTests : IAsyncLifetime
|
||||
{
|
||||
private readonly string _connectionString;
|
||||
private readonly string _cacheDirectory;
|
||||
private readonly IDbConnectionFactory _connectionFactory;
|
||||
|
||||
public BranchDevEtlTests()
|
||||
{
|
||||
// Load configuration
|
||||
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<DbConnectionFactory>.Instance);
|
||||
}
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
// Ensure Branch table is empty before test
|
||||
await using var connection = new SqlConnection(_connectionString);
|
||||
await connection.OpenAsync();
|
||||
await connection.ExecuteAsync("TRUNCATE TABLE dbo.Branch");
|
||||
}
|
||||
|
||||
public Task DisposeAsync() => Task.CompletedTask;
|
||||
|
||||
[Fact]
|
||||
public void Create_ReturnsValidPipeline()
|
||||
{
|
||||
// Arrange
|
||||
var cacheFilePath = Path.Combine(_cacheDirectory, BranchDevEtl.CacheFileName);
|
||||
if (!File.Exists(cacheFilePath))
|
||||
{
|
||||
// Skip test if cache file doesn't exist
|
||||
return;
|
||||
}
|
||||
|
||||
// Act
|
||||
var pipeline = BranchDevEtl.Create(_connectionFactory, cacheFilePath);
|
||||
|
||||
// Assert
|
||||
pipeline.ShouldNotBeNull();
|
||||
pipeline.PipelineName.ShouldBe("Branch_Dev");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Execute_LoadsBranchData()
|
||||
{
|
||||
// Arrange
|
||||
var cacheFilePath = Path.Combine(_cacheDirectory, BranchDevEtl.CacheFileName);
|
||||
if (!File.Exists(cacheFilePath))
|
||||
{
|
||||
// Skip test if cache file doesn't exist
|
||||
return;
|
||||
}
|
||||
|
||||
var pipeline = BranchDevEtl.Create(_connectionFactory, cacheFilePath);
|
||||
|
||||
// Act
|
||||
var result = await pipeline.ExecuteAsync();
|
||||
|
||||
// Assert
|
||||
result.Success.ShouldBeTrue(result.Error?.Message ?? "Pipeline should succeed");
|
||||
result.TotalRows.ShouldBeGreaterThan(0, "Should load at least one row");
|
||||
|
||||
// Verify data in database
|
||||
await using var connection = new SqlConnection(_connectionString);
|
||||
await connection.OpenAsync();
|
||||
var count = await connection.ExecuteScalarAsync<int>("SELECT COUNT(*) FROM dbo.Branch");
|
||||
|
||||
count.ShouldBe((int)result.TotalRows, "Database row count should match pipeline result");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Registry_RunAsync_LoadsBranch()
|
||||
{
|
||||
// Arrange
|
||||
if (!Directory.Exists(_cacheDirectory))
|
||||
{
|
||||
// Skip test if cache directory doesn't exist
|
||||
return;
|
||||
}
|
||||
|
||||
var registry = new DevEtlRegistry(_connectionFactory, _cacheDirectory);
|
||||
|
||||
// Act
|
||||
var result = await registry.RunAsync("Branch");
|
||||
|
||||
// Assert
|
||||
result.Success.ShouldBeTrue(result.Error?.Message ?? "Pipeline should succeed");
|
||||
result.TotalRows.ShouldBeGreaterThan(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_WithNullConnectionFactory_ThrowsArgumentNullException()
|
||||
{
|
||||
// Arrange
|
||||
var cacheFilePath = Path.Combine(_cacheDirectory, BranchDevEtl.CacheFileName);
|
||||
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() => BranchDevEtl.Create(null!, cacheFilePath));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_WithEmptyCacheFilePath_ThrowsArgumentException()
|
||||
{
|
||||
// Arrange
|
||||
var mockFactory = Substitute.For<IDbConnectionFactory>();
|
||||
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentException>(() => BranchDevEtl.Create(mockFactory, string.Empty));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_WithNonExistentCacheFile_ThrowsFileNotFoundException()
|
||||
{
|
||||
// Arrange
|
||||
var mockFactory = Substitute.For<IDbConnectionFactory>();
|
||||
var nonExistentPath = "/nonexistent/path/branch.json.zstd";
|
||||
|
||||
// Act & Assert
|
||||
Should.Throw<FileNotFoundException>(() => BranchDevEtl.Create(mockFactory, nonExistentPath));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DevEtlRegistry_WithNonExistentCacheDirectory_ThrowsDirectoryNotFoundException()
|
||||
{
|
||||
// Arrange
|
||||
var mockFactory = Substitute.For<IDbConnectionFactory>();
|
||||
var nonExistentPath = "/nonexistent/cache/directory";
|
||||
|
||||
// Act & Assert
|
||||
Should.Throw<DirectoryNotFoundException>(() => new DevEtlRegistry(mockFactory, nonExistentPath));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DevEtlRegistry_GetAvailableTables_IncludesBranch()
|
||||
{
|
||||
// Arrange
|
||||
if (!Directory.Exists(_cacheDirectory))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var registry = new DevEtlRegistry(_connectionFactory, _cacheDirectory);
|
||||
|
||||
// Act
|
||||
var tables = registry.GetAvailableTables().ToList();
|
||||
|
||||
// Assert
|
||||
tables.ShouldContain("Branch");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
using Dapper;
|
||||
using JdeScoping.DataAccess;
|
||||
using JdeScoping.DataAccess.Interfaces;
|
||||
using JdeScoping.DataSync.Dev.Configuration;
|
||||
using JdeScoping.DataSync.Dev.Contracts;
|
||||
using JdeScoping.DataSync.Dev.Options;
|
||||
using JdeScoping.DataSync.Dev.Services;
|
||||
using JdeScoping.DataSync.Etl.Pipeline;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NSubstitute;
|
||||
using Shouldly;
|
||||
|
||||
namespace JdeScoping.DataSync.Dev.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for DevEtlPipelineFactory.
|
||||
/// </summary>
|
||||
public class DevEtlPipelineFactoryTests
|
||||
{
|
||||
private readonly IDbConnectionFactory _connectionFactory;
|
||||
private readonly ILogger<EtlPipeline> _logger;
|
||||
private readonly string _cacheDirectory;
|
||||
|
||||
public DevEtlPipelineFactoryTests()
|
||||
{
|
||||
var config = new ConfigurationBuilder()
|
||||
.SetBasePath(Directory.GetCurrentDirectory())
|
||||
.AddJsonFile("appsettings.json", optional: true)
|
||||
.AddEnvironmentVariables()
|
||||
.Build();
|
||||
|
||||
_connectionFactory = new DbConnectionFactory(config, NullLogger<DbConnectionFactory>.Instance);
|
||||
_logger = NullLogger<EtlPipeline>.Instance;
|
||||
_cacheDirectory = config["DevEtl:CacheDirectory"]
|
||||
?? Path.Combine(Directory.GetCurrentDirectory(), "..", "..", "..", "..", "..", "CACHED_DB_FILES");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithValidConfig_LoadsPipelines()
|
||||
{
|
||||
// Arrange
|
||||
var pipelines = new Dictionary<string, DevPipelineConfig>
|
||||
{
|
||||
["TestTable"] = new(new DevSourceConfig("test.pb.zstd"), new DevDestinationConfig("TestTable"))
|
||||
};
|
||||
var config = new DevPipelinesRoot(null, pipelines);
|
||||
|
||||
// Act
|
||||
var factory = new DevEtlPipelineFactory(_connectionFactory, config, _logger);
|
||||
|
||||
// Assert
|
||||
factory.GetAvailableTables().ShouldContain("TestTable");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAvailableTables_Returns21Tables()
|
||||
{
|
||||
// Arrange
|
||||
var factory = CreateFactoryFromConfig();
|
||||
|
||||
// Act
|
||||
var tables = factory.GetAvailableTables().ToList();
|
||||
|
||||
// Assert
|
||||
tables.Count.ShouldBe(21);
|
||||
tables.ShouldContain("Branch");
|
||||
tables.ShouldContain("WorkOrder_Curr");
|
||||
tables.ShouldContain("LotUsage_Curr");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsVeryLargeTable_ReturnsTrueForVeryLargeTables()
|
||||
{
|
||||
// Arrange
|
||||
var factory = CreateFactoryFromConfig();
|
||||
|
||||
// Act & Assert
|
||||
factory.IsVeryLargeTable("WorkOrderTime_Curr").ShouldBeTrue();
|
||||
factory.IsVeryLargeTable("WorkOrderStep_Curr").ShouldBeTrue();
|
||||
factory.IsVeryLargeTable("LotUsage_Curr").ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsVeryLargeTable_ReturnsFalseForSmallTables()
|
||||
{
|
||||
// Arrange
|
||||
var factory = CreateFactoryFromConfig();
|
||||
|
||||
// Act & Assert
|
||||
factory.IsVeryLargeTable("Branch").ShouldBeFalse();
|
||||
factory.IsVeryLargeTable("Item").ShouldBeFalse();
|
||||
factory.IsVeryLargeTable("JdeUser").ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetPipeline_WithValidTable_ReturnsPipeline()
|
||||
{
|
||||
// Arrange
|
||||
var factory = CreateFactoryFromConfig();
|
||||
|
||||
// Skip if cache directory doesn't exist
|
||||
if (!Directory.Exists(_cacheDirectory))
|
||||
return;
|
||||
|
||||
var cacheFilePath = Path.Combine(_cacheDirectory, "branch.pb.zstd");
|
||||
if (!File.Exists(cacheFilePath))
|
||||
return;
|
||||
|
||||
// Act
|
||||
var pipeline = factory.GetPipeline("Branch", _cacheDirectory);
|
||||
|
||||
// Assert
|
||||
pipeline.ShouldNotBeNull();
|
||||
pipeline.PipelineName.ShouldBe("Branch_Dev");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetPipeline_WithInvalidTable_ThrowsInvalidOperationException()
|
||||
{
|
||||
// Arrange
|
||||
var factory = CreateFactoryFromConfig();
|
||||
|
||||
// Act & Assert
|
||||
Should.Throw<InvalidOperationException>(() => factory.GetPipeline("NonExistentTable", _cacheDirectory));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetPipeline_WithNullTableName_ThrowsArgumentException()
|
||||
{
|
||||
// Arrange
|
||||
var factory = CreateFactoryFromConfig();
|
||||
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentException>(() => factory.GetPipeline(null!, _cacheDirectory));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetPipeline_WithEmptyCacheDirectory_ThrowsArgumentException()
|
||||
{
|
||||
// Arrange
|
||||
var factory = CreateFactoryFromConfig();
|
||||
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentException>(() => factory.GetPipeline("Branch", string.Empty));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithNullConnectionFactory_ThrowsArgumentNullException()
|
||||
{
|
||||
// Arrange
|
||||
var config = new DevPipelinesRoot(null, new Dictionary<string, DevPipelineConfig>());
|
||||
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() => new DevEtlPipelineFactory(null!, config, _logger));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithNullConfig_ThrowsArgumentNullException()
|
||||
{
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() => new DevEtlPipelineFactory(_connectionFactory, (DevPipelinesRoot)null!, _logger));
|
||||
}
|
||||
|
||||
private DevEtlPipelineFactory CreateFactoryFromConfig()
|
||||
{
|
||||
// Load actual config from JSON file
|
||||
var settings = new DevPipelineSettings
|
||||
{
|
||||
SizeCategories = new SizeCategories
|
||||
{
|
||||
Small = ["Branch", "OrgHierarchy", "WorkCenter", "ProfitCenter"],
|
||||
Medium = ["JdeUser", "FunctionCode", "Item", "RouteMaster"],
|
||||
Large = ["Lot", "MisData", "WorkOrder_Curr", "WorkOrder_Hist", "LotUsage_Hist", "WorkOrderComponent_Hist"],
|
||||
VeryLarge = ["WorkOrderStep_Hist", "WorkOrderComponent_Curr", "WorkOrderRouting", "LotUsage_Curr", "WorkOrderStep_Curr", "WorkOrderTime_Hist", "WorkOrderTime_Curr"]
|
||||
}
|
||||
};
|
||||
|
||||
var pipelines = new Dictionary<string, DevPipelineConfig>
|
||||
{
|
||||
["Branch"] = new(new DevSourceConfig("branch.pb.zstd"), new DevDestinationConfig("Branch")),
|
||||
["OrgHierarchy"] = new(new DevSourceConfig("orghierarchy.pb.zstd"), new DevDestinationConfig("OrgHierarchy")),
|
||||
["WorkCenter"] = new(new DevSourceConfig("workcenter.pb.zstd"), new DevDestinationConfig("WorkCenter")),
|
||||
["ProfitCenter"] = new(new DevSourceConfig("profitcenter.pb.zstd"), new DevDestinationConfig("ProfitCenter")),
|
||||
["JdeUser"] = new(new DevSourceConfig("jdeuser.pb.zstd"), new DevDestinationConfig("JdeUser")),
|
||||
["FunctionCode"] = new(new DevSourceConfig("functioncode.pb.zstd"), new DevDestinationConfig("FunctionCode")),
|
||||
["Item"] = new(new DevSourceConfig("item.pb.zstd"), new DevDestinationConfig("Item")),
|
||||
["RouteMaster"] = new(new DevSourceConfig("routemaster.pb.zstd"), new DevDestinationConfig("RouteMaster")),
|
||||
["Lot"] = new(new DevSourceConfig("lot.pb.zstd"), new DevDestinationConfig("Lot")),
|
||||
["MisData"] = new(new DevSourceConfig("misdata.pb.zstd"), new DevDestinationConfig("MisData")),
|
||||
["WorkOrder_Curr"] = new(new DevSourceConfig("workorder_curr.pb.zstd"), new DevDestinationConfig("WorkOrder_Curr")),
|
||||
["WorkOrder_Hist"] = new(new DevSourceConfig("workorder_hist.pb.zstd"), new DevDestinationConfig("WorkOrder_Hist")),
|
||||
["LotUsage_Curr"] = new(new DevSourceConfig("lotusage_curr.pb.zstd"), new DevDestinationConfig("LotUsage_Curr")),
|
||||
["LotUsage_Hist"] = new(new DevSourceConfig("lotusage_hist.pb.zstd"), new DevDestinationConfig("LotUsage_Hist")),
|
||||
["WorkOrderComponent_Curr"] = new(new DevSourceConfig("workordercomponent_curr.pb.zstd"), new DevDestinationConfig("WorkOrderComponent_Curr")),
|
||||
["WorkOrderComponent_Hist"] = new(new DevSourceConfig("workordercomponent_hist.pb.zstd"), new DevDestinationConfig("WorkOrderComponent_Hist")),
|
||||
["WorkOrderStep_Curr"] = new(new DevSourceConfig("workorderstep_curr.pb.zstd"), new DevDestinationConfig("WorkOrderStep_Curr")),
|
||||
["WorkOrderStep_Hist"] = new(new DevSourceConfig("workorderstep_hist.pb.zstd"), new DevDestinationConfig("WorkOrderStep_Hist")),
|
||||
["WorkOrderTime_Curr"] = new(new DevSourceConfig("workordertime_curr.pb.zstd"), new DevDestinationConfig("WorkOrderTime_Curr")),
|
||||
["WorkOrderTime_Hist"] = new(new DevSourceConfig("workordertime_hist.pb.zstd"), new DevDestinationConfig("WorkOrderTime_Hist")),
|
||||
["WorkOrderRouting"] = new(new DevSourceConfig("workorderrouting.pb.zstd"), new DevDestinationConfig("WorkOrderRouting"))
|
||||
};
|
||||
|
||||
var config = new DevPipelinesRoot(settings, pipelines);
|
||||
return new DevEtlPipelineFactory(_connectionFactory, config, _logger);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
using JdeScoping.DataSync.Dev.Configuration;
|
||||
using JdeScoping.DataSync.Dev.Contracts;
|
||||
using JdeScoping.DataSync.Dev.Services;
|
||||
using JdeScoping.DataSync.Etl.Pipeline;
|
||||
using JdeScoping.DataSync.Etl.Results;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using NSubstitute;
|
||||
using Shouldly;
|
||||
|
||||
namespace JdeScoping.DataSync.Dev.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Tests for DevEtlRegistry.
|
||||
/// </summary>
|
||||
public class DevEtlRegistryTests : IDisposable
|
||||
{
|
||||
private readonly IDevEtlPipelineFactory _mockFactory;
|
||||
private readonly ILogger<DevEtlRegistry> _logger;
|
||||
private readonly string _tempDirectory;
|
||||
|
||||
public DevEtlRegistryTests()
|
||||
{
|
||||
_mockFactory = Substitute.For<IDevEtlPipelineFactory>();
|
||||
_logger = NullLogger<DevEtlRegistry>.Instance;
|
||||
|
||||
// Create a temp directory for tests
|
||||
_tempDirectory = Path.Combine(Path.GetTempPath(), $"DevEtlRegistryTests_{Guid.NewGuid():N}");
|
||||
Directory.CreateDirectory(_tempDirectory);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithValidArguments_Succeeds()
|
||||
{
|
||||
// Arrange
|
||||
_mockFactory.GetAvailableTables().Returns(new[] { "Branch" });
|
||||
|
||||
// Act
|
||||
var registry = new DevEtlRegistry(_mockFactory, _tempDirectory, _logger);
|
||||
|
||||
// Assert
|
||||
registry.ShouldNotBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithNullFactory_ThrowsArgumentNullException()
|
||||
{
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() => new DevEtlRegistry(null!, _tempDirectory, _logger));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithEmptyCacheDirectory_ThrowsArgumentException()
|
||||
{
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentException>(() => new DevEtlRegistry(_mockFactory, string.Empty, _logger));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithNonExistentCacheDirectory_ThrowsDirectoryNotFoundException()
|
||||
{
|
||||
// Arrange
|
||||
var nonExistentPath = "/nonexistent/cache/directory";
|
||||
|
||||
// Act & Assert
|
||||
Should.Throw<DirectoryNotFoundException>(() => new DevEtlRegistry(_mockFactory, nonExistentPath, _logger));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetAvailableTables_DelegatesToFactory()
|
||||
{
|
||||
// Arrange
|
||||
var expectedTables = new[] { "Branch", "Item", "Lot" };
|
||||
_mockFactory.GetAvailableTables().Returns(expectedTables);
|
||||
|
||||
var registry = new DevEtlRegistry(_mockFactory, _tempDirectory, _logger);
|
||||
|
||||
// Act
|
||||
var tables = registry.GetAvailableTables().ToList();
|
||||
|
||||
// Assert
|
||||
tables.ShouldBe(expectedTables);
|
||||
_mockFactory.Received(1).GetAvailableTables();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Cleanup temp directory
|
||||
if (Directory.Exists(_tempDirectory))
|
||||
{
|
||||
try
|
||||
{
|
||||
Directory.Delete(_tempDirectory, recursive: true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Ignore cleanup errors
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Integration tests for FunctionCode development ETL.
|
||||
/// </summary>
|
||||
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<DbConnectionFactory>.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<int>("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<ArgumentNullException>(() => FunctionCodeDevEtl.Create(null!, cacheFilePath));
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Integration tests for Item development ETL.
|
||||
/// </summary>
|
||||
public class ItemDevEtlTests : IAsyncLifetime
|
||||
{
|
||||
private readonly string _connectionString;
|
||||
private readonly string _cacheDirectory;
|
||||
private readonly IDbConnectionFactory _connectionFactory;
|
||||
|
||||
public ItemDevEtlTests()
|
||||
{
|
||||
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<DbConnectionFactory>.Instance);
|
||||
}
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
await using var connection = new SqlConnection(_connectionString);
|
||||
await connection.OpenAsync();
|
||||
await connection.ExecuteAsync("TRUNCATE TABLE dbo.Item");
|
||||
}
|
||||
|
||||
public Task DisposeAsync() => Task.CompletedTask;
|
||||
|
||||
[Fact]
|
||||
public void Create_ReturnsValidPipeline()
|
||||
{
|
||||
var cacheFilePath = Path.Combine(_cacheDirectory, ItemDevEtl.CacheFileName);
|
||||
if (!File.Exists(cacheFilePath)) return;
|
||||
|
||||
var pipeline = ItemDevEtl.Create(_connectionFactory, cacheFilePath);
|
||||
|
||||
pipeline.ShouldNotBeNull();
|
||||
pipeline.PipelineName.ShouldBe("Item_Dev");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Execute_LoadsData()
|
||||
{
|
||||
var cacheFilePath = Path.Combine(_cacheDirectory, ItemDevEtl.CacheFileName);
|
||||
if (!File.Exists(cacheFilePath)) return;
|
||||
|
||||
var pipeline = ItemDevEtl.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<int>("SELECT COUNT(*) FROM dbo.Item");
|
||||
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("Item");
|
||||
|
||||
result.Success.ShouldBeTrue(result.Error?.Message ?? "Pipeline should succeed");
|
||||
result.TotalRows.ShouldBeGreaterThan(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_WithNullConnectionFactory_ThrowsArgumentNullException()
|
||||
{
|
||||
var cacheFilePath = Path.Combine(_cacheDirectory, ItemDevEtl.CacheFileName);
|
||||
Should.Throw<ArgumentNullException>(() => ItemDevEtl.Create(null!, cacheFilePath));
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Integration tests for JdeUser development ETL.
|
||||
/// </summary>
|
||||
public class JdeUserDevEtlTests : IAsyncLifetime
|
||||
{
|
||||
private readonly string _connectionString;
|
||||
private readonly string _cacheDirectory;
|
||||
private readonly IDbConnectionFactory _connectionFactory;
|
||||
|
||||
public JdeUserDevEtlTests()
|
||||
{
|
||||
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<DbConnectionFactory>.Instance);
|
||||
}
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
await using var connection = new SqlConnection(_connectionString);
|
||||
await connection.OpenAsync();
|
||||
await connection.ExecuteAsync("TRUNCATE TABLE dbo.JdeUser");
|
||||
}
|
||||
|
||||
public Task DisposeAsync() => Task.CompletedTask;
|
||||
|
||||
[Fact]
|
||||
public void Create_ReturnsValidPipeline()
|
||||
{
|
||||
var cacheFilePath = Path.Combine(_cacheDirectory, JdeUserDevEtl.CacheFileName);
|
||||
if (!File.Exists(cacheFilePath)) return;
|
||||
|
||||
var pipeline = JdeUserDevEtl.Create(_connectionFactory, cacheFilePath);
|
||||
|
||||
pipeline.ShouldNotBeNull();
|
||||
pipeline.PipelineName.ShouldBe("JdeUser_Dev");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Execute_LoadsData()
|
||||
{
|
||||
var cacheFilePath = Path.Combine(_cacheDirectory, JdeUserDevEtl.CacheFileName);
|
||||
if (!File.Exists(cacheFilePath)) return;
|
||||
|
||||
var pipeline = JdeUserDevEtl.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<int>("SELECT COUNT(*) FROM dbo.JdeUser");
|
||||
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("JdeUser");
|
||||
|
||||
result.Success.ShouldBeTrue(result.Error?.Message ?? "Pipeline should succeed");
|
||||
result.TotalRows.ShouldBeGreaterThan(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_WithNullConnectionFactory_ThrowsArgumentNullException()
|
||||
{
|
||||
var cacheFilePath = Path.Combine(_cacheDirectory, JdeUserDevEtl.CacheFileName);
|
||||
Should.Throw<ArgumentNullException>(() => JdeUserDevEtl.Create(null!, cacheFilePath));
|
||||
}
|
||||
}
|
||||
@@ -1,166 +0,0 @@
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Integration tests for OrgHierarchy development ETL.
|
||||
/// Requires: Local SQL Server, CACHED_DB_FILES directory with orghierarchy.json.zstd
|
||||
/// </summary>
|
||||
public class OrgHierarchyDevEtlTests : IAsyncLifetime
|
||||
{
|
||||
private readonly string _connectionString;
|
||||
private readonly string _cacheDirectory;
|
||||
private readonly IDbConnectionFactory _connectionFactory;
|
||||
|
||||
public OrgHierarchyDevEtlTests()
|
||||
{
|
||||
// Load configuration
|
||||
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<DbConnectionFactory>.Instance);
|
||||
}
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
// Ensure OrgHierarchy table is empty before test
|
||||
await using var connection = new SqlConnection(_connectionString);
|
||||
await connection.OpenAsync();
|
||||
await connection.ExecuteAsync("TRUNCATE TABLE dbo.OrgHierarchy");
|
||||
}
|
||||
|
||||
public Task DisposeAsync() => Task.CompletedTask;
|
||||
|
||||
[Fact]
|
||||
public void Create_ReturnsValidPipeline()
|
||||
{
|
||||
// Arrange
|
||||
var cacheFilePath = Path.Combine(_cacheDirectory, OrgHierarchyDevEtl.CacheFileName);
|
||||
if (!File.Exists(cacheFilePath))
|
||||
{
|
||||
// Skip test if cache file doesn't exist
|
||||
return;
|
||||
}
|
||||
|
||||
// Act
|
||||
var pipeline = OrgHierarchyDevEtl.Create(_connectionFactory, cacheFilePath);
|
||||
|
||||
// Assert
|
||||
pipeline.ShouldNotBeNull();
|
||||
pipeline.PipelineName.ShouldBe("OrgHierarchy_Dev");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Execute_LoadsOrgHierarchyData()
|
||||
{
|
||||
// Arrange
|
||||
var cacheFilePath = Path.Combine(_cacheDirectory, OrgHierarchyDevEtl.CacheFileName);
|
||||
if (!File.Exists(cacheFilePath))
|
||||
{
|
||||
// Skip test if cache file doesn't exist
|
||||
return;
|
||||
}
|
||||
|
||||
var pipeline = OrgHierarchyDevEtl.Create(_connectionFactory, cacheFilePath);
|
||||
|
||||
// Act
|
||||
var result = await pipeline.ExecuteAsync();
|
||||
|
||||
// Assert
|
||||
result.Success.ShouldBeTrue(result.Error?.Message ?? "Pipeline should succeed");
|
||||
result.TotalRows.ShouldBeGreaterThan(0, "Should load at least one row");
|
||||
|
||||
// Verify data in database
|
||||
await using var connection = new SqlConnection(_connectionString);
|
||||
await connection.OpenAsync();
|
||||
var count = await connection.ExecuteScalarAsync<int>("SELECT COUNT(*) FROM dbo.OrgHierarchy");
|
||||
|
||||
count.ShouldBe((int)result.TotalRows, "Database row count should match pipeline result");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Registry_RunAsync_LoadsOrgHierarchy()
|
||||
{
|
||||
// Arrange
|
||||
if (!Directory.Exists(_cacheDirectory))
|
||||
{
|
||||
// Skip test if cache directory doesn't exist
|
||||
return;
|
||||
}
|
||||
|
||||
var registry = new DevEtlRegistry(_connectionFactory, _cacheDirectory);
|
||||
|
||||
// Act
|
||||
var result = await registry.RunAsync("OrgHierarchy");
|
||||
|
||||
// Assert
|
||||
result.Success.ShouldBeTrue(result.Error?.Message ?? "Pipeline should succeed");
|
||||
result.TotalRows.ShouldBeGreaterThan(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_WithNullConnectionFactory_ThrowsArgumentNullException()
|
||||
{
|
||||
// Arrange
|
||||
var cacheFilePath = Path.Combine(_cacheDirectory, OrgHierarchyDevEtl.CacheFileName);
|
||||
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentNullException>(() => OrgHierarchyDevEtl.Create(null!, cacheFilePath));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_WithEmptyCacheFilePath_ThrowsArgumentException()
|
||||
{
|
||||
// Arrange
|
||||
var mockFactory = Substitute.For<IDbConnectionFactory>();
|
||||
|
||||
// Act & Assert
|
||||
Should.Throw<ArgumentException>(() => OrgHierarchyDevEtl.Create(mockFactory, string.Empty));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_WithNonExistentCacheFile_ThrowsFileNotFoundException()
|
||||
{
|
||||
// Arrange
|
||||
var mockFactory = Substitute.For<IDbConnectionFactory>();
|
||||
var nonExistentPath = "/nonexistent/path/orghierarchy.json.zstd";
|
||||
|
||||
// Act & Assert
|
||||
Should.Throw<FileNotFoundException>(() => OrgHierarchyDevEtl.Create(mockFactory, nonExistentPath));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DevEtlRegistry_GetAvailableTables_IncludesOrgHierarchy()
|
||||
{
|
||||
// Arrange
|
||||
if (!Directory.Exists(_cacheDirectory))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var registry = new DevEtlRegistry(_connectionFactory, _cacheDirectory);
|
||||
|
||||
// Act
|
||||
var tables = registry.GetAvailableTables().ToList();
|
||||
|
||||
// Assert
|
||||
tables.ShouldContain("OrgHierarchy");
|
||||
}
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Integration tests for ProfitCenter development ETL.
|
||||
/// Requires: Local SQL Server, CACHED_DB_FILES directory with profitcenter.json.zstd
|
||||
/// </summary>
|
||||
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<DbConnectionFactory>.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<int>("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<ArgumentNullException>(() => ProfitCenterDevEtl.Create(null!, cacheFilePath));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_WithEmptyCacheFilePath_ThrowsArgumentException()
|
||||
{
|
||||
var mockFactory = Substitute.For<IDbConnectionFactory>();
|
||||
Should.Throw<ArgumentException>(() => 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");
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Integration tests for RouteMaster development ETL.
|
||||
/// </summary>
|
||||
public class RouteMasterDevEtlTests : IAsyncLifetime
|
||||
{
|
||||
private readonly string _connectionString;
|
||||
private readonly string _cacheDirectory;
|
||||
private readonly IDbConnectionFactory _connectionFactory;
|
||||
|
||||
public RouteMasterDevEtlTests()
|
||||
{
|
||||
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<DbConnectionFactory>.Instance);
|
||||
}
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
await using var connection = new SqlConnection(_connectionString);
|
||||
await connection.OpenAsync();
|
||||
await connection.ExecuteAsync("TRUNCATE TABLE dbo.RouteMaster");
|
||||
}
|
||||
|
||||
public Task DisposeAsync() => Task.CompletedTask;
|
||||
|
||||
[Fact]
|
||||
public void Create_ReturnsValidPipeline()
|
||||
{
|
||||
var cacheFilePath = Path.Combine(_cacheDirectory, RouteMasterDevEtl.CacheFileName);
|
||||
if (!File.Exists(cacheFilePath)) return;
|
||||
|
||||
var pipeline = RouteMasterDevEtl.Create(_connectionFactory, cacheFilePath);
|
||||
|
||||
pipeline.ShouldNotBeNull();
|
||||
pipeline.PipelineName.ShouldBe("RouteMaster_Dev");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Execute_LoadsData()
|
||||
{
|
||||
var cacheFilePath = Path.Combine(_cacheDirectory, RouteMasterDevEtl.CacheFileName);
|
||||
if (!File.Exists(cacheFilePath)) return;
|
||||
|
||||
var pipeline = RouteMasterDevEtl.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<int>("SELECT COUNT(*) FROM dbo.RouteMaster");
|
||||
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("RouteMaster");
|
||||
|
||||
result.Success.ShouldBeTrue(result.Error?.Message ?? "Pipeline should succeed");
|
||||
result.TotalRows.ShouldBeGreaterThan(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_WithNullConnectionFactory_ThrowsArgumentNullException()
|
||||
{
|
||||
var cacheFilePath = Path.Combine(_cacheDirectory, RouteMasterDevEtl.CacheFileName);
|
||||
Should.Throw<ArgumentNullException>(() => RouteMasterDevEtl.Create(null!, cacheFilePath));
|
||||
}
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Integration tests for WorkCenter development ETL.
|
||||
/// Requires: Local SQL Server, CACHED_DB_FILES directory with workcenter.json.zstd
|
||||
/// </summary>
|
||||
public class WorkCenterDevEtlTests : IAsyncLifetime
|
||||
{
|
||||
private readonly string _connectionString;
|
||||
private readonly string _cacheDirectory;
|
||||
private readonly IDbConnectionFactory _connectionFactory;
|
||||
|
||||
public WorkCenterDevEtlTests()
|
||||
{
|
||||
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<DbConnectionFactory>.Instance);
|
||||
}
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
await using var connection = new SqlConnection(_connectionString);
|
||||
await connection.OpenAsync();
|
||||
await connection.ExecuteAsync("TRUNCATE TABLE dbo.WorkCenter");
|
||||
}
|
||||
|
||||
public Task DisposeAsync() => Task.CompletedTask;
|
||||
|
||||
[Fact]
|
||||
public void Create_ReturnsValidPipeline()
|
||||
{
|
||||
var cacheFilePath = Path.Combine(_cacheDirectory, WorkCenterDevEtl.CacheFileName);
|
||||
if (!File.Exists(cacheFilePath)) return;
|
||||
|
||||
var pipeline = WorkCenterDevEtl.Create(_connectionFactory, cacheFilePath);
|
||||
|
||||
pipeline.ShouldNotBeNull();
|
||||
pipeline.PipelineName.ShouldBe("WorkCenter_Dev");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Execute_LoadsWorkCenterData()
|
||||
{
|
||||
var cacheFilePath = Path.Combine(_cacheDirectory, WorkCenterDevEtl.CacheFileName);
|
||||
if (!File.Exists(cacheFilePath)) return;
|
||||
|
||||
var pipeline = WorkCenterDevEtl.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<int>("SELECT COUNT(*) FROM dbo.WorkCenter");
|
||||
|
||||
count.ShouldBe((int)result.TotalRows);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Registry_RunAsync_LoadsWorkCenter()
|
||||
{
|
||||
if (!Directory.Exists(_cacheDirectory)) return;
|
||||
|
||||
var registry = new DevEtlRegistry(_connectionFactory, _cacheDirectory);
|
||||
var result = await registry.RunAsync("WorkCenter");
|
||||
|
||||
result.Success.ShouldBeTrue(result.Error?.Message ?? "Pipeline should succeed");
|
||||
result.TotalRows.ShouldBeGreaterThan(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_WithNullConnectionFactory_ThrowsArgumentNullException()
|
||||
{
|
||||
var cacheFilePath = Path.Combine(_cacheDirectory, WorkCenterDevEtl.CacheFileName);
|
||||
Should.Throw<ArgumentNullException>(() => WorkCenterDevEtl.Create(null!, cacheFilePath));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Create_WithEmptyCacheFilePath_ThrowsArgumentException()
|
||||
{
|
||||
var mockFactory = Substitute.For<IDbConnectionFactory>();
|
||||
Should.Throw<ArgumentException>(() => WorkCenterDevEtl.Create(mockFactory, string.Empty));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DevEtlRegistry_GetAvailableTables_IncludesWorkCenter()
|
||||
{
|
||||
if (!Directory.Exists(_cacheDirectory)) return;
|
||||
|
||||
var registry = new DevEtlRegistry(_connectionFactory, _cacheDirectory);
|
||||
var tables = registry.GetAvailableTables().ToList();
|
||||
|
||||
tables.ShouldContain("WorkCenter");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user