using JdeScoping.ConfigManager.Core.Services; namespace JdeScoping.ConfigManager.Core.Tests.Services; public class AutoDiscoveryServiceTests : IDisposable { private readonly IFileSystem _fileSystem; private readonly AutoDiscoveryService _sut; private readonly string? _originalEnvVar; public AutoDiscoveryServiceTests() { _fileSystem = Substitute.For(); _sut = new AutoDiscoveryService(_fileSystem); // Save original env var to restore later _originalEnvVar = Environment.GetEnvironmentVariable("JDESCOPING_CONFIG_PATH"); // Set up default Combine behavior to work like Path.Combine _fileSystem.Combine(Arg.Any()).Returns(callInfo => { var paths = callInfo.Arg(); return Path.Combine(paths); }); } public void Dispose() { // Restore original env var Environment.SetEnvironmentVariable("JDESCOPING_CONFIG_PATH", _originalEnvVar); } [Fact] public async Task FindConfigFolderAsync_WhenEnvVarSet_ReturnsEnvPath() { // Arrange Environment.SetEnvironmentVariable("JDESCOPING_CONFIG_PATH", "/custom/config"); _fileSystem.DirectoryExists("/custom/config").Returns(true); _fileSystem.FileExists(Arg.Is(s => s.Contains("appsettings.json") && s.Contains("/custom/config"))).Returns(true); // Act var result = await _sut.FindConfigFolderAsync(); // Assert result.ShouldBe("/custom/config"); } [Fact] public async Task FindConfigFolderAsync_WhenEnvVarSetButDirectoryInvalid_SearchesOtherLocations() { // Arrange Environment.SetEnvironmentVariable("JDESCOPING_CONFIG_PATH", "/invalid/config"); _fileSystem.DirectoryExists(Arg.Any()).Returns(false); _fileSystem.FileExists(Arg.Any()).Returns(false); // Act var result = await _sut.FindConfigFolderAsync(); // Assert result.ShouldBeNull(); } [Fact] public async Task FindConfigFolderAsync_WhenEnvVarSetButNoAppSettings_SearchesOtherLocations() { // Arrange Environment.SetEnvironmentVariable("JDESCOPING_CONFIG_PATH", "/custom/config"); // Directory exists but no appsettings.json _fileSystem.DirectoryExists("/custom/config").Returns(true); _fileSystem.FileExists(Arg.Any()).Returns(false); // All other locations also don't have config _fileSystem.DirectoryExists(Arg.Is(s => s != "/custom/config")).Returns(false); // Act var result = await _sut.FindConfigFolderAsync(); // Assert result.ShouldBeNull(); } [Fact] public async Task FindConfigFolderAsync_WhenNotFound_ReturnsNull() { // Arrange Environment.SetEnvironmentVariable("JDESCOPING_CONFIG_PATH", null); _fileSystem.DirectoryExists(Arg.Any()).Returns(false); _fileSystem.FileExists(Arg.Any()).Returns(false); // Act var result = await _sut.FindConfigFolderAsync(); // Assert result.ShouldBeNull(); } [Fact] public async Task FindConfigFolderAsync_WhenExeDirectoryHasConfig_ReturnsExeDirectory() { // Arrange Environment.SetEnvironmentVariable("JDESCOPING_CONFIG_PATH", null); var exeDir = AppContext.BaseDirectory; _fileSystem.DirectoryExists(exeDir).Returns(true); _fileSystem.FileExists(Arg.Is(s => s.Contains(exeDir) && s.Contains("appsettings.json"))).Returns(true); // Act var result = await _sut.FindConfigFolderAsync(); // Assert result.ShouldBe(exeDir); } [Fact] public async Task FindConfigFolderAsync_WhenHostDirectoryHasConfig_ReturnsHostDirectory() { // Arrange Environment.SetEnvironmentVariable("JDESCOPING_CONFIG_PATH", null); var exeDir = AppContext.BaseDirectory; var hostDir = Path.Combine(exeDir, "..", "JdeScoping.Host"); // Exe directory doesn't have config _fileSystem.DirectoryExists(exeDir).Returns(false); // Host directory has config _fileSystem.DirectoryExists(hostDir).Returns(true); _fileSystem.FileExists(Arg.Is(s => s.Contains("JdeScoping.Host") && s.Contains("appsettings.json"))).Returns(true); // Other directories don't have config _fileSystem.DirectoryExists(Arg.Is(s => s != exeDir && s != hostDir)).Returns(false); // Act var result = await _sut.FindConfigFolderAsync(); // Assert result.ShouldBe(hostDir); } [Fact] public async Task FindConfigFolderAsync_WhenUserConfigDirectoryHasConfig_ReturnsUserConfigDirectory() { // Arrange Environment.SetEnvironmentVariable("JDESCOPING_CONFIG_PATH", null); var exeDir = AppContext.BaseDirectory; var hostDir = Path.Combine(exeDir, "..", "JdeScoping.Host"); // Determine user config path based on OS string userConfigDir; if (OperatingSystem.IsWindows()) { var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); userConfigDir = Path.Combine(localAppData, "JdeScoping"); } else { var home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); userConfigDir = Path.Combine(home, ".jdescoping"); } // Exe directory doesn't have config _fileSystem.DirectoryExists(exeDir).Returns(false); // Host directory doesn't have config _fileSystem.DirectoryExists(hostDir).Returns(false); // User config directory has config _fileSystem.DirectoryExists(userConfigDir).Returns(true); _fileSystem.FileExists(Arg.Is(s => s.Contains(userConfigDir) && s.Contains("appsettings.json"))).Returns(true); // Act var result = await _sut.FindConfigFolderAsync(); // Assert result.ShouldBe(userConfigDir); } [Fact] public async Task FindConfigFolderAsync_SearchesPrioritizedOrder() { // Arrange - all locations have config, but env var should be returned first Environment.SetEnvironmentVariable("JDESCOPING_CONFIG_PATH", "/env/config"); var exeDir = AppContext.BaseDirectory; // Both env var path and exe dir have valid config _fileSystem.DirectoryExists("/env/config").Returns(true); _fileSystem.DirectoryExists(exeDir).Returns(true); _fileSystem.FileExists(Arg.Any()).Returns(true); // Act var result = await _sut.FindConfigFolderAsync(); // Assert - should return env var path, not exe dir result.ShouldBe("/env/config"); } [Fact] public async Task FindConfigFolderAsync_WithCancellationToken_DoesNotThrow() { // Arrange Environment.SetEnvironmentVariable("JDESCOPING_CONFIG_PATH", "/custom/config"); _fileSystem.DirectoryExists("/custom/config").Returns(true); _fileSystem.FileExists(Arg.Any()).Returns(true); using var cts = new CancellationTokenSource(); // Act var result = await _sut.FindConfigFolderAsync(cts.Token); // Assert result.ShouldBe("/custom/config"); } }