Files
duplicati/Duplicati/UnitTest/Issue6552.cs
T

113 lines
4.3 KiB
C#
Raw Normal View History

2026-01-28 21:03:03 +01:00
using System;
using System.IO;
using System.Linq;
using NUnit.Framework;
using Duplicati.Library.Main;
using Microsoft.Data.Sqlite;
using Assert = NUnit.Framework.Legacy.ClassicAssert;
namespace Duplicati.UnitTest
{
public class Issue6552 : BasicSetupHelper
{
[Test]
[Category("Issue6552")]
public void RecreateDbShouldNotCreateInvalidDeletedBlockReferences()
{
// Arrange
var testopts = TestOptions;
testopts["backup-test-samples"] = "0";
testopts["no-encryption"] = "true"; // Easier to manipulate files
var sourceFolder = Path.Combine(DATAFOLDER, "source");
Directory.CreateDirectory(sourceFolder);
// Step 1: Create file A.txt and backup
File.WriteAllText(Path.Combine(sourceFolder, "A.txt"), "Content A");
string savedDindexPath = null;
using (var c = new Controller("file://" + TARGETFOLDER, testopts, null))
{
var res = c.Backup(new[] { sourceFolder });
Assert.AreEqual(0, res.Errors.Count());
}
// Save the dindex file from first backup
var dindexFiles = Directory.GetFiles(TARGETFOLDER, "*.dindex.*");
Assert.IsTrue(dindexFiles.Length > 0, "Should have at least one dindex file");
savedDindexPath = Path.Combine(DATAFOLDER, Path.GetFileName(dindexFiles[0]));
File.Copy(dindexFiles[0], savedDindexPath);
// Identify the dblock and dlist from the first backup
var firstDblock = Directory.GetFiles(TARGETFOLDER, "*.dblock.*").First();
var firstDlist = Directory.GetFiles(TARGETFOLDER, "*.dlist.*").First();
// Step 2: Delete A.txt, create B.txt and backup
File.Delete(Path.Combine(sourceFolder, "A.txt"));
File.WriteAllText(Path.Combine(sourceFolder, "B.txt"), "Content B - different content");
using (var c = new Controller("file://" + TARGETFOLDER, testopts, null))
{
var res = c.Backup(new[] { sourceFolder });
Assert.AreEqual(0, res.Errors.Count());
}
// Step 3: Delete the dblock file that the saved dindex references
// and add the old dindex back
// Delete the dblock associated with the first backup
if (File.Exists(firstDblock))
{
File.Delete(firstDblock);
}
// Delete the dlist associated with the first backup to ensure blocks are orphaned
if (File.Exists(firstDlist))
{
File.Delete(firstDlist);
}
// Delete the database to force recreate
File.Delete(testopts["dbpath"]);
// Add the old dindex file back (which references a dblock that may not exist)
File.Copy(savedDindexPath, Path.Combine(TARGETFOLDER, Path.GetFileName(savedDindexPath)), true);
// Step 4: Recreate the database
using (var c = new Controller("file://" + TARGETFOLDER, testopts, null))
{
// This should complete but may create invalid references
try
{
var res = c.Repair();
}
catch (Exception ex)
{
// Recreate might fail, but we want to check the DB state
TestContext.Progress.WriteLine($"Repair failed: {ex.Message}");
}
}
// Step 5: Check for invalid DeletedBlock references
using (var connection = new SqliteConnection($"Data Source={testopts["dbpath"]};Pooling=false"))
{
connection.Open();
var command = connection.CreateCommand();
// Check for DeletedBlock entries with invalid VolumeID
command.CommandText = @"
SELECT COUNT(*)
FROM DeletedBlock
WHERE VolumeID NOT IN (SELECT ID FROM RemoteVolume)
";
var invalidCount = (long)command.ExecuteScalar();
// This assertion will fail with the current bug
Assert.AreEqual(0, invalidCount,
$"Found {invalidCount} DeletedBlock entries with invalid VolumeID references");
}
}
}
}