From 82573df0230c36241e9fb03544d278e941e19e15 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Sat, 3 Jan 2026 09:03:14 -0500 Subject: [PATCH] feat(etl): implement SqlScriptRunner Add SqlScriptRunner class that implements IScriptRunner for executing SQL scripts against the LotFinderDB cache database. Includes constructor validation and configurable timeout support (default 1 hour). --- .../Etl/Scripts/SqlScriptRunner.cs | 37 ++++++++++++++++ .../Etl/Scripts/SqlScriptRunnerTests.cs | 44 +++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 NEW/src/JdeScoping.DataSync/Etl/Scripts/SqlScriptRunner.cs create mode 100644 NEW/tests/JdeScoping.DataSync.Tests/Etl/Scripts/SqlScriptRunnerTests.cs diff --git a/NEW/src/JdeScoping.DataSync/Etl/Scripts/SqlScriptRunner.cs b/NEW/src/JdeScoping.DataSync/Etl/Scripts/SqlScriptRunner.cs new file mode 100644 index 0000000..fc4f10b --- /dev/null +++ b/NEW/src/JdeScoping.DataSync/Etl/Scripts/SqlScriptRunner.cs @@ -0,0 +1,37 @@ +using JdeScoping.DataAccess.Interfaces; +using JdeScoping.DataSync.Etl.Contracts; + +namespace JdeScoping.DataSync.Etl.Scripts; + +public class SqlScriptRunner : IScriptRunner +{ + private readonly IDbConnectionFactory _connectionFactory; + private readonly string _sql; + private readonly int _timeoutSeconds; + + public string ScriptName { get; } + + public SqlScriptRunner( + IDbConnectionFactory connectionFactory, + string sql, + string? name = null, + int timeoutSeconds = 3600) + { + ArgumentNullException.ThrowIfNull(connectionFactory); + ArgumentException.ThrowIfNullOrWhiteSpace(sql); + + _connectionFactory = connectionFactory; + _sql = sql; + _timeoutSeconds = timeoutSeconds; + ScriptName = name ?? "SqlScript"; + } + + public async Task ExecuteAsync(CancellationToken cancellationToken = default) + { + await using var connection = await _connectionFactory.CreateLotFinderConnectionAsync(cancellationToken); + await using var command = connection.CreateCommand(); + command.CommandText = _sql; + command.CommandTimeout = _timeoutSeconds; + await command.ExecuteNonQueryAsync(cancellationToken); + } +} diff --git a/NEW/tests/JdeScoping.DataSync.Tests/Etl/Scripts/SqlScriptRunnerTests.cs b/NEW/tests/JdeScoping.DataSync.Tests/Etl/Scripts/SqlScriptRunnerTests.cs new file mode 100644 index 0000000..cb67c63 --- /dev/null +++ b/NEW/tests/JdeScoping.DataSync.Tests/Etl/Scripts/SqlScriptRunnerTests.cs @@ -0,0 +1,44 @@ +using JdeScoping.DataAccess.Interfaces; +using JdeScoping.DataSync.Etl.Scripts; +using NSubstitute; + +namespace JdeScoping.DataSync.Tests.Etl.Scripts; + +public class SqlScriptRunnerTests +{ + [Fact] + public void Constructor_SetsScriptName() + { + var factory = Substitute.For(); + var runner = new SqlScriptRunner(factory, "SELECT 1", "TestScript"); + Assert.Equal("TestScript", runner.ScriptName); + } + + [Fact] + public void Constructor_WithNullName_DefaultsToSqlScript() + { + var factory = Substitute.For(); + var runner = new SqlScriptRunner(factory, "SELECT 1"); + Assert.Equal("SqlScript", runner.ScriptName); + } + + [Fact] + public void Constructor_NullFactory_ThrowsArgumentNullException() + { + Assert.Throws(() => new SqlScriptRunner(null!, "SELECT 1")); + } + + [Fact] + public void Constructor_NullSql_ThrowsArgumentNullException() + { + var factory = Substitute.For(); + Assert.Throws(() => new SqlScriptRunner(factory, null!)); + } + + [Fact] + public void Constructor_EmptySql_ThrowsArgumentException() + { + var factory = Substitute.For(); + Assert.Throws(() => new SqlScriptRunner(factory, "")); + } +}