Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
c9fa4be
feat: implement CSVDiscoverer and catalog registry models
mnoserat Jan 30, 2026
45f8374
add missing validation for index.Entries
mnoserat Jan 30, 2026
35d5c82
add CsvConstants.cs and reslove all warnings
mnoserat Jan 30, 2026
6445bc3
change publisher to constant (the super hacker
mnoserat Jan 30, 2026
938263b
fix: configuration section is at wrong path
mnoserat Jan 30, 2026
bc8706f
fix: schema mismatch CsvCatalogRegistryIndex
mnoserat Jan 30, 2026
5853ab1
fix: Inconsistent FileCount type between CsvValidationCatalog and Csv…
mnoserat Jan 30, 2026
a272d46
feat(fix): configuration section is at wrong path
mnoserat Feb 20, 2026
58b2780
feat(fix): rephrase the AI authorship comment
mnoserat Feb 20, 2026
6d5d16e
feat(fix): Expand test coverage for CSVDiscoverer
mnoserat Feb 20, 2026
61a6156
feat(fix): SemaphoreSlim is not disposed
mnoserat Feb 28, 2026
03ef494
feat(fix): Double-checked locking pattern: consider using Volatile.Read
mnoserat Feb 28, 2026
ead2ac6
feat(fix): Minor documentation inaccuracy.
mnoserat Feb 28, 2026
5ba833a
feat(fix): Remove unused import
mnoserat Feb 28, 2026
8a41bed
feat(fix): Consider using a sentinel value for LastUpdatedAt default
mnoserat Feb 28, 2026
6e1f2cd
feat(fix): Language code should be uppercase
mnoserat Feb 28, 2026
1290c6f
feat(fix): TOCTOU race condition between File.Exists and File.ReadAll…
mnoserat Feb 28, 2026
bfda8d3
feat(fix): caching JsonSerializerOptions to avoid repeated allocation.
mnoserat Feb 28, 2026
5eb7fe8
feat(fix): Update CsvConstants.ResolverId to match issue #139 specifi…
mnoserat Feb 28, 2026
7f58082
feat(fix): match CsvCatalogRegistryEntry.cs with the actual JSON stru…
mnoserat Feb 28, 2026
b50f52c
feat(fix): adding explicit handling each valid game type
mnoserat Mar 3, 2026
317e23c
remove unused constant
mnoserat Mar 3, 2026
abf8c8f
Clarify IndexFilePath documentation semantics.
mnoserat Mar 3, 2026
2ca99cb
Switch expression now returns null for unknown GameType values inste…
mnoserat Mar 3, 2026
e896fdf
feat(fix): Language handling misses unified catalogs and emits non-ca…
mnoserat Mar 3, 2026
265ac94
feat(fix): Cancellation is swallowed and converted into a generic fai…
mnoserat Mar 3, 2026
3b55070
fix: Missing properties that exist in index.json
mnoserat Mar 3, 2026
e4d22fc
fix: CSVDiscovererTests.cs
mnoserat Mar 3, 2026
4d3363b
fix No validation for null or invalid entries. If index.Entries conta…
mnoserat Mar 3, 2026
7af0378
fix: IsActive flag is not checked when filtering entries.
mnoserat Mar 3, 2026
a78f88e
fix: remove unused config
mnoserat Mar 3, 2026
5d78c8c
fix: add null guard for _config in constructor
mnoserat Mar 3, 2026
5a5992f
fix: remove Unused using
mnoserat Mar 3, 2026
d1f34c8
Guard language collections before normalization to avoid discoverer-w…
mnoserat Mar 3, 2026
6884e87
Build manifest identity from canonical game type, not raw source casing.
mnoserat Mar 3, 2026
ba9f625
Merge remote-tracking branch 'upstream/development' into feature/CSV-…
mnoserat Mar 3, 2026
af1942f
fix: compile Failing
mnoserat Mar 3, 2026
8b00efd
fix: compile Failing wih exit code -1
mnoserat Mar 3, 2026
a725109
fix change branch url in index.json
mnoserat Mar 21, 2026
fdce2c6
refactor(csv-discovery): use index.json as single registry sourc
mnoserat Apr 24, 2026
9980a68
fix(csv-discovery): load index.json from remote source with default f…
mnoserat Apr 27, 2026
58d57e5
fix(csv-discovery): load CSV catalogs from unified source chain
mnoserat Apr 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions GenHub/GenHub.Core/Constants/ConfigurationKeys.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,16 @@ public static class ConfigurationKeys
/// Configuration key for application data path.
/// </summary>
public const string AppDataPath = "GenHub:AppDataPath";

// CSV Catalog configuration keys

/// <summary>
/// Configuration key for the CSV catalog index file path.
/// </summary>
public const string CsvCatalogIndexPath = "GenHub:CsvCatalog:IndexFilePath";

/// <summary>
/// Configuration key for the CSV validation catalogs fallback.
/// </summary>
public const string CsvValidationCatalogs = "GenHub:CsvCatalog:ValidationCatalogs";
}
7 changes: 7 additions & 0 deletions GenHub/GenHub.Core/Interfaces/Common/IAppConfiguration.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using GenHub.Core.Models.Content;
using GenHub.Core.Models.Enums;
using Microsoft.Extensions.Logging;

