500 lines
22 KiB
C#
500 lines
22 KiB
C#
using System;
|
|
using Duplicati.Proprietary.DiskImage.General;
|
|
using NUnit.Framework;
|
|
using Assert = NUnit.Framework.Legacy.ClassicAssert;
|
|
|
|
namespace Duplicati.UnitTest.DiskImage.UnitTests;
|
|
|
|
public partial class DiskImageUnitTests : BasicSetupHelper
|
|
{
|
|
|
|
/// <summary>
|
|
/// Tests that a fully populated GeometryMetadata can be serialized to JSON and
|
|
/// deserialized back, preserving all field values.
|
|
/// </summary>
|
|
[Test]
|
|
public void Test_GeometryMetadata_RoundTrip_PreservesAllFields()
|
|
{
|
|
// Create a GeometryMetadata with all fields populated
|
|
var original = new GeometryMetadata
|
|
{
|
|
Version = 1,
|
|
Disk = new DiskGeometry
|
|
{
|
|
DevicePath = "/dev/sda",
|
|
Size = 100 * MiB,
|
|
SectorSize = 512,
|
|
Sectors = (int)(100 * MiB / 512),
|
|
TableType = PartitionTableType.GPT
|
|
},
|
|
PartitionTable = new PartitionTableGeometry
|
|
{
|
|
Type = PartitionTableType.GPT,
|
|
Size = 17408, // Typical GPT size
|
|
SectorSize = 512,
|
|
HasProtectiveMbr = true,
|
|
HeaderSize = 92
|
|
},
|
|
Partitions =
|
|
[
|
|
new PartitionGeometry
|
|
{
|
|
Number = 1,
|
|
Type = PartitionType.Primary,
|
|
StartOffset = 512 * 2048,
|
|
Size = 40 * MiB,
|
|
Name = "Test Partition 1",
|
|
FilesystemType = FileSystemType.FAT32,
|
|
VolumeGuid = Guid.Parse("12345678-1234-1234-1234-123456789abc"),
|
|
TableType = PartitionTableType.GPT
|
|
},
|
|
new PartitionGeometry
|
|
{
|
|
Number = 2,
|
|
Type = PartitionType.Primary,
|
|
StartOffset = 512 * 2048 + 40 * MiB,
|
|
Size = 40 * MiB,
|
|
Name = "Test Partition 2",
|
|
FilesystemType = FileSystemType.NTFS,
|
|
VolumeGuid = Guid.Parse("abcdef12-3456-7890-abcd-ef1234567890"),
|
|
TableType = PartitionTableType.GPT
|
|
}
|
|
],
|
|
Filesystems =
|
|
[
|
|
new FilesystemGeometry
|
|
{
|
|
PartitionNumber = 1,
|
|
Type = FileSystemType.FAT32,
|
|
PartitionStartOffset = 512 * 2048,
|
|
BlockSize = 4096,
|
|
Metadata = "{\"label\":\"FAT32_VOL\",\"serial\":\"1234-5678\"}"
|
|
},
|
|
new FilesystemGeometry
|
|
{
|
|
PartitionNumber = 2,
|
|
Type = FileSystemType.NTFS,
|
|
PartitionStartOffset = 512 * 2048 + 40 * MiB,
|
|
BlockSize = 4096,
|
|
Metadata = "{\"label\":\"NTFS_VOL\",\"guid\":\"{12345678-1234-1234-1234-123456789ABC}\"}"
|
|
}
|
|
]
|
|
};
|
|
|
|
// Serialize to JSON
|
|
var json = original.ToJson();
|
|
|
|
// Verify JSON is not null or empty
|
|
Assert.IsNotNull(json, "JSON output should not be null.");
|
|
Assert.IsNotEmpty(json, "JSON output should not be empty.");
|
|
|
|
// Deserialize from JSON
|
|
var deserialized = GeometryMetadata.FromJson(json);
|
|
|
|
// Verify deserialized object is not null
|
|
Assert.IsNotNull(deserialized, "Deserialized object should not be null.");
|
|
|
|
// Verify Version
|
|
Assert.AreEqual(original.Version, deserialized!.Version, "Version should match.");
|
|
|
|
// Verify Disk properties
|
|
Assert.IsNotNull(deserialized.Disk, "Disk should not be null after deserialization.");
|
|
Assert.AreEqual(original.Disk!.DevicePath, deserialized.Disk!.DevicePath, "Disk DevicePath should match.");
|
|
Assert.AreEqual(original.Disk.Size, deserialized.Disk.Size, "Disk Size should match.");
|
|
Assert.AreEqual(original.Disk.SectorSize, deserialized.Disk.SectorSize, "Disk SectorSize should match.");
|
|
Assert.AreEqual(original.Disk.Sectors, deserialized.Disk.Sectors, "Disk Sectors should match.");
|
|
Assert.AreEqual(original.Disk.TableType, deserialized.Disk.TableType, "Disk TableType should match.");
|
|
|
|
// Verify PartitionTable properties
|
|
Assert.IsNotNull(deserialized.PartitionTable, "PartitionTable should not be null after deserialization.");
|
|
Assert.AreEqual(original.PartitionTable!.Type, deserialized.PartitionTable!.Type, "PartitionTable Type should match.");
|
|
Assert.AreEqual(original.PartitionTable.Size, deserialized.PartitionTable.Size, "PartitionTable Size should match.");
|
|
Assert.AreEqual(original.PartitionTable.SectorSize, deserialized.PartitionTable.SectorSize, "PartitionTable SectorSize should match.");
|
|
Assert.AreEqual(original.PartitionTable.HasProtectiveMbr, deserialized.PartitionTable.HasProtectiveMbr, "PartitionTable HasProtectiveMbr should match.");
|
|
Assert.AreEqual(original.PartitionTable.HeaderSize, deserialized.PartitionTable.HeaderSize, "PartitionTable HeaderSize should match.");
|
|
|
|
// Verify Partitions
|
|
Assert.IsNotNull(deserialized.Partitions, "Partitions should not be null after deserialization.");
|
|
Assert.AreEqual(original.Partitions!.Count, deserialized.Partitions!.Count, "Partition count should match.");
|
|
|
|
for (int i = 0; i < original.Partitions.Count; i++)
|
|
{
|
|
var originalPart = original.Partitions[i];
|
|
var deserializedPart = deserialized.Partitions[i];
|
|
|
|
Assert.AreEqual(originalPart.Number, deserializedPart.Number, $"Partition {i + 1} Number should match.");
|
|
Assert.AreEqual(originalPart.Type, deserializedPart.Type, $"Partition {i + 1} Type should match.");
|
|
Assert.AreEqual(originalPart.StartOffset, deserializedPart.StartOffset, $"Partition {i + 1} StartOffset should match.");
|
|
Assert.AreEqual(originalPart.Size, deserializedPart.Size, $"Partition {i + 1} Size should match.");
|
|
Assert.AreEqual(originalPart.Name, deserializedPart.Name, $"Partition {i + 1} Name should match.");
|
|
Assert.AreEqual(originalPart.FilesystemType, deserializedPart.FilesystemType, $"Partition {i + 1} FilesystemType should match.");
|
|
Assert.AreEqual(originalPart.VolumeGuid, deserializedPart.VolumeGuid, $"Partition {i + 1} VolumeGuid should match.");
|
|
Assert.AreEqual(originalPart.TableType, deserializedPart.TableType, $"Partition {i + 1} TableType should match.");
|
|
}
|
|
|
|
// Verify Filesystems
|
|
Assert.IsNotNull(deserialized.Filesystems, "Filesystems should not be null after deserialization.");
|
|
Assert.AreEqual(original.Filesystems!.Count, deserialized.Filesystems!.Count, "Filesystem count should match.");
|
|
|
|
for (int i = 0; i < original.Filesystems.Count; i++)
|
|
{
|
|
var originalFs = original.Filesystems[i];
|
|
var deserializedFs = deserialized.Filesystems[i];
|
|
|
|
Assert.AreEqual(originalFs.PartitionNumber, deserializedFs.PartitionNumber, $"Filesystem {i + 1} PartitionNumber should match.");
|
|
Assert.AreEqual(originalFs.Type, deserializedFs.Type, $"Filesystem {i + 1} Type should match.");
|
|
Assert.AreEqual(originalFs.PartitionStartOffset, deserializedFs.PartitionStartOffset, $"Filesystem {i + 1} PartitionStartOffset should match.");
|
|
Assert.AreEqual(originalFs.BlockSize, deserializedFs.BlockSize, $"Filesystem {i + 1} BlockSize should match.");
|
|
Assert.AreEqual(originalFs.Metadata, deserializedFs.Metadata, $"Filesystem {i + 1} Metadata should match.");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tests that a GeometryMetadata with null optional fields can be serialized
|
|
/// and deserialized correctly.
|
|
/// </summary>
|
|
[Test]
|
|
public void Test_GeometryMetadata_RoundTrip_NullOptionalFields_PreservesStructure()
|
|
{
|
|
// Create a GeometryMetadata with minimal/null optional fields
|
|
var original = new GeometryMetadata
|
|
{
|
|
Version = 1,
|
|
Disk = new DiskGeometry
|
|
{
|
|
DevicePath = null, // Null optional field
|
|
Size = 50 * MiB,
|
|
SectorSize = 512,
|
|
Sectors = (int)(50 * MiB / 512),
|
|
TableType = PartitionTableType.MBR
|
|
},
|
|
PartitionTable = new PartitionTableGeometry
|
|
{
|
|
Type = PartitionTableType.MBR,
|
|
Size = 512,
|
|
SectorSize = 512,
|
|
HasProtectiveMbr = false,
|
|
HeaderSize = 0,
|
|
MbrSize = 512
|
|
},
|
|
Partitions =
|
|
[
|
|
new PartitionGeometry
|
|
{
|
|
Number = 1,
|
|
Type = PartitionType.Primary,
|
|
StartOffset = 512 * 2048,
|
|
Size = 40 * MiB,
|
|
Name = null, // Null optional field
|
|
FilesystemType = FileSystemType.Unknown,
|
|
VolumeGuid = null, // Null optional field
|
|
TableType = PartitionTableType.MBR
|
|
}
|
|
],
|
|
Filesystems = null // Null optional collection
|
|
};
|
|
|
|
// Serialize to JSON
|
|
var json = original.ToJson();
|
|
|
|
// Verify JSON is not null or empty
|
|
Assert.IsNotNull(json, "JSON output should not be null.");
|
|
Assert.IsNotEmpty(json, "JSON output should not be empty.");
|
|
|
|
// Deserialize from JSON
|
|
var deserialized = GeometryMetadata.FromJson(json);
|
|
|
|
// Verify deserialized object is not null
|
|
Assert.IsNotNull(deserialized, "Deserialized object should not be null.");
|
|
|
|
// Verify null fields remain null
|
|
Assert.IsNull(deserialized!.Disk!.DevicePath, "DevicePath should be null after deserialization.");
|
|
Assert.IsNull(deserialized.Filesystems, "Filesystems should be null after deserialization.");
|
|
|
|
// Verify partition with null fields
|
|
Assert.IsNotNull(deserialized.Partitions, "Partitions should not be null.");
|
|
Assert.AreEqual(1, deserialized.Partitions!.Count, "Should have 1 partition.");
|
|
Assert.IsNull(deserialized.Partitions[0].Name, "Partition Name should be null.");
|
|
Assert.IsNull(deserialized.Partitions[0].VolumeGuid, "Partition VolumeGuid should be null.");
|
|
|
|
// Verify other fields are preserved
|
|
Assert.AreEqual(original.Disk.Size, deserialized.Disk.Size, "Disk Size should match.");
|
|
Assert.AreEqual(original.Disk.SectorSize, deserialized.Disk.SectorSize, "Disk SectorSize should match.");
|
|
Assert.AreEqual(original.PartitionTable!.Type, deserialized.PartitionTable!.Type, "PartitionTable Type should match.");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tests that an empty GeometryMetadata (with all null/empty fields) can be
|
|
/// serialized and deserialized correctly.
|
|
/// </summary>
|
|
[Test]
|
|
public void Test_GeometryMetadata_RoundTrip_EmptyMetadata_HandlesGracefully()
|
|
{
|
|
// Create a GeometryMetadata with mostly empty/null fields
|
|
var original = new GeometryMetadata
|
|
{
|
|
Version = 1,
|
|
Disk = null,
|
|
PartitionTable = null,
|
|
Partitions = null,
|
|
Filesystems = null
|
|
};
|
|
|
|
// Serialize to JSON
|
|
var json = original.ToJson();
|
|
|
|
// Verify JSON is not null or empty
|
|
Assert.IsNotNull(json, "JSON output should not be null.");
|
|
Assert.IsNotEmpty(json, "JSON output should not be empty.");
|
|
|
|
// Deserialize from JSON
|
|
var deserialized = GeometryMetadata.FromJson(json);
|
|
|
|
// Verify deserialized object is not null
|
|
Assert.IsNotNull(deserialized, "Deserialized object should not be null.");
|
|
|
|
// Verify fields are null
|
|
Assert.IsNull(deserialized!.Disk, "Disk should be null.");
|
|
Assert.IsNull(deserialized.PartitionTable, "PartitionTable should be null.");
|
|
Assert.IsNull(deserialized.Partitions, "Partitions should be null.");
|
|
Assert.IsNull(deserialized.Filesystems, "Filesystems should be null.");
|
|
Assert.AreEqual(1, deserialized.Version, "Version should be preserved.");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tests that a GeometryMetadata with multiple partitions and filesystems of
|
|
/// different types can be serialized and deserialized correctly.
|
|
/// </summary>
|
|
[Test]
|
|
public void Test_GeometryMetadata_RoundTrip_MultiplePartitionsAndFilesystems_PreservesAllData()
|
|
{
|
|
// Create a GeometryMetadata with multiple partitions and filesystems
|
|
var original = new GeometryMetadata
|
|
{
|
|
Version = 1,
|
|
Disk = new DiskGeometry
|
|
{
|
|
DevicePath = "/dev/nvme0n1",
|
|
Size = 500 * MiB,
|
|
SectorSize = 4096, // 4K sectors
|
|
Sectors = (int)(500 * MiB / 4096),
|
|
TableType = PartitionTableType.GPT
|
|
},
|
|
PartitionTable = new PartitionTableGeometry
|
|
{
|
|
Type = PartitionTableType.GPT,
|
|
Size = 32768,
|
|
SectorSize = 4096,
|
|
HasProtectiveMbr = true,
|
|
HeaderSize = 92
|
|
},
|
|
Partitions =
|
|
[
|
|
new PartitionGeometry
|
|
{
|
|
Number = 1,
|
|
Type = PartitionType.Primary,
|
|
StartOffset = 4096 * 6, // Start at sector 6 (after GPT headers)
|
|
Size = 100 * MiB,
|
|
Name = "EFI System Partition",
|
|
FilesystemType = FileSystemType.FAT32,
|
|
VolumeGuid = Guid.NewGuid(),
|
|
TableType = PartitionTableType.GPT
|
|
},
|
|
new PartitionGeometry
|
|
{
|
|
Number = 2,
|
|
Type = PartitionType.Primary,
|
|
StartOffset = 4096 * 6 + 100 * MiB,
|
|
Size = 150 * MiB,
|
|
Name = "Windows OS",
|
|
FilesystemType = FileSystemType.NTFS,
|
|
VolumeGuid = Guid.NewGuid(),
|
|
TableType = PartitionTableType.GPT
|
|
},
|
|
new PartitionGeometry
|
|
{
|
|
Number = 3,
|
|
Type = PartitionType.Primary,
|
|
StartOffset = 4096 * 6 + 250 * MiB,
|
|
Size = 200 * MiB,
|
|
Name = "Linux Root",
|
|
FilesystemType = FileSystemType.Ext4,
|
|
VolumeGuid = Guid.NewGuid(),
|
|
TableType = PartitionTableType.GPT
|
|
}
|
|
],
|
|
Filesystems =
|
|
[
|
|
new FilesystemGeometry
|
|
{
|
|
PartitionNumber = 1,
|
|
Type = FileSystemType.FAT32,
|
|
PartitionStartOffset = 4096 * 6,
|
|
BlockSize = 4096,
|
|
Metadata = "{\"label\":\"EFI\"}"
|
|
},
|
|
new FilesystemGeometry
|
|
{
|
|
PartitionNumber = 2,
|
|
Type = FileSystemType.NTFS,
|
|
PartitionStartOffset = 4096 * 6 + 100 * MiB,
|
|
BlockSize = 4096,
|
|
Metadata = "{\"label\":\"Windows\"}"
|
|
},
|
|
new FilesystemGeometry
|
|
{
|
|
PartitionNumber = 3,
|
|
Type = FileSystemType.Ext4,
|
|
PartitionStartOffset = 4096 * 6 + 250 * MiB,
|
|
BlockSize = 4096,
|
|
Metadata = "{\"label\":\"/\"}"
|
|
}
|
|
]
|
|
};
|
|
|
|
// Serialize to JSON
|
|
var json = original.ToJson();
|
|
|
|
// Verify JSON is not null or empty
|
|
Assert.IsNotNull(json, "JSON output should not be null.");
|
|
Assert.IsNotEmpty(json, "JSON output should not be empty.");
|
|
|
|
// Deserialize from JSON
|
|
var deserialized = GeometryMetadata.FromJson(json);
|
|
|
|
// Verify deserialized object is not null
|
|
Assert.IsNotNull(deserialized, "Deserialized object should not be null.");
|
|
|
|
// Verify counts
|
|
Assert.AreEqual(3, deserialized!.Partitions!.Count, "Should have 3 partitions.");
|
|
Assert.AreEqual(3, deserialized.Filesystems!.Count, "Should have 3 filesystems.");
|
|
|
|
// Verify each partition's filesystem type
|
|
Assert.AreEqual(FileSystemType.FAT32, deserialized.Partitions[0].FilesystemType, "Partition 1 should be FAT32.");
|
|
Assert.AreEqual(FileSystemType.NTFS, deserialized.Partitions[1].FilesystemType, "Partition 2 should be NTFS.");
|
|
Assert.AreEqual(FileSystemType.Ext4, deserialized.Partitions[2].FilesystemType, "Partition 3 should be Ext4.");
|
|
|
|
// Verify each filesystem's type
|
|
Assert.AreEqual(FileSystemType.FAT32, deserialized.Filesystems[0].Type, "Filesystem 1 should be FAT32.");
|
|
Assert.AreEqual(FileSystemType.NTFS, deserialized.Filesystems[1].Type, "Filesystem 2 should be NTFS.");
|
|
Assert.AreEqual(FileSystemType.Ext4, deserialized.Filesystems[2].Type, "Filesystem 3 should be Ext4.");
|
|
|
|
// Verify 4K sector size is preserved
|
|
Assert.AreEqual(4096, deserialized.Disk!.SectorSize, "Disk SectorSize should be 4096.");
|
|
Assert.AreEqual(4096, deserialized.PartitionTable!.SectorSize, "PartitionTable SectorSize should be 4096.");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tests that serializing an MBR-based GeometryMetadata produces valid JSON
|
|
/// that can be correctly deserialized.
|
|
/// </summary>
|
|
[Test]
|
|
public void Test_GeometryMetadata_RoundTrip_MBRType_PreservesMBRData()
|
|
{
|
|
var original = new GeometryMetadata
|
|
{
|
|
Version = 1,
|
|
Disk = new DiskGeometry
|
|
{
|
|
DevicePath = "/dev/sdb",
|
|
Size = 200 * MiB,
|
|
SectorSize = 512,
|
|
Sectors = (int)(200 * MiB / 512),
|
|
TableType = PartitionTableType.MBR
|
|
},
|
|
PartitionTable = new PartitionTableGeometry
|
|
{
|
|
Type = PartitionTableType.MBR,
|
|
Size = 512,
|
|
SectorSize = 512,
|
|
HasProtectiveMbr = false,
|
|
MbrSize = 512
|
|
},
|
|
Partitions =
|
|
[
|
|
new PartitionGeometry
|
|
{
|
|
Number = 1,
|
|
Type = PartitionType.Primary,
|
|
StartOffset = 512 * 2048,
|
|
Size = 100 * MiB,
|
|
Name = "Primary Partition",
|
|
FilesystemType = FileSystemType.FAT32,
|
|
TableType = PartitionTableType.MBR
|
|
},
|
|
new PartitionGeometry
|
|
{
|
|
Number = 2,
|
|
Type = PartitionType.Primary,
|
|
StartOffset = 512 * 2048 + 100 * MiB,
|
|
Size = 90 * MiB,
|
|
Name = "Secondary Partition",
|
|
FilesystemType = FileSystemType.NTFS,
|
|
TableType = PartitionTableType.MBR
|
|
}
|
|
],
|
|
Filesystems =
|
|
[
|
|
new FilesystemGeometry
|
|
{
|
|
PartitionNumber = 1,
|
|
Type = FileSystemType.FAT32,
|
|
PartitionStartOffset = 512 * 2048,
|
|
BlockSize = 4096,
|
|
Metadata = null
|
|
}
|
|
]
|
|
};
|
|
|
|
// Serialize to JSON
|
|
var json = original.ToJson();
|
|
|
|
// Deserialize from JSON
|
|
var deserialized = GeometryMetadata.FromJson(json);
|
|
|
|
// Verify MBR-specific fields
|
|
Assert.IsNotNull(deserialized, "Deserialized object should not be null.");
|
|
Assert.AreEqual(PartitionTableType.MBR, deserialized!.Disk!.TableType, "Disk TableType should be MBR.");
|
|
Assert.AreEqual(PartitionTableType.MBR, deserialized.PartitionTable!.Type, "PartitionTable Type should be MBR.");
|
|
Assert.AreEqual(512, deserialized.PartitionTable.MbrSize, "PartitionTable MbrSize should be 512.");
|
|
Assert.IsFalse(deserialized.PartitionTable.HasProtectiveMbr, "PartitionTable HasProtectiveMbr should be false.");
|
|
|
|
// Verify all partitions have MBR table type
|
|
foreach (var partition in deserialized.Partitions!)
|
|
{
|
|
Assert.AreEqual(PartitionTableType.MBR, partition.TableType, "All partitions should have MBR TableType.");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tests that deserializing invalid JSON throws an appropriate exception.
|
|
/// </summary>
|
|
[Test]
|
|
public void Test_GeometryMetadata_FromJson_InvalidJson_ThrowsException()
|
|
{
|
|
// Test with invalid JSON - should throw JsonException
|
|
var invalidJson = "{invalid json";
|
|
Assert.Throws<System.Text.Json.JsonException>(() => GeometryMetadata.FromJson(invalidJson));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tests that deserializing empty JSON object creates a minimal GeometryMetadata
|
|
/// with default property values set by the class.
|
|
/// </summary>
|
|
[Test]
|
|
public void Test_GeometryMetadata_FromJson_EmptyObject_CreatesDefaultInstance()
|
|
{
|
|
var emptyJson = "{}";
|
|
var result = GeometryMetadata.FromJson(emptyJson);
|
|
|
|
Assert.IsNotNull(result, "Should create an instance from empty JSON object.");
|
|
// Version has a default value of 1 in the class definition
|
|
Assert.AreEqual(1, result!.Version, "Version should have default value (1) from class definition.");
|
|
Assert.IsNull(result.Disk, "Disk should be null.");
|
|
Assert.IsNull(result.PartitionTable, "PartitionTable should be null.");
|
|
Assert.IsNull(result.Partitions, "Partitions should be null.");
|
|
Assert.IsNull(result.Filesystems, "Filesystems should be null.");
|
|
}
|
|
|
|
} |