diff --git a/NEW/tests/JdeScoping.DataSync.Tests/DevEtl/BranchDevEtlTests.cs b/NEW/tests/JdeScoping.DataSync.Tests/DevEtl/BranchDevEtlTests.cs
new file mode 100644
index 0000000..1e49f40
--- /dev/null
+++ b/NEW/tests/JdeScoping.DataSync.Tests/DevEtl/BranchDevEtlTests.cs
@@ -0,0 +1,177 @@
+using Dapper;
+using JdeScoping.DataAccess;
+using JdeScoping.DataAccess.Interfaces;
+using JdeScoping.DataSync.DevEtl;
+using Microsoft.Data.SqlClient;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging.Abstractions;
+using NSubstitute;
+using Shouldly;
+
+namespace JdeScoping.DataSync.Tests.DevEtl;
+
+///
+/// Integration tests for Branch development ETL.
+/// Requires: Local SQL Server, CACHED_DB_FILES directory with branch.json.zstd
+///
+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("LotFinder")
+ ?? throw new InvalidOperationException("LotFinder connection string not configured.");
+
+ _cacheDirectory = config["DevEtl:CacheDirectory"]
+ ?? "/Users/dohertj2/Desktop/JdeScopingTool/CACHED_DB_FILES";
+
+ _connectionFactory = new DbConnectionFactory(config, NullLogger.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("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(() => BranchDevEtl.Create(null!, cacheFilePath));
+ }
+
+ [Fact]
+ public void Create_WithEmptyCacheFilePath_ThrowsArgumentException()
+ {
+ // Arrange
+ var mockFactory = Substitute.For();
+
+ // Act & Assert
+ Should.Throw(() => BranchDevEtl.Create(mockFactory, string.Empty));
+ }
+
+ [Fact]
+ public void Create_WithNonExistentCacheFile_ThrowsFileNotFoundException()
+ {
+ // Arrange
+ var mockFactory = Substitute.For();
+ var nonExistentPath = "/nonexistent/path/branch.json.zstd";
+
+ // Act & Assert
+ Should.Throw(() => BranchDevEtl.Create(mockFactory, nonExistentPath));
+ }
+
+ [Fact]
+ public void DevEtlRegistry_WithNonExistentCacheDirectory_ThrowsDirectoryNotFoundException()
+ {
+ // Arrange
+ var mockFactory = Substitute.For();
+ var nonExistentPath = "/nonexistent/cache/directory";
+
+ // Act & Assert
+ Should.Throw(() => 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");
+ }
+}
diff --git a/NEW/tests/JdeScoping.DataSync.Tests/JdeScoping.DataSync.Tests.csproj b/NEW/tests/JdeScoping.DataSync.Tests/JdeScoping.DataSync.Tests.csproj
index 352d954..7dfd001 100644
--- a/NEW/tests/JdeScoping.DataSync.Tests/JdeScoping.DataSync.Tests.csproj
+++ b/NEW/tests/JdeScoping.DataSync.Tests/JdeScoping.DataSync.Tests.csproj
@@ -25,6 +25,10 @@
+
+
+
+
@@ -36,4 +40,8 @@
+
+
+
+
diff --git a/NEW/tests/JdeScoping.DataSync.Tests/appsettings.json b/NEW/tests/JdeScoping.DataSync.Tests/appsettings.json
new file mode 100644
index 0000000..865fbd9
--- /dev/null
+++ b/NEW/tests/JdeScoping.DataSync.Tests/appsettings.json
@@ -0,0 +1,8 @@
+{
+ "ConnectionStrings": {
+ "LotFinder": "Server=localhost,1434;Database=ScopingTool;User Id=scopingapp;Password=Sc0ping@pp_Dev#2024;TrustServerCertificate=true"
+ },
+ "DevEtl": {
+ "CacheDirectory": "/Users/dohertj2/Desktop/JdeScopingTool/CACHED_DB_FILES"
+ }
+}