Expand Down Expand Up @@ -81,4 +82,10 @@ public interface IAppConfiguration
/// <summary>Gets the maximum allowed download buffer size in bytes.</summary>
/// <returns>The maximum buffer size in bytes.</returns>
int GetMaxDownloadBufferSizeBytes();

/// <summary>
/// Gets the CSV catalog configuration.
/// </summary>
/// <returns>The CSV catalog configuration.</returns>
CsvCatalogConfiguration GetCsvCatalogConfiguration();
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using GenHub.Core.Models.Common;
using GenHub.Core.Models.Content;
using GenHub.Core.Models.Enums;
using GenHub.Core.Models.Storage;

Expand Down Expand Up @@ -155,4 +156,10 @@ public interface IConfigurationProviderService
/// </summary>
/// <returns>The logs directory path.</returns>
string GetLogsPath();

/// <summary>
/// Gets the CSV catalog configuration.
/// </summary>
/// <returns>The CSV catalog configuration.</returns>
CsvCatalogConfiguration GetCsvCatalogConfiguration();
}
53 changes: 53 additions & 0 deletions GenHub/GenHub.Core/Models/Content/CsvCatalogConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System.Collections.Generic;

namespace GenHub.Core.Models.Content;

/// <summary>
/// Configuration for CSV Catalog discovery.
/// Binds to "CsvValidationCatalogs" and "CsvCatalogIndexPath" in configuration.
/// </summary>
Comment thread
mnoserat marked this conversation as resolved.
public class CsvCatalogConfiguration
{
/// <summary>
/// Gets or sets the list of fallback validation catalogs defined in configuration.
/// Used when index.json cannot be reached.
/// </summary>
public List<CsvValidationCatalog> CsvValidationCatalogs { get; set; } = [];
Comment thread
mnoserat marked this conversation as resolved.
Outdated

/// <summary>
/// Gets or sets the path to the index.json file relative to the docs directory.
/// </summary>
Comment thread
mnoserat marked this conversation as resolved.
Outdated
public string IndexFilePath { get; set; } = "docs/GameInstallationFilesRegistry/index.json";
Comment thread
mnoserat marked this conversation as resolved.
Outdated
Comment thread
mnoserat marked this conversation as resolved.
Outdated
}

/// <summary>
/// Represents a CSV catalog defined in appsettings.json.
/// Matches the structure of CsvCatalogRegistryEntry but optimized for config binding.
/// </summary>
public class CsvValidationCatalog

Check warning on line 27 in GenHub/GenHub.Core/Models/Content/CsvCatalogConfiguration.cs

View workflow job for this annotation

GitHub Actions / Build Windows

Check warning on line 27 in GenHub/GenHub.Core/Models/Content/CsvCatalogConfiguration.cs

