test(configmanager): expand unit test coverage to 451 tests

Add comprehensive tests for services (ConnectionTestService, RuntimeConfigValidation),
ViewModels (PipelineEditor, dialogs, transformers), and Avalonia headless UI tests
for views and forms.
This commit is contained in:
Joseph Doherty
2026-01-27 07:24:55 -05:00
parent 227a749cdf
commit 937eb66ac8
14 changed files with 4053 additions and 62 deletions
@@ -1,5 +1,6 @@
using JdeScoping.ConfigManager.Models;
using JdeScoping.ConfigManager.Services;
using JdeScoping.DataSync.Configuration;
namespace JdeScoping.ConfigManager.Tests.Services;
@@ -51,4 +52,221 @@ public class ConfigFileServiceTests
() => _sut.LoadAppSettingsAsync("/config/appsettings.json"));
ex.Message.ShouldContain("parse");
}
[Fact]
public async Task LoadPipelineAsync_WithValidJson_ReturnsPipelineConfig()
{
// Arrange
var json = """
{
"name": "TestPipeline",
"isEnabled": true,
"massSyncIntervalMinutes": 1440,
"source": {
"connectionStringName": "JDE",
"query": "SELECT * FROM test"
},
"destination": {
"connectionStringName": "LotFinder",
"tableName": "TestTable"
}
}
""";
_fileSystem.ReadAllTextAsync(Arg.Any<string>(), Arg.Any<CancellationToken>())
.Returns(Task.FromResult(json));
// Act
var result = await _sut.LoadPipelineAsync("/config/pipeline.test.json");
// Assert
result.ShouldNotBeNull();
result.Name.ShouldBe("TestPipeline");
result.IsEnabled.ShouldBeTrue();
result.MassSyncIntervalMinutes.ShouldBe(1440);
}
[Fact]
public async Task LoadPipelineAsync_WithInvalidJson_ThrowsConfigLoadException()
{
// Arrange
var json = "{ invalid json }";
_fileSystem.ReadAllTextAsync(Arg.Any<string>(), Arg.Any<CancellationToken>())
.Returns(Task.FromResult(json));
// Act & Assert
var ex = await Should.ThrowAsync<ConfigLoadException>(
() => _sut.LoadPipelineAsync("/config/pipeline.test.json"));
ex.Message.ShouldContain("parse");
}
[Fact]
public async Task SavePipelineAsync_SerializesAndWritesPipeline()
{
// Arrange
var path = "/config/pipeline.test.json";
var pipeline = new EtlPipelineConfig
{
Name = "TestPipeline",
IsEnabled = true,
MassSyncIntervalMinutes = 1440
};
string? writtenContent = null;
_fileSystem.WriteAllTextAsync(Arg.Any<string>(), Arg.Do<string>(c => writtenContent = c), Arg.Any<CancellationToken>())
.Returns(Task.CompletedTask);
// Act
await _sut.SavePipelineAsync(path, pipeline);
// Assert
await _fileSystem.Received(1).WriteAllTextAsync(path, Arg.Any<string>(), Arg.Any<CancellationToken>());
writtenContent.ShouldNotBeNull();
writtenContent.ShouldContain("TestPipeline");
}
[Fact]
public async Task LoadAllPipelinesAsync_WithMultiplePipelines_ReturnsAll()
{
// Arrange
var directory = "/config/pipelines";
var files = new[]
{
"/config/pipelines/pipeline.first.json",
"/config/pipelines/pipeline.second.json"
};
_fileSystem.DirectoryExists(directory).Returns(true);
_fileSystem.GetFilesAsync(directory, "pipeline.*.json", Arg.Any<CancellationToken>())
.Returns(Task.FromResult(files));
_fileSystem.ReadAllTextAsync(files[0], Arg.Any<CancellationToken>())
.Returns(Task.FromResult("""{"name": "First", "isEnabled": true}"""));
_fileSystem.ReadAllTextAsync(files[1], Arg.Any<CancellationToken>())
.Returns(Task.FromResult("""{"name": "Second", "isEnabled": false}"""));
_fileSystem.GetFileNameWithoutExtension(files[0]).Returns("pipeline.first");
_fileSystem.GetFileNameWithoutExtension(files[1]).Returns("pipeline.second");
// Act
var result = await _sut.LoadAllPipelinesAsync(directory);
// Assert
result.ShouldNotBeNull();
result.Count.ShouldBe(2);
result.ContainsKey("first").ShouldBeTrue();
result.ContainsKey("second").ShouldBeTrue();
result["first"].Name.ShouldBe("First");
result["second"].IsEnabled.ShouldBeFalse();
}
[Fact]
public async Task LoadAllPipelinesAsync_WithNonExistentDirectory_ReturnsEmpty()
{
// Arrange
var directory = "/config/nonexistent";
_fileSystem.DirectoryExists(directory).Returns(false);
// Act
var result = await _sut.LoadAllPipelinesAsync(directory);
// Assert
result.ShouldNotBeNull();
result.ShouldBeEmpty();
}
[Fact]
public async Task DeletePipelineFileAsync_CallsFileSystemDelete()
{
// Arrange
var path = "/config/pipeline.test.json";
// Act
await _sut.DeletePipelineFileAsync(path);
// Assert
await _fileSystem.Received(1).DeleteFileAsync(path, Arg.Any<CancellationToken>());
}
[Fact]
public async Task SaveAppSettingsAsync_SerializesAndWritesConfig()
{
// Arrange
var path = "/config/appsettings.json";
var config = new ConfigModel
{
DataSync = new DataSyncSection
{
Enabled = true,
MaxDegreeOfParallelism = 16
}
};
string? writtenContent = null;
_fileSystem.WriteAllTextAsync(Arg.Any<string>(), Arg.Do<string>(c => writtenContent = c), Arg.Any<CancellationToken>())
.Returns(Task.CompletedTask);
// Act
await _sut.SaveAppSettingsAsync(path, config);
// Assert
await _fileSystem.Received(1).WriteAllTextAsync(path, Arg.Any<string>(), Arg.Any<CancellationToken>());
writtenContent.ShouldNotBeNull();
writtenContent.ShouldContain("enabled");
writtenContent.ShouldContain("16");
}
[Fact]
public async Task LoadAllPipelinesAsync_SkipsInvalidPipelineFiles()
{
// Arrange
var directory = "/config/pipelines";
var files = new[]
{
"/config/pipelines/pipeline.valid.json",
"/config/pipelines/pipeline.invalid.json"
};
_fileSystem.DirectoryExists(directory).Returns(true);
_fileSystem.GetFilesAsync(directory, "pipeline.*.json", Arg.Any<CancellationToken>())
.Returns(Task.FromResult(files));
_fileSystem.ReadAllTextAsync(files[0], Arg.Any<CancellationToken>())
.Returns(Task.FromResult("""{"name": "Valid", "isEnabled": true}"""));
_fileSystem.ReadAllTextAsync(files[1], Arg.Any<CancellationToken>())
.Returns(Task.FromResult("{ invalid json }"));
_fileSystem.GetFileNameWithoutExtension(files[0]).Returns("pipeline.valid");
_fileSystem.GetFileNameWithoutExtension(files[1]).Returns("pipeline.invalid");
// Act
var result = await _sut.LoadAllPipelinesAsync(directory);
// Assert
result.ShouldNotBeNull();
result.Count.ShouldBe(1);
result.ContainsKey("valid").ShouldBeTrue();
}
[Fact]
public async Task LoadAllPipelinesAsync_AssignsPipelineNameFromFilename_WhenNameIsEmpty()
{
// Arrange
var directory = "/config/pipelines";
var files = new[] { "/config/pipelines/pipeline.unnamed.json" };
_fileSystem.DirectoryExists(directory).Returns(true);
_fileSystem.GetFilesAsync(directory, "pipeline.*.json", Arg.Any<CancellationToken>())
.Returns(Task.FromResult(files));
// Pipeline with empty name
_fileSystem.ReadAllTextAsync(files[0], Arg.Any<CancellationToken>())
.Returns(Task.FromResult("""{"name": "", "isEnabled": true}"""));
_fileSystem.GetFileNameWithoutExtension(files[0]).Returns("pipeline.unnamed");
// Act
var result = await _sut.LoadAllPipelinesAsync(directory);
// Assert
result.ShouldNotBeNull();
result.Count.ShouldBe(1);
result["unnamed"].Name.ShouldBe("unnamed");
}
}