50 lines
2.7 KiB
C#
50 lines
2.7 KiB
C#
using System.Text;
|
|
using System.Xml;
|
|
using Ludarium.Application;
|
|
using Ludarium.Domain;
|
|
|
|
namespace Ludarium.UnitTests;
|
|
|
|
public sealed class CatalogImportTests
|
|
{
|
|
[Fact]
|
|
public async Task LogiqxImportIsVersionedStreamingAndDeterministic()
|
|
{
|
|
const string xml = """
|
|
<?xml version="1.0"?>
|
|
<datafile><header><name>Synthetic</name></header><game name="Example Game">
|
|
<rom name="example.nes" size="16" crc="CBF43926" sha1="a9993e364706816aba3e25717850c26c9cd0d89d" sha256="ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" />
|
|
<rom name="ignored.bin" size="4" />
|
|
</game></datafile>
|
|
""";
|
|
var store = new CapturingCatalogStore();
|
|
var importer = new LogiqxCatalogImporter(store);
|
|
await using var input = new MemoryStream(Encoding.UTF8.GetBytes(xml));
|
|
var source = await importer.ImportLogiqxAsync("Synthetic DAT", "2026.07", input, CancellationToken.None);
|
|
Assert.Equal("Ready", source.State); Assert.Equal(1, source.EntryCount); Assert.Equal(64, source.Sha256?.Length);
|
|
var entry = Assert.Single(store.Entries);
|
|
Assert.Equal("Example Game", entry.GameName); Assert.Equal("cbf43926", entry.Crc32);
|
|
Assert.Equal(1, store.MatchCalls);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task LogiqxImportRejectsDocumentTypes()
|
|
{
|
|
const string xml = "<!DOCTYPE datafile [<!ENTITY x SYSTEM 'file:///etc/passwd'>]><datafile><game name='x'><rom name='x' crc='00000000'/></game></datafile>";
|
|
var importer = new LogiqxCatalogImporter(new CapturingCatalogStore());
|
|
await using var input = new MemoryStream(Encoding.UTF8.GetBytes(xml));
|
|
await Assert.ThrowsAsync<XmlException>(() => importer.ImportLogiqxAsync("Hostile", "1", input, CancellationToken.None));
|
|
}
|
|
|
|
private sealed class CapturingCatalogStore : ICatalogStore
|
|
{
|
|
public List<CatalogEntry> Entries { get; } = [];
|
|
public int MatchCalls { get; private set; }
|
|
public Task UpsertCatalogSourceAsync(CatalogSource source, CancellationToken cancellationToken) => Task.CompletedTask;
|
|
public Task UpsertCatalogEntriesAsync(IReadOnlyList<CatalogEntry> entries, CancellationToken cancellationToken) { Entries.AddRange(entries); return Task.CompletedTask; }
|
|
public Task<IReadOnlyList<CatalogSource>> ListCatalogSourcesAsync(CancellationToken cancellationToken) => Task.FromResult<IReadOnlyList<CatalogSource>>([]);
|
|
public Task<int> MatchCatalogAsync(Guid sourceId, CancellationToken cancellationToken) { MatchCalls++; return Task.FromResult(Entries.Count); }
|
|
public Task<IReadOnlyList<MatchCandidate>> ListMatchCandidatesAsync(CancellationToken cancellationToken) => Task.FromResult<IReadOnlyList<MatchCandidate>>([]);
|
|
}
|
|
}
|