View workflow job for this annotation

GitHub Actions / Build Windows

Check warning on line 27 in GenHub/GenHub.Core/Models/Content/CsvCatalogConfiguration.cs

View workflow job for this annotation

GitHub Actions / Build Linux

Check warning on line 27 in GenHub/GenHub.Core/Models/Content/CsvCatalogConfiguration.cs

View workflow job for this annotation

GitHub Actions / Build Linux

{
/// <summary>
/// Gets or sets the URL to the CSV file.
/// </summary>
public string Url { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the game type.
/// </summary>
public string GameType { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the version string.
/// </summary>
public string Version { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the supported languages.
/// </summary>
public List<string> SupportedLanguages { get; set; } = [];

/// <summary>
/// Gets or sets the file count.
/// </summary>
public int FileCount { get; set; }
}
Comment thread
mnoserat marked this conversation as resolved.
Outdated
57 changes: 57 additions & 0 deletions GenHub/GenHub.Core/Models/Content/CsvCatalogRegistryIndex.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;

namespace GenHub.Core.Models.Content;

/// <summary>
/// Index of available CSV catalog registries.
/// Represents the structure of the docs/GameInstallationFilesRegistry/index.json file.
/// </summary>
public class CsvCatalogRegistryIndex
{
/// <summary>
/// Gets or sets the schema version.
/// </summary>
public int Version { get; set; } = 1;

/// <summary>
/// Gets or sets when the index was last updated.
/// </summary>
public DateTime LastUpdatedAt { get; set; } = DateTime.UtcNow;

/// <summary>
/// Gets or sets the list of available catalog entries.
/// </summary>
public List<CsvCatalogRegistryEntry> Entries { get; set; } = [];
Comment thread
mnoserat marked this conversation as resolved.
}

/// <summary>
/// Represents a single CSV catalog entry in the registry.
/// </summary>
public class CsvCatalogRegistryEntry

Check warning on line 31 in GenHub/GenHub.Core/Models/Content/CsvCatalogRegistryIndex.cs

View workflow job for this annotation

GitHub Actions / Build Windows

Check warning on line 31 in GenHub/GenHub.Core/Models/Content/CsvCatalogRegistryIndex.cs

View workflow job for this annotation

GitHub Actions / Build Windows

Check warning on line 31 in GenHub/GenHub.Core/Models/Content/CsvCatalogRegistryIndex.cs

View workflow job for this annotation

GitHub Actions / Build Linux

Check warning on line 31 in GenHub/GenHub.Core/Models/Content/CsvCatalogRegistryIndex.cs

View workflow job for this annotation

GitHub Actions / Build Linux

{
/// <summary>
/// Gets or sets the URL to the CSV file.
/// </summary>
public string Url { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the game type (e.g., "Generals", "ZeroHour").
/// </summary>
public string GameType { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the game version (e.g., "1.08", "1.04").
/// </summary>
public string Version { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the list of supported languages in this CSV.
/// </summary>
public List<string> SupportedLanguages { get; set; } = [];

/// <summary>
/// Gets or sets the optional file count in the CSV.
/// </summary>
public int? FileCount { get; set; }
}
Comment thread
mnoserat marked this conversation as resolved.
Outdated
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using FluentAssertions;
using GenHub.Core.Interfaces.Common;
using GenHub.Core.Models.Content;
using GenHub.Core.Models.Enums;
using GenHub.Core.Models.Results.Content;
using GenHub.Features.Content.Services.ContentDiscoverers;
using Microsoft.Extensions.Logging;
using Moq;
using Xunit;

namespace GenHub.Tests.Core.Features.Content;

// the whole file is made by AI
Comment thread
mnoserat marked this conversation as resolved.
Outdated
Comment thread
greptile-apps[bot] marked this conversation as resolved.
Outdated

/// <summary>
/// Unit tests for <see cref="CSVDiscoverer"/>.
/// </summary>
public class CSVDiscovererTests
{
/// <summary>
/// Verifies that <see cref="CSVDiscoverer.DiscoverAsync"/> returns a valid result when querying for Generals.
/// </summary>
/// <returns>A task representing the asynchronous test operation.</returns>
[Fact]
public async Task DiscoverAsync_WithGeneralsQuery_ReturnsValidResult()
{
// Arrange
var config = new CsvCatalogConfiguration
{
IndexFilePath = "non-existent-index.json",
CsvValidationCatalogs = new List<CsvValidationCatalog>
{
new CsvValidationCatalog
{
Url = "https://example.com/generals.csv",
GameType = "Generals",
Version = "1.08",
SupportedLanguages = new List<string> { "en" },
Comment thread
mnoserat marked this conversation as resolved.
Outdated
FileCount = 100,
},
},
};

var mockConfig = new Mock<IConfigurationProviderService>();
mockConfig.Setup(o => o.GetCsvCatalogConfiguration()).Returns(config);

var discoverer = new CSVDiscoverer(
Mock.Of<ILogger<CSVDiscoverer>>(),
mockConfig.Object);

var query = new ContentSearchQuery { TargetGame = GameType.Generals };

// Act
var result = await discoverer.DiscoverAsync(query);

// Assert
result.Success.Should().BeTrue();
result.Data.Should().NotBeNull();
result.Data!.Items.Should().NotBeEmpty();
result.Data.Items.First().ResolverMetadata.Should().ContainKey("csvUrl");
result.Data.Items.First().ResolverMetadata["csvUrl"].Should().Be("https://example.com/generals.csv");
}

/// <summary>
/// Verifies that <see cref="CSVDiscoverer.DiscoverAsync"/> returns an empty result when the content type is not GameInstallation.
/// </summary>
/// <returns>A task representing the asynchronous test operation.</returns>
[Fact]
public async Task DiscoverAsync_WithNonGameInstallationContentType_ReturnsEmptyResult()
{
// Arrange
var mockConfig = new Mock<IConfigurationProviderService>();
mockConfig.Setup(o => o.GetCsvCatalogConfiguration()).Returns(new CsvCatalogConfiguration());

var discoverer = new CSVDiscoverer(
Mock.Of<ILogger<CSVDiscoverer>>(),
mockConfig.Object);

var query = new ContentSearchQuery { ContentType = GenHub.Core.Models.Enums.ContentType.Map };

// Act
var result = await discoverer.DiscoverAsync(query);

// Assert
result.Success.Should().BeTrue();
result.Data.Should().NotBeNull();
result.Data!.Items.Should().BeEmpty();
}

Comment thread
mnoserat marked this conversation as resolved.
}

7 changes: 7 additions & 0 deletions GenHub/GenHub/Common/Services/AppConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using GenHub.Core.Constants;
using GenHub.Core.Interfaces.Common;
using GenHub.Core.Models.Enums;
using GenHub.Core.Models.Content;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;

Expand Down Expand Up @@ -226,4 +227,10 @@ public string GetConfiguredDataPath()
? configured
: Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), AppConstants.AppName);
}

/// <inheritdoc />
public CsvCatalogConfiguration GetCsvCatalogConfiguration()
{
return _configuration?.GetSection("GenHub:CsvCatalog").Get<CsvCatalogConfiguration>() ?? new CsvCatalogConfiguration();
}
}
4 changes: 4 additions & 0 deletions GenHub/GenHub/Common/Services/ConfigurationProviderService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using GenHub.Core.Constants;
using GenHub.Core.Interfaces.Common;
using GenHub.Core.Models.Common;
using GenHub.Core.Models.Content;
using GenHub.Core.Models.Enums;
using GenHub.Core.Models.Storage;
using Microsoft.Extensions.Logging;
Expand Down Expand Up @@ -342,6 +343,9 @@ public string GetLogsPath()
DirectoryNames.Logs.ToLowerInvariant());
}

/// <inheritdoc />
public CsvCatalogConfiguration GetCsvCatalogConfiguration() => _appConfig.GetCsvCatalogConfiguration();

private void MigrateContentDirectory()
{
try
Expand Down
Loading
Loading