This commit is contained in:
Chris Kruining 2026-02-05 11:34:57 +01:00
parent 0801ceee6a
commit 54ba7496c6
No known key found for this signature in database
GPG key ID: EB894A3560CCCAD2
19 changed files with 1765 additions and 591 deletions

View file

@ -0,0 +1,304 @@
using Microsoft.Data.Sqlite;
using Scry.Core.Data;
using Scry.Core.Models;
using Xunit;
namespace Scry.Tests;
public class CardDatabaseTests : IDisposable
{
private readonly string _dbPath;
private readonly CardDatabase _database;
public CardDatabaseTests()
{
_dbPath = Path.Combine(Path.GetTempPath(), $"scry_test_{Guid.NewGuid()}.db");
_database = new CardDatabase(_dbPath);
}
[Fact]
public async Task InsertCard_ThenRetrieve_ReturnsMatch()
{
// First insert oracle and set (foreign keys)
var oracle = new Oracle
{
Id = "oracle-1",
Name = "Test Card",
ManaCost = "{1}{U}",
TypeLine = "Creature"
};
await _database.InsertOracleAsync(oracle);
var set = new Set
{
Id = "set-1",
Code = "TST",
Name = "Test Set"
};
await _database.InsertSetAsync(set);
var card = new Card
{
Id = "test-id",
OracleId = "oracle-1",
SetId = "set-1",
SetCode = "TST",
Name = "Test Card",
CollectorNumber = "1",
Hash = new byte[] { 0x01, 0x02, 0x03 },
ImageUri = "https://example.com/image.jpg"
};
await _database.InsertCardAsync(card);
var retrieved = await _database.GetCardByIdAsync("test-id");
Assert.NotNull(retrieved);
Assert.Equal("Test Card", retrieved.Name);
Assert.Equal("TST", retrieved.SetCode);
Assert.Equal(card.Hash, retrieved.Hash);
}
[Fact]
public async Task InsertCardBatch_InsertsAllCards()
{
// Insert oracle first
var oracle = new Oracle { Id = "oracle-batch", Name = "Batch Card" };
await _database.InsertOracleAsync(oracle);
var set = new Set { Id = "set-batch", Code = "TST", Name = "Test Set" };
await _database.InsertSetAsync(set);
var cards = Enumerable.Range(0, 100).Select(i => new Card
{
Id = $"card-{i}",
OracleId = "oracle-batch",
SetId = "set-batch",
SetCode = "TST",
Name = $"Card {i}",
Hash = new byte[] { (byte)i }
}).ToList();
await _database.InsertCardBatchAsync(cards);
var count = await _database.GetCardCountAsync();
Assert.Equal(100, count);
}
[Fact]
public async Task GetAllCards_ReturnsAllCards()
{
var oracle = new Oracle { Id = "oracle-all", Name = "All Card" };
await _database.InsertOracleAsync(oracle);
var set = new Set { Id = "set-all", Code = "TST", Name = "Test Set" };
await _database.InsertSetAsync(set);
var cards = Enumerable.Range(0, 10).Select(i => new Card
{
Id = $"card-{i}",
OracleId = "oracle-all",
SetId = "set-all",
SetCode = "TST",
Name = $"Card {i}",
Hash = new byte[] { (byte)i }
}).ToList();
await _database.InsertCardBatchAsync(cards);
var all = await _database.GetAllCardsAsync();
Assert.Equal(10, all.Count);
}
[Fact]
public async Task GetCardsByOracleId_ReturnsAllPrintings()
{
var oracle = new Oracle { Id = "oracle-multi", Name = "Multi Print Card" };
await _database.InsertOracleAsync(oracle);
var set1 = new Set { Id = "set-1", Code = "S1", Name = "Set 1" };
var set2 = new Set { Id = "set-2", Code = "S2", Name = "Set 2" };
await _database.InsertSetAsync(set1);
await _database.InsertSetAsync(set2);
var cards = new[]
{
new Card { Id = "print-1", OracleId = "oracle-multi", SetId = "set-1", SetCode = "S1", Name = "Multi Print Card", Hash = new byte[] { 0x01 } },
new Card { Id = "print-2", OracleId = "oracle-multi", SetId = "set-2", SetCode = "S2", Name = "Multi Print Card", Hash = new byte[] { 0x02 } },
};
await _database.InsertCardBatchAsync(cards);
var printings = await _database.GetCardsByOracleIdAsync("oracle-multi");
Assert.Equal(2, printings.Count);
}
[Fact]
public async Task Metadata_SetAndGet()
{
await _database.SetMetadataAsync("test_key", "test_value");
var value = await _database.GetMetadataAsync("test_key");
Assert.Equal("test_value", value);
}
[Fact]
public async Task ClearCards_RemovesAllCards()
{
var oracle = new Oracle { Id = "oracle-clear", Name = "Clear Card" };
await _database.InsertOracleAsync(oracle);
var set = new Set { Id = "set-clear", Code = "TST", Name = "Test Set" };
await _database.InsertSetAsync(set);
var cards = Enumerable.Range(0, 10).Select(i => new Card
{
Id = $"card-{i}",
OracleId = "oracle-clear",
SetId = "set-clear",
SetCode = "TST",
Name = $"Card {i}",
Hash = new byte[] { (byte)i }
}).ToList();
await _database.InsertCardBatchAsync(cards);
await _database.ClearCardsAsync();
var count = await _database.GetCardCountAsync();
Assert.Equal(0, count);
}
[Fact]
public async Task InsertCard_DuplicateId_Updates()
{
var oracle = new Oracle { Id = "oracle-dup", Name = "Dup Card" };
await _database.InsertOracleAsync(oracle);
var set = new Set { Id = "set-dup", Code = "TST", Name = "Test Set" };
await _database.InsertSetAsync(set);
var card1 = new Card
{
Id = "duplicate-id",
OracleId = "oracle-dup",
SetId = "set-dup",
SetCode = "TST",
Name = "Original Name",
Hash = new byte[] { 0x01 }
};
var card2 = new Card
{
Id = "duplicate-id",
OracleId = "oracle-dup",
SetId = "set-dup",
SetCode = "TST",
Name = "Updated Name",
Hash = new byte[] { 0x02 }
};
await _database.InsertCardAsync(card1);
await _database.InsertCardAsync(card2);
var retrieved = await _database.GetCardByIdAsync("duplicate-id");
Assert.NotNull(retrieved);
Assert.Equal("Updated Name", retrieved.Name);
Assert.Equal(new byte[] { 0x02 }, retrieved.Hash);
}
[Fact]
public async Task InsertOracle_ThenRetrieveByName()
{
var oracle = new Oracle
{
Id = "oracle-name",
Name = "Lightning Bolt",
ManaCost = "{R}",
Cmc = 1,
TypeLine = "Instant",
OracleText = "Lightning Bolt deals 3 damage to any target."
};
await _database.InsertOracleAsync(oracle);
var retrieved = await _database.GetOracleByNameAsync("Lightning Bolt");
Assert.NotNull(retrieved);
Assert.Equal("{R}", retrieved.ManaCost);
Assert.Equal(1, retrieved.Cmc);
}
[Fact]
public async Task InsertSet_ThenRetrieveByCode()
{
var set = new Set
{
Id = "set-lea",
Code = "lea",
Name = "Limited Edition Alpha",
SetType = "expansion",
ReleasedAt = "1993-08-05",
CardCount = 295
};
await _database.InsertSetAsync(set);
var retrieved = await _database.GetSetByCodeAsync("lea");
Assert.NotNull(retrieved);
Assert.Equal("Limited Edition Alpha", retrieved.Name);
Assert.Equal(295, retrieved.CardCount);
}
[Fact]
public async Task GetCardsWithHash_OnlyReturnsCardsWithHash()
{
var oracle = new Oracle { Id = "oracle-hash", Name = "Hash Card" };
await _database.InsertOracleAsync(oracle);
var set = new Set { Id = "set-hash", Code = "TST", Name = "Test Set" };
await _database.InsertSetAsync(set);
var cardWithHash = new Card
{
Id = "card-with-hash",
OracleId = "oracle-hash",
SetId = "set-hash",
SetCode = "TST",
Name = "Has Hash",
Hash = new byte[] { 0x01 }
};
var cardWithoutHash = new Card
{
Id = "card-no-hash",
OracleId = "oracle-hash",
SetId = "set-hash",
SetCode = "TST",
Name = "No Hash",
Hash = null
};
await _database.InsertCardAsync(cardWithHash);
await _database.InsertCardAsync(cardWithoutHash);
var cardsWithHash = await _database.GetCardsWithHashAsync();
Assert.Single(cardsWithHash);
Assert.Equal("card-with-hash", cardsWithHash[0].Id);
}
public void Dispose()
{
_database.Dispose();
SqliteConnection.ClearAllPools();
try
{
if (File.Exists(_dbPath))
{
File.Delete(_dbPath);
}
}
catch (IOException)
{
}
}
}

