Add Git overlay, operation tools, and virtualized preview so large folders stay responsive.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -191,3 +191,239 @@ public class EntryAndSearchTests
|
||||
Assert.All(work, w => Assert.Equal(100, w.SizeBytes));
|
||||
}
|
||||
}
|
||||
|
||||
public class TransferStoreTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task Incomplete_jobs_roundtrip_and_history_is_append_only()
|
||||
{
|
||||
await using var store = await Stores.Open();
|
||||
var queued = new TransferJob
|
||||
{
|
||||
Op = TransferOp.Copy,
|
||||
SourcePath = @"D:\src\a.txt",
|
||||
DestinationPath = @"E:\dst\a.txt",
|
||||
Status = TransferStatus.Queued,
|
||||
CreatedUtc = DateTimeOffset.UtcNow,
|
||||
RetryCount = 0,
|
||||
WaitReason = null
|
||||
};
|
||||
queued.Id = await store.Transfers.InsertAsync(queued);
|
||||
Assert.True(queued.Id > 0);
|
||||
Assert.True(queued.SortOrder > 0);
|
||||
|
||||
queued.Status = TransferStatus.Failed;
|
||||
queued.Error = "disk full";
|
||||
queued.RetryCount = 1;
|
||||
queued.BytesDone = 12;
|
||||
queued.CurrentPath = @"D:\src\a.txt";
|
||||
await store.Transfers.UpdateAsync(queued);
|
||||
|
||||
var incomplete = await store.Transfers.GetIncompleteAsync();
|
||||
var loaded = Assert.Single(incomplete);
|
||||
Assert.Equal(queued.Id, loaded.Id);
|
||||
Assert.Equal(TransferStatus.Failed, loaded.Status);
|
||||
Assert.Equal("disk full", loaded.Error);
|
||||
Assert.Equal(1, loaded.RetryCount);
|
||||
Assert.Equal(12, loaded.BytesDone);
|
||||
Assert.Equal(@"D:\src\a.txt", loaded.CurrentPath);
|
||||
|
||||
queued.Status = TransferStatus.Done;
|
||||
queued.Error = null;
|
||||
queued.Dismissed = true;
|
||||
await store.Transfers.UpdateAsync(queued);
|
||||
|
||||
Assert.Empty(await store.Transfers.GetIncompleteAsync());
|
||||
var history = await store.Transfers.GetHistoryAsync(10);
|
||||
var done = Assert.Single(history);
|
||||
Assert.Equal(TransferStatus.Done, done.Status);
|
||||
Assert.True(done.Dismissed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Schema_is_version_4()
|
||||
{
|
||||
await using var store = await Stores.Open();
|
||||
await store.Transfers.InsertAsync(new TransferJob
|
||||
{
|
||||
Op = TransferOp.Delete,
|
||||
SourcePath = @"C:\temp\a.txt",
|
||||
DestinationPath = "recycle",
|
||||
Status = TransferStatus.Queued,
|
||||
CreatedUtc = DateTimeOffset.UtcNow,
|
||||
AdditionalSources = [@"C:\temp\a.txt"]
|
||||
});
|
||||
var loaded = Assert.Single(await store.Transfers.GetIncompleteAsync());
|
||||
Assert.Equal(TransferOp.Delete, loaded.Op);
|
||||
Assert.Equal([@"C:\temp\a.txt"], loaded.AdditionalSources);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Waiting_jobs_are_incomplete_not_history()
|
||||
{
|
||||
await using var store = await Stores.Open();
|
||||
var job = new TransferJob
|
||||
{
|
||||
Op = TransferOp.Move,
|
||||
SourcePath = @"\\nas\share\file.bin",
|
||||
DestinationPath = @"F:\backup\file.bin",
|
||||
Status = TransferStatus.Waiting,
|
||||
WaitReason = "Destination unavailable",
|
||||
CreatedUtc = DateTimeOffset.UtcNow
|
||||
};
|
||||
job.Id = await store.Transfers.InsertAsync(job);
|
||||
var incomplete = Assert.Single(await store.Transfers.GetIncompleteAsync());
|
||||
Assert.Equal(TransferStatus.Waiting, incomplete.Status);
|
||||
Assert.Equal("Destination unavailable", incomplete.WaitReason);
|
||||
Assert.Empty(await store.Transfers.GetHistoryAsync(10));
|
||||
}
|
||||
}
|
||||
|
||||
public class FileRelationTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task Relations_roundtrip_and_normalize_entry_order()
|
||||
{
|
||||
await using var store = await Stores.Open();
|
||||
var source = await store.AddSourceAsync(@"C:\d");
|
||||
var a = await store.Entries.UpsertAsync(new IndexEntry
|
||||
{
|
||||
SourceId = source.Id,
|
||||
Name = "a.bin",
|
||||
NameNorm = "a.bin",
|
||||
PathRel = "a.bin",
|
||||
SizeBytes = 8,
|
||||
LastSeenUtc = DateTimeOffset.UtcNow
|
||||
});
|
||||
var b = await store.Entries.UpsertAsync(new IndexEntry
|
||||
{
|
||||
SourceId = source.Id,
|
||||
Name = "b.bin",
|
||||
NameNorm = "b.bin",
|
||||
PathRel = "b.bin",
|
||||
SizeBytes = 8,
|
||||
LastSeenUtc = DateTimeOffset.UtcNow
|
||||
});
|
||||
|
||||
await store.Relations.UpsertAsync(new FileRelation
|
||||
{
|
||||
LeftEntryId = b,
|
||||
RightEntryId = a,
|
||||
Kind = FileRelationKind.IntentionalDuplicate,
|
||||
Origin = FileRelationOrigin.User,
|
||||
CreatedUtc = DateTimeOffset.UtcNow
|
||||
});
|
||||
|
||||
var relation = Assert.Single(await store.Relations.GetAmongAsync([a, b]));
|
||||
Assert.Equal(Math.Min(a, b), relation.LeftEntryId);
|
||||
Assert.Equal(Math.Max(a, b), relation.RightEntryId);
|
||||
Assert.Equal(FileRelationKind.IntentionalDuplicate, relation.Kind);
|
||||
|
||||
await store.Relations.DeleteAmongAsync([a, b], [FileRelationKind.IntentionalDuplicate]);
|
||||
Assert.Empty(await store.Relations.GetAmongAsync([a, b]));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Rename_batch_roundtrip_and_mark_undone()
|
||||
{
|
||||
await using var store = await Stores.Open();
|
||||
var id = await store.RenameBatches.CreateAsync(
|
||||
[
|
||||
new RenameBatchItem(@"C:\a\old.txt", @"C:\a\new.txt", 0),
|
||||
new RenameBatchItem(@"C:\a\one.txt", @"C:\a\two.txt", 1)
|
||||
]);
|
||||
var batch = await store.RenameBatches.GetLatestUndoableAsync();
|
||||
Assert.NotNull(batch);
|
||||
Assert.Equal(id, batch.Id);
|
||||
Assert.Equal(2, batch.Items.Count);
|
||||
Assert.Equal(@"C:\a\old.txt", batch.Items[0].OldPath);
|
||||
Assert.Equal(@"C:\a\two.txt", batch.Items[1].NewPath);
|
||||
await store.RenameBatches.MarkUndoneAsync(id);
|
||||
Assert.Null(await store.RenameBatches.GetLatestUndoableAsync());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Sync_profile_roundtrip()
|
||||
{
|
||||
await using var store = await Stores.Open();
|
||||
var id = await store.SyncProfiles.UpsertAsync(new SyncProfile
|
||||
{
|
||||
Name = "Photos",
|
||||
SourcePath = @"C:\src",
|
||||
DestPath = @"D:\dst",
|
||||
Mode = SyncMode.CopyUpdate,
|
||||
Excludes = "*.tmp",
|
||||
AutoRun = true,
|
||||
SourceVolumeGuid = @"{src}",
|
||||
DestVolumeGuid = @"{dst}",
|
||||
CreatedUtc = DateTimeOffset.Parse("2024-06-01T12:00:00Z")
|
||||
});
|
||||
Assert.True(id > 0);
|
||||
var loaded = Assert.Single(await store.SyncProfiles.ListAsync());
|
||||
Assert.Equal(id, loaded.Id);
|
||||
Assert.Equal("Photos", loaded.Name);
|
||||
Assert.Equal(@"D:\dst", loaded.DestPath);
|
||||
Assert.Equal(SyncMode.CopyUpdate, loaded.Mode);
|
||||
Assert.Equal("*.tmp", loaded.Excludes);
|
||||
Assert.True(loaded.AutoRun);
|
||||
Assert.Equal(@"{dst}", loaded.DestVolumeGuid);
|
||||
|
||||
loaded.Mode = SyncMode.Mirror;
|
||||
loaded.AutoRun = false;
|
||||
loaded.LastStatus = "Queued 1 copy";
|
||||
await store.SyncProfiles.UpsertAsync(loaded);
|
||||
var updated = await store.SyncProfiles.GetAsync(id);
|
||||
Assert.Equal(SyncMode.Mirror, updated!.Mode);
|
||||
Assert.False(updated.AutoRun);
|
||||
Assert.Equal("Queued 1 copy", updated.LastStatus);
|
||||
|
||||
await store.SyncProfiles.DeleteAsync(id);
|
||||
Assert.Empty(await store.SyncProfiles.ListAsync());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Operation_profile_roundtrip()
|
||||
{
|
||||
await using var store = await Stores.Open();
|
||||
var id = await store.OperationProfiles.UpsertAsync(new OperationProfile
|
||||
{
|
||||
Name = "Archive folder",
|
||||
SourcePath = @"C:\src",
|
||||
DestPath = @"D:\dst",
|
||||
RequireGitClean = true,
|
||||
DoCompress = true,
|
||||
ArchiveFormat = ArchiveFormat.SevenZip,
|
||||
DoCopy = false,
|
||||
DoRename = true,
|
||||
RenamePrefix = "x_",
|
||||
Excludes = ".git\nbin",
|
||||
AutoRun = false,
|
||||
SourceVolumeGuid = @"{src}",
|
||||
DestVolumeGuid = @"{dst}",
|
||||
IsBuiltIn = true,
|
||||
CreatedUtc = DateTimeOffset.Parse("2024-06-01T12:00:00Z")
|
||||
});
|
||||
Assert.True(id > 0);
|
||||
var loaded = Assert.Single(await store.OperationProfiles.ListAsync());
|
||||
Assert.Equal(id, loaded.Id);
|
||||
Assert.Equal("Archive folder", loaded.Name);
|
||||
Assert.True(loaded.RequireGitClean);
|
||||
Assert.True(loaded.DoCompress);
|
||||
Assert.Equal(ArchiveFormat.SevenZip, loaded.ArchiveFormat);
|
||||
Assert.True(loaded.DoRename);
|
||||
Assert.Equal("x_", loaded.RenamePrefix);
|
||||
Assert.Equal(".git\nbin", loaded.Excludes);
|
||||
Assert.True(loaded.IsBuiltIn);
|
||||
Assert.Equal(@"{dst}", loaded.DestVolumeGuid);
|
||||
|
||||
loaded.DoCopy = true;
|
||||
loaded.LastStatus = "Queued 1 compress";
|
||||
await store.OperationProfiles.UpsertAsync(loaded);
|
||||
var updated = await store.OperationProfiles.GetAsync(id);
|
||||
Assert.True(updated!.DoCopy);
|
||||
Assert.Equal("Queued 1 compress", updated.LastStatus);
|
||||
|
||||
await store.OperationProfiles.DeleteAsync(id);
|
||||
Assert.Empty(await store.OperationProfiles.ListAsync());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user