From e82366e47f77377ca5bd4ee747bb1e7759b1d865 Mon Sep 17 00:00:00 2001 From: daniel-c-harvey Date: Thu, 4 Sep 2025 14:20:29 -0400 Subject: [PATCH] DeepDrftTests.csproj - FileDatabase Tests --- DeepDrftHome.sln | 61 +++++++ DeepDrftTests/DeepDrftTests.csproj | 27 +++ DeepDrftTests/FileDatabaseTests.cs | 229 ++++++++++++++++++++++++++ DeepDrftTests/ModelTests.cs | 255 +++++++++++++++++++++++++++++ DeepDrftTests/TestData.cs | 52 ++++++ DeepDrftTests/UtilityTests.cs | 252 ++++++++++++++++++++++++++++ 6 files changed, 876 insertions(+) create mode 100644 DeepDrftTests/DeepDrftTests.csproj create mode 100644 DeepDrftTests/FileDatabaseTests.cs create mode 100644 DeepDrftTests/ModelTests.cs create mode 100644 DeepDrftTests/TestData.cs create mode 100644 DeepDrftTests/UtilityTests.cs 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"); + } + } +}