View file

@ -1,146 +0,0 @@
using Microsoft.Data.Sqlite;
using Scry.Core.Data;
using Scry.Core.Models;
using Xunit;
namespace Scry.Tests;
public class CardHashDatabaseTests : IDisposable
{
private readonly string _dbPath;
private readonly CardHashDatabase _database;
public CardHashDatabaseTests()
{
_dbPath = Path.Combine(Path.GetTempPath(), $"scry_test_{Guid.NewGuid()}.db");
_database = new CardHashDatabase(_dbPath);
}
[Fact]
public async Task InsertHash_ThenRetrieve_ReturnsMatch()
{
var hash = new CardHash
{
CardId = "test-id",
Name = "Test Card",
SetCode = "TST",
CollectorNumber = "1",
Hash = new byte[] { 0x01, 0x02, 0x03 },
ImageUri = "https://example.com/image.jpg"
};
await _database.InsertHashAsync(hash);
var retrieved = await _database.GetHashByIdAsync("test-id");
Assert.NotNull(retrieved);
Assert.Equal("Test Card", retrieved.Name);
Assert.Equal("TST", retrieved.SetCode);
Assert.Equal(hash.Hash, retrieved.Hash);
}
[Fact]
public async Task InsertHashBatch_InsertsAllHashes()
{
var hashes = Enumerable.Range(0, 100).Select(i => new CardHash
{
CardId = $"card-{i}",
Name = $"Card {i}",
SetCode = "TST",
Hash = new byte[] { (byte)i }
}).ToList();
await _database.InsertHashBatchAsync(hashes);
var count = await _database.GetHashCountAsync();
Assert.Equal(100, count);
}
[Fact]
public async Task GetAllHashes_ReturnsAllHashes()
{
var hashes = Enumerable.Range(0, 10).Select(i => new CardHash
{
CardId = $"card-{i}",
Name = $"Card {i}",
SetCode = "TST",
Hash = new byte[] { (byte)i }
}).ToList();
await _database.InsertHashBatchAsync(hashes);
var all = await _database.GetAllHashesAsync();
Assert.Equal(10, all.Count);
}
[Fact]
public async Task Metadata_SetAndGet()
{
await _database.SetMetadataAsync("test_key", "test_value");
var value = await _database.GetMetadataAsync("test_key");
Assert.Equal("test_value", value);
}
[Fact]
public async Task Clear_RemovesAllHashes()
{
var hashes = Enumerable.Range(0, 10).Select(i => new CardHash
{
CardId = $"card-{i}",
Name = $"Card {i}",
SetCode = "TST",
Hash = new byte[] { (byte)i }
}).ToList();
await _database.InsertHashBatchAsync(hashes);
await _database.ClearAsync();
var count = await _database.GetHashCountAsync();
Assert.Equal(0, count);
}
[Fact]
public async Task InsertHash_DuplicateId_Updates()
{
var hash1 = new CardHash
{
CardId = "duplicate-id",
Name = "Original Name",
SetCode = "TST",
Hash = new byte[] { 0x01 }
};
var hash2 = new CardHash
{
CardId = "duplicate-id",
Name = "Updated Name",
SetCode = "TST",
Hash = new byte[] { 0x02 }
};
await _database.InsertHashAsync(hash1);
await _database.InsertHashAsync(hash2);
var retrieved = await _database.GetHashByIdAsync("duplicate-id");
Assert.NotNull(retrieved);
Assert.Equal("Updated Name", retrieved.Name);
Assert.Equal(new byte[] { 0x02 }, retrieved.Hash);
}
public void Dispose()
{
_database.Dispose();
SqliteConnection.ClearAllPools();
try
{
if (File.Exists(_dbPath))
{
File.Delete(_dbPath);
}
}
catch (IOException)
{
}
}
}

