diff --git a/GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Manifest/ManifestDiscoveryServiceTests.cs b/GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Manifest/ManifestDiscoveryServiceTests.cs index 6cf445c5d..81b6c547b 100644 --- a/GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Manifest/ManifestDiscoveryServiceTests.cs +++ b/GenHub/GenHub.Tests/GenHub.Tests.Core/Features/Manifest/ManifestDiscoveryServiceTests.cs @@ -4,6 +4,7 @@ using GenHub.Features.Manifest; using Microsoft.Extensions.Logging; using Moq; +using System.Text.Json; using ContentType = GenHub.Core.Models.Enums.ContentType; namespace GenHub.Tests.Features.Manifest; @@ -11,7 +12,7 @@ namespace GenHub.Tests.Features.Manifest; /// /// Unit tests for the class. /// -public class ManifestDiscoveryServiceTests +public class ManifestDiscoveryServiceTests : IDisposable { /// /// Mock logger for the manifest discovery service. @@ -28,6 +29,11 @@ public class ManifestDiscoveryServiceTests /// private readonly ManifestDiscoveryService _discoveryService; + /// + /// Temporary directory used for filesystem discovery tests. + /// + private readonly string _tempDirectory; + /// /// Initializes a new instance of the class. /// @@ -36,6 +42,7 @@ public ManifestDiscoveryServiceTests() _loggerMock = new Mock>(); _cacheMock = new Mock(); _discoveryService = new ManifestDiscoveryService(_loggerMock.Object, _cacheMock.Object); + _tempDirectory = Directory.CreateTempSubdirectory("GenHub.ManifestDiscoveryTests.").FullName; } /// @@ -84,6 +91,58 @@ public void GetCompatibleManifests_FiltersCorrectly() Assert.Single(zeroHourCompatible); } + /// + /// Tests that manifest discovery finds JSON manifests in nested directories and ignores non-JSON files. + /// + /// A representing the asynchronous test operation. + [Fact] + public async Task DiscoverManifestsAsync_DiscoversNestedJsonManifest_AndIgnoresNonJsonFile() + { + // Arrange + const string nestedManifestId = "1.0.genhub.mod.nested"; + const string ignoredManifestId = "1.0.genhub.mod.ignored"; + var nestedDirectory = Path.Combine(_tempDirectory, "content", "manifests"); + Directory.CreateDirectory(nestedDirectory); + await File.WriteAllTextAsync( + Path.Combine(nestedDirectory, "nested.json"), + SerializeManifest(nestedManifestId)); + await File.WriteAllTextAsync( + Path.Combine(_tempDirectory, "ignored.txt"), + SerializeManifest(ignoredManifestId)); + + // Act + var manifests = await _discoveryService.DiscoverManifestsAsync([_tempDirectory]); + + // Assert + Assert.Single(manifests); + Assert.Contains(nestedManifestId, manifests); + Assert.DoesNotContain(ignoredManifestId, manifests); + } + + /// + /// Tests that a malformed JSON file does not prevent other nested manifests from being discovered. + /// + /// A representing the asynchronous test operation. + [Fact] + public async Task DiscoverManifestsAsync_WithMalformedJson_ContinuesDiscoveringNestedManifest() + { + // Arrange + const string nestedManifestId = "1.0.genhub.mod.valid"; + var nestedDirectory = Path.Combine(_tempDirectory, "content", "manifests"); + Directory.CreateDirectory(nestedDirectory); + await File.WriteAllTextAsync( + Path.Combine(nestedDirectory, "valid.json"), + SerializeManifest(nestedManifestId)); + await File.WriteAllTextAsync(Path.Combine(_tempDirectory, "malformed.json"), "{ invalid json"); + + // Act + var manifests = await _discoveryService.DiscoverManifestsAsync([_tempDirectory]); + + // Assert + var manifest = Assert.Single(manifests); + Assert.Equal(nestedManifestId, manifest.Key); + } + /// /// Tests that ValidateDependencies returns false when a required dependency is missing. /// @@ -156,4 +215,27 @@ public void ValidateDependencies_ReturnsTrue_WhenNoDependencies() // Assert Assert.True(result); } -} \ No newline at end of file + + /// + /// Deletes temporary files created by filesystem discovery tests. + /// + public void Dispose() + { + try + { + if (Directory.Exists(_tempDirectory)) + { + Directory.Delete(_tempDirectory, true); + } + } + catch (Exception ex) when (ex is IOException or UnauthorizedAccessException) + { + // Best-effort cleanup should not fail an otherwise successful test. + } + } + + private static string SerializeManifest(string id) + { + return JsonSerializer.Serialize(new ContentManifest { Id = ManifestId.Create(id) }); + } +} diff --git a/GenHub/GenHub/Features/Manifest/ManifestDiscoveryService.cs b/GenHub/GenHub/Features/Manifest/ManifestDiscoveryService.cs index 6f9350c32..9691169ba 100644 --- a/GenHub/GenHub/Features/Manifest/ManifestDiscoveryService.cs +++ b/GenHub/GenHub/Features/Manifest/ManifestDiscoveryService.cs @@ -61,7 +61,7 @@ public async Task> DiscoverManifestsAsync( foreach (var directory in searchDirectories.Where(Directory.Exists)) { logger.LogInformation("Scanning directory for manifests: {Directory}", directory); - var manifestFiles = Directory.EnumerateFiles(directory, "FileTypes.JsonFilePattern", SearchOption.AllDirectories); + var manifestFiles = Directory.EnumerateFiles(directory, FileTypes.JsonFilePattern, SearchOption.AllDirectories); foreach (var manifestFile in manifestFiles) { try @@ -242,4 +242,4 @@ private async Task DiscoverEmbeddedManifestsAsync(CancellationToken cancellation } } } -} \ No newline at end of file +}