mirror of
https://github.com/duplicati/duplicati.git
synced 2026-05-06 07:16:38 -04:00
fa0c9c72a9
This PR guards agains inserting entries into the DeletedVolume if they reference non-existing volumes, which can happen in error scenarios. This fixes #6553
110 lines
4.2 KiB
C#
110 lines
4.2 KiB
C#
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 Issue6553 : BasicSetupHelper
|
|
{
|
|
[Test]
|
|
[Category("Issue6553")]
|
|
public void RecreateDbShouldNotLoseFilesetWhenDindexLacksDblock()
|
|
{
|
|
// Arrange
|
|
var testopts = TestOptions;
|
|
testopts["backup-test-samples"] = "0";
|
|
testopts["no-encryption"] = "true";
|
|
testopts["keep-versions"] = "0"; // Keep all versions
|
|
|
|
var sourceFolder = Path.Combine(DATAFOLDER, "source");
|
|
Directory.CreateDirectory(sourceFolder);
|
|
|
|
// Step 1: Create file A and backup (Backup 1)
|
|
File.WriteAllText(Path.Combine(sourceFolder, "A.txt"), "Content A");
|
|
|
|
using (var c = new Controller("file://" + TARGETFOLDER, testopts, null))
|
|
{
|
|
var res = c.Backup(new[] { sourceFolder });
|
|
Assert.AreEqual(0, res.Errors.Count());
|
|
}
|
|
|
|
// Save dindex1
|
|
var dindexFiles = Directory.GetFiles(TARGETFOLDER, "*.dindex.*");
|
|
Assert.AreEqual(1, dindexFiles.Length);
|
|
var dindex1Path = Path.Combine(DATAFOLDER, "dindex1.zip");
|
|
File.Copy(dindexFiles[0], dindex1Path);
|
|
|
|
// Step 2: Delete DB and Backup A again (Backup 2)
|
|
// This forces A to be uploaded to a new dblock (dblock2)
|
|
File.Delete(testopts["dbpath"]);
|
|
|
|
foreach (var f in Directory.GetFiles(TARGETFOLDER))
|
|
File.Delete(f);
|
|
|
|
using (var c = new Controller("file://" + TARGETFOLDER, testopts, null))
|
|
{
|
|
var res = c.Backup(new[] { sourceFolder });
|
|
Assert.AreEqual(0, res.Errors.Count());
|
|
}
|
|
|
|
// Step 3: Prepare for Recreate
|
|
File.Delete(testopts["dbpath"]);
|
|
|
|
// Restore dindex1. Rename it to ensure it is processed FIRST.
|
|
var dindex1RestorePath = Path.Combine(TARGETFOLDER, "duplicati-00000000000000000000000000000000.dindex.zip");
|
|
File.Copy(dindex1Path, dindex1RestorePath);
|
|
|
|
// Step 4: Recreate the database
|
|
using (var c = new Controller("file://" + TARGETFOLDER, testopts, null))
|
|
{
|
|
try
|
|
{
|
|
var res = c.Repair();
|
|
}
|
|
catch (Exception)
|
|
{
|
|
// Repair is expected to fail or warn due to missing blocks
|
|
}
|
|
}
|
|
|
|
// Step 5: Verify that we have at least one version (Backup 2)
|
|
using (var c = new Controller("file://" + TARGETFOLDER, testopts, null))
|
|
{
|
|
var listRes = c.List();
|
|
|
|
Assert.IsTrue(listRes.Filesets.Count() > 0,
|
|
"Expected at least one backup version after recreate, but found none");
|
|
}
|
|
|
|
// Step 6: Check for DeletedBlock entries pointing to Temporary volumes
|
|
using (var connection = new SqliteConnection($"Data Source={testopts["dbpath"]};Pooling=false"))
|
|
{
|
|
connection.Open();
|
|
var command = connection.CreateCommand();
|
|
|
|
command.CommandText = @"
|
|
SELECT COUNT(*)
|
|
FROM DeletedBlock
|
|
WHERE VolumeID IN (SELECT ID FROM RemoteVolume WHERE State = 'Temporary')
|
|
";
|
|
var tempCount = (long)command.ExecuteScalar();
|
|
|
|
// With the fix (and apparently even without it in this environment), this should be 0
|
|
Assert.AreEqual(0, tempCount, "Should not have DeletedBlock entries pointing to Temporary volumes");
|
|
|
|
command.CommandText = @"
|
|
SELECT COUNT(*)
|
|
FROM DeletedBlock
|
|
WHERE VolumeID NOT IN (SELECT ID FROM RemoteVolume)
|
|
";
|
|
var invalidCount = (long)command.ExecuteScalar();
|
|
Assert.AreEqual(0, invalidCount, "Should not have DeletedBlock entries with invalid VolumeID references");
|
|
}
|
|
}
|
|
}
|
|
}
|