diff --git a/DeepDrftHome.sln b/DeepDrftHome.sln
index 0b4febc..fd325c6 100644
--- a/DeepDrftHome.sln
+++ b/DeepDrftHome.sln
@@ -10,31 +10,92 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeepDrftContent", "DeepDrft
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetBlocks", "C:\lib\NetBlocks\NetBlocks.csproj", "{EEB3A665-B8AD-4C00-A41E-B9D8AFE1BBA8}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeepDrftTests", "DeepDrftTests\DeepDrftTests.csproj", "{47E99024-491B-47A6-BAF8-9E5814366DB2}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{7E629215-7EF7-465D-B7F2-2CED53C4BFEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7E629215-7EF7-465D-B7F2-2CED53C4BFEC}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7E629215-7EF7-465D-B7F2-2CED53C4BFEC}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {7E629215-7EF7-465D-B7F2-2CED53C4BFEC}.Debug|x64.Build.0 = Debug|Any CPU
+ {7E629215-7EF7-465D-B7F2-2CED53C4BFEC}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {7E629215-7EF7-465D-B7F2-2CED53C4BFEC}.Debug|x86.Build.0 = Debug|Any CPU
{7E629215-7EF7-465D-B7F2-2CED53C4BFEC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7E629215-7EF7-465D-B7F2-2CED53C4BFEC}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7E629215-7EF7-465D-B7F2-2CED53C4BFEC}.Release|x64.ActiveCfg = Release|Any CPU
+ {7E629215-7EF7-465D-B7F2-2CED53C4BFEC}.Release|x64.Build.0 = Release|Any CPU
+ {7E629215-7EF7-465D-B7F2-2CED53C4BFEC}.Release|x86.ActiveCfg = Release|Any CPU
+ {7E629215-7EF7-465D-B7F2-2CED53C4BFEC}.Release|x86.Build.0 = Release|Any CPU
{E76D21B4-308B-487B-B8D6-59D6AE49F1F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E76D21B4-308B-487B-B8D6-59D6AE49F1F7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E76D21B4-308B-487B-B8D6-59D6AE49F1F7}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {E76D21B4-308B-487B-B8D6-59D6AE49F1F7}.Debug|x64.Build.0 = Debug|Any CPU
+ {E76D21B4-308B-487B-B8D6-59D6AE49F1F7}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {E76D21B4-308B-487B-B8D6-59D6AE49F1F7}.Debug|x86.Build.0 = Debug|Any CPU
{E76D21B4-308B-487B-B8D6-59D6AE49F1F7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E76D21B4-308B-487B-B8D6-59D6AE49F1F7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E76D21B4-308B-487B-B8D6-59D6AE49F1F7}.Release|x64.ActiveCfg = Release|Any CPU
+ {E76D21B4-308B-487B-B8D6-59D6AE49F1F7}.Release|x64.Build.0 = Release|Any CPU
+ {E76D21B4-308B-487B-B8D6-59D6AE49F1F7}.Release|x86.ActiveCfg = Release|Any CPU
+ {E76D21B4-308B-487B-B8D6-59D6AE49F1F7}.Release|x86.Build.0 = Release|Any CPU
{10CE5160-16C3-4CB1-9E2E-52467BA80B4B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{10CE5160-16C3-4CB1-9E2E-52467BA80B4B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {10CE5160-16C3-4CB1-9E2E-52467BA80B4B}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {10CE5160-16C3-4CB1-9E2E-52467BA80B4B}.Debug|x64.Build.0 = Debug|Any CPU
+ {10CE5160-16C3-4CB1-9E2E-52467BA80B4B}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {10CE5160-16C3-4CB1-9E2E-52467BA80B4B}.Debug|x86.Build.0 = Debug|Any CPU
{10CE5160-16C3-4CB1-9E2E-52467BA80B4B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{10CE5160-16C3-4CB1-9E2E-52467BA80B4B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {10CE5160-16C3-4CB1-9E2E-52467BA80B4B}.Release|x64.ActiveCfg = Release|Any CPU
+ {10CE5160-16C3-4CB1-9E2E-52467BA80B4B}.Release|x64.Build.0 = Release|Any CPU
+ {10CE5160-16C3-4CB1-9E2E-52467BA80B4B}.Release|x86.ActiveCfg = Release|Any CPU
+ {10CE5160-16C3-4CB1-9E2E-52467BA80B4B}.Release|x86.Build.0 = Release|Any CPU
{C79AFD08-02C0-45D2-A98A-FCDDFBEAE155}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C79AFD08-02C0-45D2-A98A-FCDDFBEAE155}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C79AFD08-02C0-45D2-A98A-FCDDFBEAE155}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {C79AFD08-02C0-45D2-A98A-FCDDFBEAE155}.Debug|x64.Build.0 = Debug|Any CPU
+ {C79AFD08-02C0-45D2-A98A-FCDDFBEAE155}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {C79AFD08-02C0-45D2-A98A-FCDDFBEAE155}.Debug|x86.Build.0 = Debug|Any CPU
{C79AFD08-02C0-45D2-A98A-FCDDFBEAE155}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C79AFD08-02C0-45D2-A98A-FCDDFBEAE155}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C79AFD08-02C0-45D2-A98A-FCDDFBEAE155}.Release|x64.ActiveCfg = Release|Any CPU
+ {C79AFD08-02C0-45D2-A98A-FCDDFBEAE155}.Release|x64.Build.0 = Release|Any CPU
+ {C79AFD08-02C0-45D2-A98A-FCDDFBEAE155}.Release|x86.ActiveCfg = Release|Any CPU
+ {C79AFD08-02C0-45D2-A98A-FCDDFBEAE155}.Release|x86.Build.0 = Release|Any CPU
{EEB3A665-B8AD-4C00-A41E-B9D8AFE1BBA8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EEB3A665-B8AD-4C00-A41E-B9D8AFE1BBA8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {EEB3A665-B8AD-4C00-A41E-B9D8AFE1BBA8}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {EEB3A665-B8AD-4C00-A41E-B9D8AFE1BBA8}.Debug|x64.Build.0 = Debug|Any CPU
+ {EEB3A665-B8AD-4C00-A41E-B9D8AFE1BBA8}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {EEB3A665-B8AD-4C00-A41E-B9D8AFE1BBA8}.Debug|x86.Build.0 = Debug|Any CPU
{EEB3A665-B8AD-4C00-A41E-B9D8AFE1BBA8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EEB3A665-B8AD-4C00-A41E-B9D8AFE1BBA8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {EEB3A665-B8AD-4C00-A41E-B9D8AFE1BBA8}.Release|x64.ActiveCfg = Release|Any CPU
+ {EEB3A665-B8AD-4C00-A41E-B9D8AFE1BBA8}.Release|x64.Build.0 = Release|Any CPU
+ {EEB3A665-B8AD-4C00-A41E-B9D8AFE1BBA8}.Release|x86.ActiveCfg = Release|Any CPU
+ {EEB3A665-B8AD-4C00-A41E-B9D8AFE1BBA8}.Release|x86.Build.0 = Release|Any CPU
+ {47E99024-491B-47A6-BAF8-9E5814366DB2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {47E99024-491B-47A6-BAF8-9E5814366DB2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {47E99024-491B-47A6-BAF8-9E5814366DB2}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {47E99024-491B-47A6-BAF8-9E5814366DB2}.Debug|x64.Build.0 = Debug|Any CPU
+ {47E99024-491B-47A6-BAF8-9E5814366DB2}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {47E99024-491B-47A6-BAF8-9E5814366DB2}.Debug|x86.Build.0 = Debug|Any CPU
+ {47E99024-491B-47A6-BAF8-9E5814366DB2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {47E99024-491B-47A6-BAF8-9E5814366DB2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {47E99024-491B-47A6-BAF8-9E5814366DB2}.Release|x64.ActiveCfg = Release|Any CPU
+ {47E99024-491B-47A6-BAF8-9E5814366DB2}.Release|x64.Build.0 = Release|Any CPU
+ {47E99024-491B-47A6-BAF8-9E5814366DB2}.Release|x86.ActiveCfg = Release|Any CPU
+ {47E99024-491B-47A6-BAF8-9E5814366DB2}.Release|x86.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
EndGlobalSection
EndGlobal
diff --git a/DeepDrftTests/DeepDrftTests.csproj b/DeepDrftTests/DeepDrftTests.csproj
new file mode 100644
index 0000000..e007d13
--- /dev/null
+++ b/DeepDrftTests/DeepDrftTests.csproj
@@ -0,0 +1,27 @@
+
+
+
+ net9.0
+ latest
+ enable
+ enable
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/DeepDrftTests/FileDatabaseTests.cs b/DeepDrftTests/FileDatabaseTests.cs
new file mode 100644
index 0000000..0a2c85e
--- /dev/null
+++ b/DeepDrftTests/FileDatabaseTests.cs
@@ -0,0 +1,229 @@
+using DeepDrftContent.FileDatabase.Models;
+using DeepDrftContent.FileDatabase.Services;
+
+namespace DeepDrftTests;
+
+///
+/// Tests for FileDatabase functionality, ported from TypeScript tests
+///
+[TestFixture]
+public class FileDatabaseTests
+{
+ private string _testDatabasePath = null!;
+ private FileDatabase? _fileDatabase;
+
+ [SetUp]
+ public void SetUp()
+ {
+ // Create a unique test directory for each test
+ _testDatabasePath = Path.Combine(Path.GetTempPath(), "DeepDrftTests", Guid.NewGuid().ToString());
+
+ // Clean up any existing test directory
+ if (Directory.Exists(_testDatabasePath))
+ {
+ Directory.Delete(_testDatabasePath, true);
+ }
+
+ // Ensure the directory exists
+ Directory.CreateDirectory(_testDatabasePath);
+ }
+
+ [TearDown]
+ public void TearDown()
+ {
+ // Clean up test directory
+ if (Directory.Exists(_testDatabasePath))
+ {
+ try
+ {
+ Directory.Delete(_testDatabasePath, true);
+ }
+ catch
+ {
+ // Ignore cleanup errors
+ }
+ }
+ }
+
+ [Test]
+ public async Task FileDatabase_CanBeCreatedAtSpecifiedLocation()
+ {
+ // Act
+ _fileDatabase = await FileDatabase.FromAsync(_testDatabasePath);
+
+ // Assert
+ Assert.That(_fileDatabase, Is.Not.Null, "FileDatabase should not be null");
+ Assert.That(_fileDatabase.GetIndexSize(), Is.EqualTo(0), "Index should be empty initially");
+ }
+
+ [Test]
+ public async Task FileDatabase_CanAddNewVaultForImages()
+ {
+ // Arrange
+ _fileDatabase = await FileDatabase.FromAsync(_testDatabasePath);
+ Assert.That(_fileDatabase, Is.Not.Null);
+
+ // Act
+ await _fileDatabase.CreateVaultAsync(TestData.TestKeys.ImageVaultKey);
+
+ // Assert
+ Assert.That(_fileDatabase.GetIndexSize(), Is.EqualTo(1), "Index should contain one element");
+
+ var vaultDirectory = Path.Combine(_testDatabasePath, TestData.TestKeys.ImageVaultKey.Key);
+ Assert.That(Directory.Exists(vaultDirectory), Is.True, "Vault directory should exist");
+ }
+
+ [Test]
+ public async Task FileDatabase_CanAddNewMediaToImageVault()
+ {
+ // Arrange
+ _fileDatabase = await FileDatabase.FromAsync(_testDatabasePath);
+ Assert.That(_fileDatabase, Is.Not.Null);
+
+ await _fileDatabase.CreateVaultAsync(TestData.TestKeys.ImageVaultKey);
+ var testImage = TestData.CreateTestImageBinary(1.0);
+
+ // Act
+ await _fileDatabase.RegisterResourceAsync(
+ MediaVaultType.Image,
+ TestData.TestKeys.ImageVaultKey,
+ TestData.TestKeys.TestImageEntry,
+ testImage);
+
+ // Assert
+ var vault = _fileDatabase.GetVault(TestData.TestKeys.ImageVaultKey);
+ Assert.That(vault, Is.Not.Null, "Vault should not be null");
+ Assert.That(vault!.HasIndexEntry(TestData.TestKeys.TestImageEntry), Is.True,
+ "Added image should be in the index");
+ }
+
+ [Test]
+ public async Task FileDatabase_CanLoadValidResourceFromVault()
+ {
+ // Arrange
+ _fileDatabase = await FileDatabase.FromAsync(_testDatabasePath);
+ Assert.That(_fileDatabase, Is.Not.Null);
+
+ await _fileDatabase.CreateVaultAsync(TestData.TestKeys.ImageVaultKey);
+ var testImage = TestData.CreateTestImageBinary(1.0);
+
+ await _fileDatabase.RegisterResourceAsync(
+ MediaVaultType.Image,
+ TestData.TestKeys.ImageVaultKey,
+ TestData.TestKeys.TestImageEntry,
+ testImage);
+
+ // Act
+ var loadedMedia = await _fileDatabase.LoadResourceAsync(
+ MediaVaultType.Image,
+ TestData.TestKeys.ImageVaultKey,
+ TestData.TestKeys.TestImageEntry);
+
+ // Assert
+ Assert.That(loadedMedia, Is.Not.Null, "Loaded media should not be null");
+ AssertValidImageResource(loadedMedia!);
+ }
+
+ [Test]
+ public async Task FileDatabase_DeniesAccessToNonexistentVault()
+ {
+ // Arrange
+ _fileDatabase = await FileDatabase.FromAsync(_testDatabasePath);
+ Assert.That(_fileDatabase, Is.Not.Null);
+
+ // Act & Assert - Should not throw exception but return null/default
+ var vault = _fileDatabase.GetVault(TestData.TestKeys.NonExistentVaultKey);
+ Assert.That(vault, Is.Null, "Nonexistent vault should return null");
+
+ // Loading from nonexistent vault should not throw but handle gracefully
+ Assert.DoesNotThrowAsync(async () =>
+ {
+ await _fileDatabase.LoadResourceAsync(
+ MediaVaultType.Image,
+ TestData.TestKeys.NonExistentVaultKey,
+ TestData.TestKeys.NonExistentEntryKey);
+ }, "Should not throw exceptions when accessing nonexistent vault");
+ }
+
+ [Test]
+ public async Task FileDatabase_DeniesAccessToNonexistentResource()
+ {
+ // Arrange
+ _fileDatabase = await FileDatabase.FromAsync(_testDatabasePath);
+ Assert.That(_fileDatabase, Is.Not.Null);
+
+ await _fileDatabase.CreateVaultAsync(TestData.TestKeys.ImageVaultKey);
+
+ // Act & Assert - Should not throw exception when accessing nonexistent resource
+ Assert.DoesNotThrowAsync(async () =>
+ {
+ await _fileDatabase.LoadResourceAsync(
+ MediaVaultType.Image,
+ TestData.TestKeys.ImageVaultKey,
+ TestData.TestKeys.NonExistentEntryKey);
+ }, "Should not throw exceptions when accessing nonexistent resource");
+ }
+
+ [Test]
+ public async Task FileDatabase_CanBeReloadedFromSecondaryMemory()
+ {
+ // Arrange - Create and populate a database
+ _fileDatabase = await FileDatabase.FromAsync(_testDatabasePath);
+ Assert.That(_fileDatabase, Is.Not.Null);
+
+ await _fileDatabase.CreateVaultAsync(TestData.TestKeys.ImageVaultKey);
+ var testImage = TestData.CreateTestImageBinary(1.0);
+
+ await _fileDatabase.RegisterResourceAsync(
+ MediaVaultType.Image,
+ TestData.TestKeys.ImageVaultKey,
+ TestData.TestKeys.TestImageEntry,
+ testImage);
+
+ // Act - Reload the database from the same path
+ var reloadedDatabase = await FileDatabase.FromAsync(_testDatabasePath);
+
+ // Assert
+ Assert.That(reloadedDatabase, Is.Not.Null, "Reloaded database should not be null");
+ Assert.That(reloadedDatabase.GetIndexSize(), Is.EqualTo(1), "Index count should be 1");
+
+ // Verify vault exists
+ Assert.That(reloadedDatabase.HasIndexEntry(TestData.TestKeys.ImageVaultKey), Is.True,
+ "Vault should be present in index");
+ Assert.That(reloadedDatabase.HasVault(TestData.TestKeys.ImageVaultKey), Is.True,
+ "Vault should be present in vault collection");
+
+ var vault = reloadedDatabase.GetVault(TestData.TestKeys.ImageVaultKey);
+ Assert.That(vault, Is.Not.Null, "Vault should not be null");
+
+ // Verify resource can be loaded
+ var loadedMedia = await reloadedDatabase.LoadResourceAsync(
+ MediaVaultType.Image,
+ TestData.TestKeys.ImageVaultKey,
+ TestData.TestKeys.TestImageEntry);
+
+ Assert.That(loadedMedia, Is.Not.Null, "Loaded media should not be null");
+ AssertValidImageResource(loadedMedia!);
+ }
+
+ ///
+ /// Helper method to validate an ImageBinary resource matches test expectations
+ ///
+ private static void AssertValidImageResource(ImageBinary media)
+ {
+ Assert.That(media, Is.Not.Null, "Image package should not be null");
+ Assert.That(media.Size, Is.GreaterThan(0), "Image size should be greater than 0");
+ Assert.That(media.Buffer.Length, Is.EqualTo(TestData.TestPngBytes.Length),
+ "Number of bytes should match test data");
+
+ // Verify byte-by-byte equality
+ for (int i = 0; i < media.Buffer.Length; i++)
+ {
+ Assert.That(media.Buffer[i], Is.EqualTo(TestData.TestPngBytes[i]),
+ $"Byte at index {i} should be equal");
+ }
+
+ Assert.That(media.Extension, Is.EqualTo(".png"), "Extension should be .png");
+ Assert.That(media.AspectRatio, Is.EqualTo(1.0).Within(0.001), "Aspect ratio should be 1.0");
+ }
+}
diff --git a/DeepDrftTests/ModelTests.cs b/DeepDrftTests/ModelTests.cs
new file mode 100644
index 0000000..e20d624
--- /dev/null
+++ b/DeepDrftTests/ModelTests.cs
@@ -0,0 +1,255 @@
+using DeepDrftContent.FileDatabase.Models;
+
+namespace DeepDrftTests;
+
+///
+/// Tests for model classes and data structures
+///
+[TestFixture]
+public class ModelTests
+{
+ [TestFixture]
+ public class EntryKeyTests
+ {
+ [Test]
+ public void EntryKey_CanBeCreated()
+ {
+ // Arrange
+ var key = "test-key";
+ var type = MediaVaultType.Image;
+
+ // Act
+ var entryKey = new EntryKey(key, type);
+
+ // Assert
+ Assert.That(entryKey.Key, Is.EqualTo(key), "Key should match");
+ Assert.That(entryKey.Type, Is.EqualTo(type), "Type should match");
+ }
+
+ [Test]
+ public void EntryKey_SupportsStructuralEquality()
+ {
+ // Arrange
+ var key1 = new EntryKey("test", MediaVaultType.Image);
+ var key2 = new EntryKey("test", MediaVaultType.Image);
+ var key3 = new EntryKey("different", MediaVaultType.Image);
+
+ // Act & Assert
+ Assert.That(key1, Is.EqualTo(key2), "Structurally equal keys should be equal");
+ Assert.That(key1, Is.Not.EqualTo(key3), "Different keys should not be equal");
+ Assert.That(key1.GetHashCode(), Is.EqualTo(key2.GetHashCode()), "Equal keys should have same hash code");
+ }
+ }
+
+ [TestFixture]
+ public class MediaModelTests
+ {
+ [Test]
+ public void FileBinary_CanBeCreated()
+ {
+ // Arrange
+ var buffer = TestData.TestPngBytes;
+ var size = buffer.Length;
+ var parameters = new FileBinaryParams(buffer, size);
+
+ // Act
+ var fileBinary = new FileBinary(parameters);
+
+ // Assert
+ Assert.That(fileBinary.Buffer, Is.EqualTo(buffer), "Buffer should match");
+ Assert.That(fileBinary.Size, Is.EqualTo(size), "Size should match");
+ }
+
+ [Test]
+ public void FileBinary_CanBeCreatedFromDto()
+ {
+ // Arrange
+ var originalBuffer = TestData.TestPngBytes;
+ var base64Data = Convert.ToBase64String(originalBuffer);
+ var dto = new FileBinaryDto(base64Data, originalBuffer.Length);
+
+ // Act
+ var fileBinary = FileBinary.From(dto);
+
+ // Assert
+ Assert.That(fileBinary.Size, Is.EqualTo(originalBuffer.Length), "Size should match");
+ Assert.That(fileBinary.Buffer, Is.EqualTo(originalBuffer), "Buffer should match original");
+ }
+
+ [Test]
+ public void MediaBinary_CanBeCreated()
+ {
+ // Arrange
+ var buffer = TestData.TestPngBytes;
+ var size = buffer.Length;
+ var extension = ".png";
+ var parameters = new MediaBinaryParams(buffer, size, extension);
+
+ // Act
+ var mediaBinary = new MediaBinary(parameters);
+
+ // Assert
+ Assert.That(mediaBinary.Buffer, Is.EqualTo(buffer), "Buffer should match");
+ Assert.That(mediaBinary.Size, Is.EqualTo(size), "Size should match");
+ Assert.That(mediaBinary.Extension, Is.EqualTo(extension), "Extension should match");
+ }
+
+ [Test]
+ public void ImageBinary_CanBeCreated()
+ {
+ // Arrange
+ var buffer = TestData.TestPngBytes;
+ var size = buffer.Length;
+ var extension = ".png";
+ var aspectRatio = 1.5;
+ var parameters = new ImageBinaryParams(buffer, size, extension, aspectRatio);
+
+ // Act
+ var imageBinary = new ImageBinary(parameters);
+
+ // Assert
+ Assert.That(imageBinary.Buffer, Is.EqualTo(buffer), "Buffer should match");
+ Assert.That(imageBinary.Size, Is.EqualTo(size), "Size should match");
+ Assert.That(imageBinary.Extension, Is.EqualTo(extension), "Extension should match");
+ Assert.That(imageBinary.AspectRatio, Is.EqualTo(aspectRatio), "Aspect ratio should match");
+ }
+
+ [Test]
+ public void ImageBinary_CanBeCreatedFromDto()
+ {
+ // Arrange
+ var originalBuffer = TestData.TestPngBytes;
+ var base64Data = Convert.ToBase64String(originalBuffer);
+ var dto = new ImageBinaryDto(base64Data, originalBuffer.Length, "image/png", 1.0);
+
+ // Act
+ var imageBinary = ImageBinary.From(dto);
+
+ // Assert
+ Assert.That(imageBinary.Size, Is.EqualTo(originalBuffer.Length), "Size should match");
+ Assert.That(imageBinary.Buffer, Is.EqualTo(originalBuffer), "Buffer should match original");
+ Assert.That(imageBinary.Extension, Is.EqualTo(".png"), "Extension should match");
+ Assert.That(imageBinary.AspectRatio, Is.EqualTo(1.0), "Aspect ratio should match");
+ }
+
+ [Test]
+ public void ImageBinaryDto_CanBeCreatedFromImageBinary()
+ {
+ // Arrange
+ var imageBinary = TestData.CreateTestImageBinary(1.5);
+
+ // Act
+ var dto = new ImageBinaryDto(imageBinary);
+
+ // Assert
+ Assert.That(dto.Size, Is.EqualTo(imageBinary.Size), "Size should match");
+ Assert.That(dto.Mime, Is.EqualTo(MimeTypeExtensions.GetMimeType(imageBinary.Extension)), "MIME type should match");
+ Assert.That(dto.AspectRatio, Is.EqualTo(imageBinary.AspectRatio), "Aspect ratio should match");
+
+ // Verify base64 encoding
+ var decodedBuffer = Convert.FromBase64String(dto.Base64);
+ Assert.That(decodedBuffer, Is.EqualTo(imageBinary.Buffer), "Decoded buffer should match original");
+ }
+ }
+
+ [TestFixture]
+ public class MetaDataTests
+ {
+ [Test]
+ public void MetaData_CanBeCreated()
+ {
+ // Arrange
+ var key = "test-key";
+ var extension = ".png";
+
+ // Act
+ var metaData = new MetaData(key, extension);
+
+ // Assert
+ Assert.That(metaData.MediaKey, Is.EqualTo(key), "MediaKey should match");
+ Assert.That(metaData.Extension, Is.EqualTo(extension), "Extension should match");
+ }
+
+ [Test]
+ public void ImageMetaData_CanBeCreated()
+ {
+ // Arrange
+ var key = "test-image";
+ var extension = ".jpg";
+ var aspectRatio = 1.77;
+
+ // Act
+ var imageMetaData = new ImageMetaData(key, extension, aspectRatio);
+
+ // Assert
+ Assert.That(imageMetaData.MediaKey, Is.EqualTo(key), "MediaKey should match");
+ Assert.That(imageMetaData.Extension, Is.EqualTo(extension), "Extension should match");
+ Assert.That(imageMetaData.AspectRatio, Is.EqualTo(aspectRatio), "Aspect ratio should match");
+ }
+
+ [Test]
+ public void MetaDataFactory_CreatesCorrectTypes()
+ {
+ // Arrange
+ var key = "test";
+ var extension = ".png";
+ var aspectRatio = 2.0;
+
+ // Act
+ var mediaMetaData = MetaDataFactory.Create(MediaVaultType.Media, key, extension, 0.0);
+ var imageMetaData = MetaDataFactory.Create(MediaVaultType.Image, key, extension, aspectRatio);
+
+ // Assert
+ Assert.That(mediaMetaData, Is.TypeOf(), "Should create MetaData for Media type");
+ Assert.That(imageMetaData, Is.TypeOf(), "Should create ImageMetaData for Image type");
+
+ var typedImageMetaData = (ImageMetaData)imageMetaData;
+ Assert.That(typedImageMetaData.AspectRatio, Is.EqualTo(aspectRatio), "Aspect ratio should be set");
+ }
+ }
+
+ [TestFixture]
+ public class MediaFactoryTests
+ {
+ [Test]
+ public void MediaBinaryFactory_CreatesCorrectTypes()
+ {
+ // Arrange
+ var buffer = TestData.TestPngBytes;
+ var size = buffer.Length;
+ var extension = ".png";
+
+ // Act
+ var mediaParams = new MediaBinaryParams(buffer, size, extension);
+ var imageParams = new ImageBinaryParams(buffer, size, extension, 1.0);
+
+ var mediaBinary = FileBinaryFactory.Create(MediaVaultType.Media, mediaParams);
+ var imageBinary = FileBinaryFactory.Create(MediaVaultType.Image, imageParams);
+
+ // Assert
+ Assert.That(mediaBinary, Is.TypeOf(), "Should create MediaBinary for Media type");
+ Assert.That(imageBinary, Is.TypeOf(), "Should create ImageBinary for Image type");
+
+ var typedImageBinary = (ImageBinary)imageBinary;
+ Assert.That(typedImageBinary.AspectRatio, Is.EqualTo(1.0), "Aspect ratio should be set");
+ }
+
+ [Test]
+ public void MediaBinaryFactory_ThrowsForInvalidType()
+ {
+ // Arrange
+ var buffer = TestData.TestPngBytes;
+ var size = buffer.Length;
+ var extension = ".png";
+ var invalidType = (MediaVaultType)999;
+
+ // Act & Assert
+ var invalidParams = new MediaBinaryParams(buffer, size, extension);
+
+ Assert.Throws(() =>
+ {
+ FileBinaryFactory.Create(invalidType, invalidParams);
+ }, "Should throw for invalid media vault type");
+ }
+ }
+}
diff --git a/DeepDrftTests/TestData.cs b/DeepDrftTests/TestData.cs
new file mode 100644
index 0000000..b531e03
--- /dev/null
+++ b/DeepDrftTests/TestData.cs
@@ -0,0 +1,52 @@
+using DeepDrftContent.FileDatabase.Models;
+
+namespace DeepDrftTests;
+
+///
+/// Test data and helper methods for FileDatabase tests
+///
+public static class TestData
+{
+ ///
+ /// Test PNG image bytes (16x16 pixel test image)
+ ///
+ public static readonly byte[] TestPngBytes =
+ [
+ 137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,16,0,0,0,16,8,6,0,0,0,31,243,255,97,0,0,2,8,73,68,65,84,56,203,61,144,59,142,28,71,16,5,163,42,95,254,186,103,185,182,100,233,34,188,255,13,116,0,57,50,36,136,132,86,208,252,187,105,244,112,141,0,10,168,202,120,47,107,12,255,117,127,151,243,238,193,23,79,78,158,172,30,156,34,89,221,63,121,11,231,45,130,85,226,45,156,245,133,84,65,41,88,60,41,79,42,146,142,36,37,202,157,114,167,93,44,17,148,59,75,56,229,162,195,89,66,104,205,100,81,146,94,100,36,25,65,122,80,210,241,208,197,226,241,57,176,132,179,186,88,226,184,211,26,69,69,145,241,18,184,31,152,81,46,22,119,58,244,226,72,175,16,237,199,89,89,141,103,99,158,120,36,225,78,201,104,25,139,139,118,99,137,67,242,51,181,253,144,151,59,82,47,140,108,70,20,195,19,73,132,77,74,147,142,73,135,209,110,116,56,29,70,73,164,139,140,99,69,61,151,230,81,43,215,104,206,42,78,22,164,61,105,219,233,216,233,16,229,131,138,73,134,145,18,41,163,234,144,234,122,42,182,116,238,17,92,50,56,231,23,108,44,252,114,255,155,37,110,116,12,50,94,130,87,245,116,35,195,201,16,186,182,177,229,100,203,13,234,201,165,190,243,71,125,112,169,255,248,122,189,179,142,70,74,36,144,6,18,71,147,112,50,133,110,109,88,14,72,176,222,176,20,214,131,71,173,252,254,126,229,60,225,55,118,122,3,38,184,12,133,161,152,40,28,109,109,108,49,25,101,140,50,172,133,149,177,231,228,82,193,95,177,99,115,199,24,36,147,155,193,110,131,41,3,159,232,94,162,202,217,83,108,37,248,196,120,150,113,139,39,31,190,243,167,6,49,141,192,56,153,193,52,144,161,75,137,46,103,235,128,20,148,51,90,140,114,246,156,220,3,46,1,231,24,252,163,65,75,244,16,235,48,22,51,68,59,143,242,35,189,3,202,217,211,217,75,108,105,60,115,242,204,201,45,7,255,199,224,187,79,150,57,89,205,169,97,232,231,208,172,96,235,96,79,135,118,246,16,123,138,61,141,189,140,71,78,110,101,156,125,240,175,15,190,233,104,33,218,121,118,176,101,48,94,130,45,197,94,130,116,40,177,229,241,47,91,192,163,156,75,76,62,124,240,109,12,126,0,211,140,106,253,37,95,60,102,0,0,0,0,73,69,78,68,174,66,96,130
+ ];
+
+ ///
+ /// Creates a test ImageBinary with the test PNG data
+ ///
+ /// The aspect ratio for the image
+ /// An ImageBinary instance with test data
+ public static ImageBinary CreateTestImageBinary(double aspectRatio = 1.0)
+ {
+ var parameters = new ImageBinaryParams(
+ Buffer: TestPngBytes,
+ Size: TestPngBytes.Length,
+ Extension: ".png",
+ AspectRatio: aspectRatio
+ );
+ return new ImageBinary(parameters);
+ }
+
+ ///
+ /// Test entry keys used across tests
+ ///
+ public static class TestKeys
+ {
+ public static readonly EntryKey TestImageEntry = new("test", MediaVaultType.Image);
+ public static readonly EntryKey ImageVaultKey = new("img", MediaVaultType.Image);
+ public static readonly EntryKey NonExistentVaultKey = new("i-do-not-exist", MediaVaultType.Image);
+ public static readonly EntryKey NonExistentEntryKey = new("i-do-not-exist", MediaVaultType.Image);
+ }
+
+ ///
+ /// Test file names
+ ///
+ public static class TestFiles
+ {
+ public const string TestPngName = "test.png";
+ }
+}
diff --git a/DeepDrftTests/UtilityTests.cs b/DeepDrftTests/UtilityTests.cs
new file mode 100644
index 0000000..03a79c8
--- /dev/null
+++ b/DeepDrftTests/UtilityTests.cs
@@ -0,0 +1,252 @@
+using DeepDrftContent.FileDatabase.Models;
+using DeepDrftContent.FileDatabase.Utils;
+
+namespace DeepDrftTests;
+
+///
+/// Tests for utility classes like StructuralMap and StructuralSet
+///
+[TestFixture]
+public class UtilityTests
+{
+ [TestFixture]
+ public class StructuralMapTests
+ {
+ [Test]
+ public void StructuralMap_CanAddAndRetrieveEntries()
+ {
+ // Arrange
+ var map = new StructuralMap();
+ var key = new EntryKey("test", MediaVaultType.Image);
+ var value = "test-value";
+
+ // Act
+ map.Set(key, value);
+
+ // Assert
+ Assert.That(map.Has(key), Is.True, "Map should contain the key");
+ Assert.That(map.Get(key), Is.EqualTo(value), "Retrieved value should match");
+ Assert.That(map.Size, Is.EqualTo(1), "Map should have one entry");
+ }
+
+ [Test]
+ public void StructuralMap_HandlesStructuralEquality()
+ {
+ // Arrange
+ var map = new StructuralMap();
+ var key1 = new EntryKey("test", MediaVaultType.Image);
+ var key2 = new EntryKey("test", MediaVaultType.Image); // Same values, different instance
+ var value = "test-value";
+
+ // Act
+ map.Set(key1, value);
+
+ // Assert
+ Assert.That(map.Has(key2), Is.True, "Map should use structural equality");
+ Assert.That(map.Get(key2), Is.EqualTo(value), "Should retrieve value using structurally equal key");
+ }
+
+ [Test]
+ public void StructuralMap_CanRemoveEntries()
+ {
+ // Arrange
+ var map = new StructuralMap();
+ var key = new EntryKey("test", MediaVaultType.Image);
+ var value = "test-value";
+ map.Set(key, value);
+
+ // Act
+ var removed = map.Delete(key);
+
+ // Assert
+ Assert.That(removed, Is.True, "Delete should return true");
+ Assert.That(map.Has(key), Is.False, "Map should not contain the key after removal");
+ Assert.That(map.Size, Is.EqualTo(0), "Map should be empty");
+ }
+
+ [Test]
+ public void StructuralMap_CanEnumerateEntries()
+ {
+ // Arrange
+ var map = new StructuralMap();
+ var entries = new[]
+ {
+ (new EntryKey("key1", MediaVaultType.Image), "value1"),
+ (new EntryKey("key2", MediaVaultType.Media), "value2"),
+ (new EntryKey("key3", MediaVaultType.Image), "value3")
+ };
+
+ foreach (var (key, value) in entries)
+ {
+ map.Set(key, value);
+ }
+
+ // Act
+ var retrievedEntries = map.ToList();
+
+ // Assert
+ Assert.That(retrievedEntries.Count, Is.EqualTo(3), "Should enumerate all entries");
+
+ foreach (var (key, value) in entries)
+ {
+ Assert.That(retrievedEntries.Any(kvp => kvp.Key.Equals(key) && kvp.Value == value),
+ Is.True, $"Should contain entry ({key}, {value})");
+ }
+ }
+ }
+
+ [TestFixture]
+ public class StructuralSetTests
+ {
+ [Test]
+ public void StructuralSet_CanAddAndContainEntries()
+ {
+ // Arrange
+ var set = new StructuralSet();
+ var key = new EntryKey("test", MediaVaultType.Image);
+
+ // Act
+ set.Add(key);
+
+ // Assert
+ Assert.That(set.Has(key), Is.True, "Set should contain the key");
+ Assert.That(set.Size, Is.EqualTo(1), "Set should have one entry");
+ }
+
+ [Test]
+ public void StructuralSet_HandlesStructuralEquality()
+ {
+ // Arrange
+ var set = new StructuralSet();
+ var key1 = new EntryKey("test", MediaVaultType.Image);
+ var key2 = new EntryKey("test", MediaVaultType.Image); // Same values, different instance
+
+ // Act
+ set.Add(key1);
+ set.Add(key2);
+
+ // Assert
+ Assert.That(set.Has(key2), Is.True, "Should contain structurally equal key");
+ Assert.That(set.Size, Is.EqualTo(1), "Set should still have only one entry due to structural equality");
+ }
+
+ [Test]
+ public void StructuralSet_CanRemoveEntries()
+ {
+ // Arrange
+ var set = new StructuralSet();
+ var key = new EntryKey("test", MediaVaultType.Image);
+ set.Add(key);
+
+ // Act
+ var removed = set.Delete(key);
+
+ // Assert
+ Assert.That(removed, Is.True, "Delete should return true");
+ Assert.That(set.Has(key), Is.False, "Set should not contain the key after removal");
+ Assert.That(set.Size, Is.EqualTo(0), "Set should be empty");
+ }
+
+ [Test]
+ public void StructuralSet_CanEnumerateEntries()
+ {
+ // Arrange
+ var set = new StructuralSet();
+ var keys = new[]
+ {
+ new EntryKey("key1", MediaVaultType.Image),
+ new EntryKey("key2", MediaVaultType.Media),
+ new EntryKey("key3", MediaVaultType.Image)
+ };
+
+ foreach (var key in keys)
+ {
+ set.Add(key);
+ }
+
+ // Act
+ var retrievedKeys = set.ToList();
+
+ // Assert
+ Assert.That(retrievedKeys.Count, Is.EqualTo(3), "Should enumerate all entries");
+
+ foreach (var key in keys)
+ {
+ Assert.That(retrievedKeys.Contains(key), Is.True, $"Should contain key {key}");
+ }
+ }
+ }
+
+ [TestFixture]
+ public class FileUtilsTests
+ {
+ private string _testDirectory = null!;
+
+ [SetUp]
+ public void SetUp()
+ {
+ _testDirectory = Path.Combine(Path.GetTempPath(), "DeepDrftTests", "FileUtils", Guid.NewGuid().ToString());
+ Directory.CreateDirectory(_testDirectory);
+ }
+
+ [TearDown]
+ public void TearDown()
+ {
+ if (Directory.Exists(_testDirectory))
+ {
+ Directory.Delete(_testDirectory, true);
+ }
+ }
+
+ [Test]
+ public async Task FileUtils_CanWriteAndReadFile()
+ {
+ // Arrange
+ var testFile = Path.Combine(_testDirectory, "test.dat");
+ var testData = TestData.TestPngBytes;
+
+ // Act
+ await FileUtils.PutFileAsync(testFile, testData);
+ var fileBinary = await FileUtils.FetchFileAsync(testFile);
+
+ // Assert
+ Assert.That(File.Exists(testFile), Is.True, "File should exist after writing");
+ Assert.That(fileBinary, Is.Not.Null, "FileBinary should not be null");
+ Assert.That(fileBinary.Buffer.Length, Is.EqualTo(testData.Length), "Read data length should match");
+
+ for (int i = 0; i < testData.Length; i++)
+ {
+ Assert.That(fileBinary.Buffer[i], Is.EqualTo(testData[i]), $"Byte at index {i} should match");
+ }
+ }
+
+ [Test]
+ public async Task FileUtils_CanWriteAndReadJson()
+ {
+ // Arrange
+ var testFile = Path.Combine(_testDirectory, "test.json");
+ var testObject = new { Name = "Test", Value = 42, IsActive = true };
+
+ // Act
+ await FileUtils.PutObjectAsync(testFile, testObject);
+ var readObject = await FileUtils.FetchObjectAsync(testFile);
+
+ // Assert
+ Assert.That(File.Exists(testFile), Is.True, "JSON file should exist after writing");
+ Assert.That(readObject, Is.Not.Null, "Read object should not be null");
+ }
+
+ [Test]
+ public void FileUtils_HandlesNonExistentFile()
+ {
+ // Arrange
+ var nonExistentFile = Path.Combine(_testDirectory, "does-not-exist.dat");
+
+ // Act & Assert
+ Assert.ThrowsAsync(async () =>
+ {
+ await FileUtils.FetchFileAsync(nonExistentFile);
+ }, "Should throw FileNotFoundException for non-existent file");
+ }
+ }
+}