diff --git a/SabreTools.DatItems/DatItem.cs b/SabreTools.DatItems/DatItem.cs
index 871b9d46..d11bf452 100644
--- a/SabreTools.DatItems/DatItem.cs
+++ b/SabreTools.DatItems/DatItem.cs
@@ -544,7 +544,7 @@ namespace SabreTools.DatItems
if (file == null)
continue;
- // If we don't have a Dis, Media, or Rom, we skip checking for duplicates
+ // If we don't have a Disk, Media, or Rom, we skip checking for duplicates
if (file.ItemType != ItemType.Disk && file.ItemType != ItemType.Media && file.ItemType != ItemType.Rom)
continue;
diff --git a/SabreTools.FileTypes/Aaru/AaruFormat.cs b/SabreTools.FileTypes/Aaru/AaruFormat.cs
index f5418033..bd7ea957 100644
--- a/SabreTools.FileTypes/Aaru/AaruFormat.cs
+++ b/SabreTools.FileTypes/Aaru/AaruFormat.cs
@@ -40,6 +40,14 @@ namespace SabreTools.FileTypes.Aaru
#region Constructors
+ ///
+ /// Empty constructor
+ ///
+ public AaruFormat()
+ {
+ Type = FileType.AaruFormat;
+ }
+
///
/// Create a new AaruFormat from an input file
///
diff --git a/SabreTools.FileTypes/CHD/CHDFile.cs b/SabreTools.FileTypes/CHD/CHDFile.cs
index 76b5dfba..ab77b278 100644
--- a/SabreTools.FileTypes/CHD/CHDFile.cs
+++ b/SabreTools.FileTypes/CHD/CHDFile.cs
@@ -23,6 +23,14 @@ namespace SabreTools.FileTypes.CHD
#region Constructors
+ ///
+ /// Empty constructor
+ ///
+ public CHDFile()
+ {
+ Type = FileType.CHD;
+ }
+
///
/// Create a new CHDFile from an input file
///
diff --git a/SabreTools.FileTypes/CHD/CHDFileV1.cs b/SabreTools.FileTypes/CHD/CHDFileV1.cs
index 2f6ffba5..6611f159 100644
--- a/SabreTools.FileTypes/CHD/CHDFileV1.cs
+++ b/SabreTools.FileTypes/CHD/CHDFileV1.cs
@@ -9,7 +9,7 @@ namespace SabreTools.FileTypes.CHD
///
/// CHD V1 File
///
- internal class CHDFileV1 : CHDFile
+ public class CHDFileV1 : CHDFile
{
///
/// CHD flags
diff --git a/SabreTools.FileTypes/CHD/CHDFileV2.cs b/SabreTools.FileTypes/CHD/CHDFileV2.cs
index d89bec34..dcf1dff4 100644
--- a/SabreTools.FileTypes/CHD/CHDFileV2.cs
+++ b/SabreTools.FileTypes/CHD/CHDFileV2.cs
@@ -9,7 +9,7 @@ namespace SabreTools.FileTypes.CHD
///
/// CHD V2 File
///
- internal class CHDFileV2 : CHDFile
+ public class CHDFileV2 : CHDFile
{
///
/// CHD flags
diff --git a/SabreTools.FileTypes/CHD/CHDFileV3.cs b/SabreTools.FileTypes/CHD/CHDFileV3.cs
index a18d2d07..0d2829fb 100644
--- a/SabreTools.FileTypes/CHD/CHDFileV3.cs
+++ b/SabreTools.FileTypes/CHD/CHDFileV3.cs
@@ -9,7 +9,7 @@ namespace SabreTools.FileTypes.CHD
///
/// CHD V3 File
///
- internal class CHDFileV3 : CHDFile
+ public class CHDFileV3 : CHDFile
{
///
/// CHD flags
diff --git a/SabreTools.FileTypes/CHD/CHDFileV4.cs b/SabreTools.FileTypes/CHD/CHDFileV4.cs
index b26a6663..3ebb9cca 100644
--- a/SabreTools.FileTypes/CHD/CHDFileV4.cs
+++ b/SabreTools.FileTypes/CHD/CHDFileV4.cs
@@ -9,7 +9,7 @@ namespace SabreTools.FileTypes.CHD
///
/// CHD V4 File
///
- internal class CHDFileV4 : CHDFile
+ public class CHDFileV4 : CHDFile
{
///
/// CHD flags
diff --git a/SabreTools.FileTypes/CHD/CHDFileV5.cs b/SabreTools.FileTypes/CHD/CHDFileV5.cs
index ab55c3a2..a50658e0 100644
--- a/SabreTools.FileTypes/CHD/CHDFileV5.cs
+++ b/SabreTools.FileTypes/CHD/CHDFileV5.cs
@@ -8,7 +8,7 @@ namespace SabreTools.FileTypes.CHD
///
/// CHD V5 File
///
- internal class CHDFileV5 : CHDFile
+ public class CHDFileV5 : CHDFile
{
///
/// Uncompressed map format
diff --git a/SabreTools.Test/DatItems/DatItemTests.cs b/SabreTools.Test/DatItems/DatItemTests.cs
new file mode 100644
index 00000000..d8d276e6
--- /dev/null
+++ b/SabreTools.Test/DatItems/DatItemTests.cs
@@ -0,0 +1,214 @@
+using SabreTools.Core;
+using SabreTools.DatItems;
+using SabreTools.FileTypes;
+using SabreTools.FileTypes.Aaru;
+using SabreTools.FileTypes.Archives;
+using SabreTools.FileTypes.CHD;
+using Xunit;
+
+namespace SabreTools.Test.DatItems
+{
+ public class DatItemTests
+ {
+ [Theory]
+ [InlineData(null, ItemType.Rom)]
+ [InlineData(ItemType.Disk, ItemType.Disk)]
+ [InlineData(ItemType.Media, ItemType.Media)]
+ [InlineData(ItemType.Rom, ItemType.Rom)]
+ public void CreateItemTypeTest(ItemType? itemType, ItemType expected)
+ {
+ var actual = DatItem.Create(itemType);
+ Assert.Equal(expected, actual.ItemType);
+ }
+
+ [Theory]
+ [InlineData(FileType.None, ItemType.Rom)]
+ [InlineData(FileType.Folder, null)]
+ [InlineData(FileType.AaruFormat, ItemType.Media)]
+ [InlineData(FileType.CHD, ItemType.Disk)]
+ [InlineData(FileType.ZipArchive, ItemType.Rom)]
+ public void CreateBaseFileTest(FileType fileType, ItemType? expected)
+ {
+ var baseFile = CreateBaseFile(fileType);
+ var actual = DatItem.Create(baseFile);
+ Assert.Equal(expected, actual?.ItemType);
+ }
+
+ [Fact]
+ public void DuplicateStatusUnequalTest()
+ {
+ var rom = new Rom();
+ var disk = new Disk();
+ var actual = rom.GetDuplicateStatus(disk);
+ Assert.Equal((DupeType)0x00, actual);
+ }
+
+ [Fact]
+ public void DuplicateStatusExternalAllTest()
+ {
+ var romA = new Rom
+ {
+ Name = "same-name",
+ CRC = "DEADBEEF",
+ Machine = new Machine
+ {
+ Name = "name-same",
+ },
+ Source = new Source
+ {
+ Index = 0,
+ },
+ };
+ var romB = new Rom
+ {
+ Name = "same-name",
+ CRC = "DEADBEEF",
+ Machine = new Machine
+ {
+ Name = "name-same",
+ },
+ Source = new Source
+ {
+ Index = 1,
+ },
+ };
+
+ var actual = romA.GetDuplicateStatus(romB);
+ Assert.Equal(DupeType.External | DupeType.All, actual);
+ }
+
+ [Fact]
+ public void DuplicateStatusExternalHashTest()
+ {
+ var romA = new Rom
+ {
+ Name = "same-name",
+ CRC = "DEADBEEF",
+ Machine = new Machine
+ {
+ Name = "name-same",
+ },
+ Source = new Source
+ {
+ Index = 0,
+ },
+ };
+ var romB = new Rom
+ {
+ Name = "same-name",
+ CRC = "DEADBEEF",
+ Machine = new Machine
+ {
+ Name = "not-name-same",
+ },
+ Source = new Source
+ {
+ Index = 1,
+ },
+ };
+
+ var actual = romA.GetDuplicateStatus(romB);
+ Assert.Equal(DupeType.External | DupeType.Hash, actual);
+ }
+
+ [Fact]
+ public void DuplicateStatusInternalAllTest()
+ {
+ var romA = new Rom
+ {
+ Name = "same-name",
+ CRC = "DEADBEEF",
+ Machine = new Machine
+ {
+ Name = "name-same",
+ },
+ Source = new Source
+ {
+ Index = 0,
+ },
+ };
+ var romB = new Rom
+ {
+ Name = "same-name",
+ CRC = "DEADBEEF",
+ Machine = new Machine
+ {
+ Name = "name-same",
+ },
+ Source = new Source
+ {
+ Index = 0,
+ },
+ };
+
+ var actual = romA.GetDuplicateStatus(romB);
+ Assert.Equal(DupeType.Internal | DupeType.All, actual);
+ }
+
+ [Fact]
+ public void DuplicateStatusInternalHashTest()
+ {
+ var romA = new Rom
+ {
+ Name = "same-name",
+ CRC = "DEADBEEF",
+ Machine = new Machine
+ {
+ Name = "name-same",
+ },
+ Source = new Source
+ {
+ Index = 0,
+ },
+ };
+ var romB = new Rom
+ {
+ Name = "same-name",
+ CRC = "DEADBEEF",
+ Machine = new Machine
+ {
+ Name = "not-name-same",
+ },
+ Source = new Source
+ {
+ Index = 0,
+ },
+ };
+
+ var actual = romA.GetDuplicateStatus(romB);
+ Assert.Equal(DupeType.Internal | DupeType.Hash, actual);
+ }
+
+ [Theory]
+ [InlineData(null, null, true)]
+ [InlineData(null, new byte[0], true)]
+ [InlineData(new byte[0], null, true)]
+ [InlineData(new byte[] { 0x00 }, new byte[] { 0x00, 0x01 }, false)]
+ [InlineData(new byte[] { 0x00 }, new byte[] { 0x01 }, false)]
+ [InlineData(new byte[] { 0x00 }, new byte[] { 0x00 }, true)]
+ public void ConditionalHashEqualsTest(byte[] first, byte[] second, bool expected)
+ {
+ bool actual = DatItem.ConditionalHashEquals(first, second);
+ Assert.Equal(expected, actual);
+ }
+
+ // TODO: Add tests for DatItem.Merge
+ // TODO: Add tests for ResolveNames
+ // TODO: Add tests for Sort
+
+ ///
+ /// Create a BaseFile for testing
+ ///
+ private BaseFile CreateBaseFile(FileType fileType)
+ {
+ return fileType switch
+ {
+ FileType.Folder => new Folder(),
+ FileType.AaruFormat => new AaruFormat(),
+ FileType.CHD => new CHDFileV5(),
+ FileType.ZipArchive => new ZipArchive(),
+ _ => new BaseFile(),
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/SabreTools.Test/SabreTools.Test.csproj b/SabreTools.Test/SabreTools.Test.csproj
index fa4e2890..a87e1791 100644
--- a/SabreTools.Test/SabreTools.Test.csproj
+++ b/SabreTools.Test/SabreTools.Test.csproj
@@ -8,6 +8,7 @@
+