View file

@ -13,14 +13,14 @@ public class CardRecognitionTests : IDisposable
{
private readonly ITestOutputHelper _output;
private readonly string _dbPath;
private readonly CardHashDatabase _database;
private readonly CardDatabase _database;
private readonly CardRecognitionService _recognitionService;
public CardRecognitionTests(ITestOutputHelper output)
{
_output = output;
_dbPath = Path.Combine(Path.GetTempPath(), $"scry_recognition_test_{Guid.NewGuid()}.db");
_database = new CardHashDatabase(_dbPath);
_database = new CardDatabase(_dbPath);
_recognitionService = new CardRecognitionService(_database);
}
@ -32,7 +32,7 @@ public class CardRecognitionTests : IDisposable
var result = await _recognitionService.RecognizeAsync(bitmap);
Assert.False(result.Success);
Assert.Contains("No card hashes", result.ErrorMessage);
Assert.Contains("No cards", result.ErrorMessage);
}
[Fact]
@ -41,11 +41,17 @@ public class CardRecognitionTests : IDisposable
using var bitmap = CreateTestBitmap(100, 100);
var hash = _recognitionService.ComputeHash(bitmap);
await _database.InsertHashAsync(new CardHash
// Insert oracle and set first
await _database.InsertOracleAsync(new Oracle { Id = "oracle-test", Name = "Test Card" });
await _database.InsertSetAsync(new Set { Id = "set-test", Code = "TST", Name = "Test Set" });
await _database.InsertCardAsync(new Card
{
CardId = "test-card",
Name = "Test Card",
Id = "test-card",
OracleId = "oracle-test",
SetId = "set-test",
SetCode = "TST",
Name = "Test Card",
Hash = hash,
ImageUri = "https://example.com/test.jpg"
});
@ -78,11 +84,16 @@ public class CardRecognitionTests : IDisposable
var hash = _recognitionService.ComputeHash(bitmap);
var cardName = Path.GetFileNameWithoutExtension(imagePath);
await _database.InsertHashAsync(new CardHash
await _database.InsertOracleAsync(new Oracle { Id = $"oracle-{cardName}", Name = cardName });
await _database.InsertSetAsync(new Set { Id = "set-ref", Code = "REF", Name = "Reference Set" });
await _database.InsertCardAsync(new Card
{
CardId = cardName,
Name = cardName,
Id = cardName,
OracleId = $"oracle-{cardName}",
SetId = "set-ref",
SetCode = "REF",
Name = cardName,
Hash = hash
});
await _recognitionService.InvalidateCacheAsync();
@ -121,7 +132,7 @@ public class CardRecognitionTests : IDisposable
return;
}
using var testDb = new CardHashDatabase(dbPath);
using var testDb = new CardDatabase(dbPath);
using var testRecognition = new CardRecognitionService(testDb);
using var bitmap = SKBitmap.Decode(imagePath);
@ -129,31 +140,31 @@ public class CardRecognitionTests : IDisposable
// First, just compute hash and check distance manually
var queryHash = testRecognition.ComputeHash(bitmap);
var allHashes = await testDb.GetAllHashesAsync();
var allCards = await testDb.GetCardsWithHashAsync();
_output.WriteLine($"Query hash length: {queryHash.Length} bytes");
_output.WriteLine($"Database has {allHashes.Count} cards");
_output.WriteLine($"Database has {allCards.Count} cards with hashes");
// Find Serra Angel and compute distance
var serraHash = allHashes.FirstOrDefault(h => h.Name == "Serra Angel");
if (serraHash != null)
var serraCard = allCards.FirstOrDefault(c => c.Name == "Serra Angel");
if (serraCard?.Hash != null)
{
var distance = PerceptualHash.HammingDistance(queryHash, serraHash.Hash);
_output.WriteLine($"Serra Angel hash length: {serraHash.Hash.Length} bytes");
var distance = PerceptualHash.HammingDistance(queryHash, serraCard.Hash);
_output.WriteLine($"Serra Angel hash length: {serraCard.Hash.Length} bytes");
_output.WriteLine($"Distance to Serra Angel: {distance}");
}
// Find the actual best match
int bestDistance = int.MaxValue;
string? bestName = null;
foreach (var hash in allHashes)
foreach (var card in allCards)
{
if (hash.Hash.Length != queryHash.Length) continue;
var dist = PerceptualHash.HammingDistance(queryHash, hash.Hash);
if (card.Hash == null || card.Hash.Length != queryHash.Length) continue;
var dist = PerceptualHash.HammingDistance(queryHash, card.Hash);
if (dist < bestDistance)
{
bestDistance = dist;
bestName = hash.Name;
bestName = card.Name;
}
}
_output.WriteLine($"Best match: {bestName}, distance: {bestDistance}");
@ -180,11 +191,16 @@ public class CardRecognitionTests : IDisposable
using var bitmap = CreateTestBitmap(200, 300);
var hash = _recognitionService.ComputeHash(bitmap);
await _database.InsertHashAsync(new CardHash
await _database.InsertOracleAsync(new Oracle { Id = "oracle-timing", Name = "Timing Test Card" });
await _database.InsertSetAsync(new Set { Id = "set-timing", Code = "TST", Name = "Test Set" });
await _database.InsertCardAsync(new Card
{
CardId = "timing-test",
Name = "Timing Test Card",
Id = "timing-test",
OracleId = "oracle-timing",
SetId = "set-timing",
SetCode = "TST",
Name = "Timing Test Card",
Hash = hash
});
await _recognitionService.InvalidateCacheAsync();

View file

@ -15,14 +15,14 @@ public class RobustnessAnalysisTests : IDisposable
{
private readonly ITestOutputHelper _output;
private readonly string _dbPath;
private readonly CardHashDatabase _database;
private readonly CardDatabase _database;
private readonly CardRecognitionService _recognitionService;
public RobustnessAnalysisTests(ITestOutputHelper output)
{
_output = output;
_dbPath = Path.Combine(Path.GetTempPath(), $"scry_robustness_test_{Guid.NewGuid()}.db");
_database = new CardHashDatabase(_dbPath);
_database = new CardDatabase(_dbPath);
_recognitionService = new CardRecognitionService(_database);
}
@ -52,11 +52,16 @@ public class RobustnessAnalysisTests : IDisposable
var originalHash = _recognitionService.ComputeHash(original);
// Register original in database
await _database.InsertHashAsync(new CardHash
await _database.InsertOracleAsync(new Oracle { Id = "oracle-serra", Name = "Serra Angel" });
await _database.InsertSetAsync(new Set { Id = "set-lea", Code = "LEA", Name = "Alpha" });
await _database.InsertCardAsync(new Card
{
CardId = "serra-angel",
Name = "Serra Angel",
Id = "serra-angel",
OracleId = "oracle-serra",
SetId = "set-lea",
SetCode = "LEA",
Name = "Serra Angel",
Hash = originalHash
});
await _recognitionService.InvalidateCacheAsync();
@ -113,11 +118,16 @@ public class RobustnessAnalysisTests : IDisposable
var originalHash = _recognitionService.ComputeHash(original);
await _database.InsertHashAsync(new CardHash
await _database.InsertOracleAsync(new Oracle { Id = "oracle-serra", Name = "Serra Angel" });
await _database.InsertSetAsync(new Set { Id = "set-lea", Code = "LEA", Name = "Alpha" });
await _database.InsertCardAsync(new Card
{
CardId = "serra-angel",
Name = "Serra Angel",
Id = "serra-angel",
OracleId = "oracle-serra",
SetId = "set-lea",
SetCode = "LEA",
Name = "Serra Angel",
Hash = originalHash
});
await _recognitionService.InvalidateCacheAsync();
@ -167,11 +177,16 @@ public class RobustnessAnalysisTests : IDisposable
var originalHash = _recognitionService.ComputeHash(original);
await _database.InsertHashAsync(new CardHash
await _database.InsertOracleAsync(new Oracle { Id = "oracle-serra", Name = "Serra Angel" });
await _database.InsertSetAsync(new Set { Id = "set-lea", Code = "LEA", Name = "Alpha" });
await _database.InsertCardAsync(new Card
{
CardId = "serra-angel",
Name = "Serra Angel",
Id = "serra-angel",
OracleId = "oracle-serra",
SetId = "set-lea",
SetCode = "LEA",
Name = "Serra Angel",
Hash = originalHash
});
await _recognitionService.InvalidateCacheAsync();
@ -218,11 +233,16 @@ public class RobustnessAnalysisTests : IDisposable
var originalHash = _recognitionService.ComputeHash(original);
await _database.InsertHashAsync(new CardHash
await _database.InsertOracleAsync(new Oracle { Id = "oracle-serra", Name = "Serra Angel" });
await _database.InsertSetAsync(new Set { Id = "set-lea", Code = "LEA", Name = "Alpha" });
await _database.InsertCardAsync(new Card
{
CardId = "serra-angel",
Name = "Serra Angel",
Id = "serra-angel",
OracleId = "oracle-serra",
SetId = "set-lea",
SetCode = "LEA",
Name = "Serra Angel",
Hash = originalHash
});
await _recognitionService.InvalidateCacheAsync();
@ -265,11 +285,16 @@ public class RobustnessAnalysisTests : IDisposable
var originalHash = _recognitionService.ComputeHash(original);
await _database.InsertHashAsync(new CardHash
await _database.InsertOracleAsync(new Oracle { Id = "oracle-serra", Name = "Serra Angel" });
await _database.InsertSetAsync(new Set { Id = "set-lea", Code = "LEA", Name = "Alpha" });
await _database.InsertCardAsync(new Card
{
CardId = "serra-angel",
Name = "Serra Angel",
Id = "serra-angel",
OracleId = "oracle-serra",
SetId = "set-lea",
SetCode = "LEA",
Name = "Serra Angel",
Hash = originalHash
});
await _recognitionService.InvalidateCacheAsync();
@ -312,14 +337,14 @@ public class RobustnessAnalysisTests : IDisposable
return;
}
using var prodDb = new CardHashDatabase(dbPath);
using var prodDb = new CardDatabase(dbPath);
using var prodRecognition = new CardRecognitionService(prodDb);
var testImagesDir = Path.Combine(rootDir, "TestImages");
var categoriesToTest = new[] { "real_photos", "varying_quality", "angled", "low_light" };
_output.WriteLine("=== Real-World Recognition Test ===");
_output.WriteLine($"Database cards: {(await prodDb.GetAllHashesAsync()).Count}");
_output.WriteLine($"Database cards: {(await prodDb.GetCardsWithHashAsync()).Count}");
_output.WriteLine("");
foreach (var category in categoriesToTest)