Skip to content

Commit 9013c53

Browse files
Add comprehensive ArchiveCommandHandler tests
1 parent c61850d commit 9013c53

File tree

2 files changed

+413
-0
lines changed

2 files changed

+413
-0
lines changed
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
using Arius.Core.Features.Commands.Archive;
2+
using Arius.Core.Shared.FileSystem;
3+
using Arius.Core.Shared.Hashing;
4+
using Arius.Core.Shared.StateRepositories;
5+
using Arius.Core.Shared.Storage;
6+
using Arius.Core.Tests.Helpers.Builders;
7+
using Arius.Core.Tests.Helpers.Fakes;
8+
using Arius.Core.Tests.Helpers.Fixtures;
9+
using Arius.Core.Tests.Helpers.FakeLogger;
10+
using Microsoft.Extensions.Logging.Abstractions;
11+
using Microsoft.Extensions.Logging.Testing;
12+
using Shouldly;
13+
using Zio;
14+
using System.Linq;
15+
16+
namespace Arius.Core.Tests.Features.Commands.Archive;
17+
18+
public class ArchiveCommandHandlerHandleTests : IClassFixture<FixtureWithFileSystem>
19+
{
20+
private readonly FixtureWithFileSystem fixture;
21+
private readonly FakeLogger<ArchiveCommandHandler> logger;
22+
private readonly ArchiveCommandHandler handler;
23+
24+
public ArchiveCommandHandlerHandleTests(FixtureWithFileSystem fixture)
25+
{
26+
this.fixture = fixture;
27+
logger = new FakeLogger<ArchiveCommandHandler>();
28+
handler = new ArchiveCommandHandler(logger, NullLoggerFactory.Instance, fixture.AriusConfiguration);
29+
}
30+
31+
[Fact]
32+
public async Task Handle_WithSmallAndLargeFiles_ShouldUploadChunksAndState()
33+
{
34+
// Arrange
35+
var smallFile = new FakeFileBuilder(fixture)
36+
.WithActualFile(FilePairType.BinaryFileOnly, UPath.Root / "small.txt")
37+
.WithRandomContent(512, seed: 1)
38+
.Build();
39+
40+
var largeFile = new FakeFileBuilder(fixture)
41+
.WithActualFile(FilePairType.BinaryFileOnly, UPath.Root / "large.bin")
42+
.WithRandomContent(4096, seed: 2)
43+
.Build();
44+
45+
var progressUpdates = new List<ProgressUpdate>();
46+
var progressReporter = new Progress<ProgressUpdate>(progressUpdates.Add);
47+
48+
var command = new ArchiveCommandBuilder(fixture)
49+
.WithProgressReporter(progressReporter)
50+
.WithHashingParallelism(1)
51+
.WithUploadParallelism(1)
52+
.WithSmallFileBoundary(1024)
53+
.Build();
54+
55+
var archiveStorage = new FakeArchiveStorage();
56+
var loggerFactory = new FakeLoggerFactory();
57+
58+
var handlerContext = await new HandlerContextBuilder(command, loggerFactory)
59+
.WithArchiveStorage(archiveStorage)
60+
.BuildAsync();
61+
62+
var expectedInitialFileCount = handlerContext.FileSystem
63+
.EnumerateFileEntries(UPath.Root, "*", SearchOption.AllDirectories)
64+
.Count();
65+
66+
// Act
67+
var result = await handler.Handle(handlerContext, CancellationToken.None);
68+
69+
// Assert
70+
result.IsSuccess.ShouldBeTrue();
71+
var summary = result.Value;
72+
73+
summary.TotalLocalFiles.ShouldBe(expectedInitialFileCount);
74+
summary.UniqueBinariesUploaded.ShouldBe(2);
75+
summary.UniqueChunksUploaded.ShouldBe(2);
76+
summary.PointerFilesCreated.ShouldBe(2);
77+
summary.PointerFileEntriesDeleted.ShouldBe(0);
78+
summary.ExistingPointerFiles.ShouldBe(0);
79+
summary.BytesUploadedUncompressed.ShouldBe(smallFile.OriginalContent.Length + largeFile.OriginalContent.Length);
80+
summary.NewStateName.ShouldNotBeNull();
81+
82+
archiveStorage.StoredChunks.Count.ShouldBe(2);
83+
archiveStorage.UploadedStates.ShouldContain(summary.NewStateName!);
84+
85+
var tarChunk = archiveStorage.StoredChunks.Values.Single(c => c.ContentType == "application/aes256cbc+tar+gzip");
86+
tarChunk.Metadata.ShouldContainKey("OriginalContentLength");
87+
tarChunk.Metadata.ShouldContainKey("SmallChunkCount");
88+
tarChunk.Metadata["SmallChunkCount"].ShouldBe("1");
89+
90+
var largeChunk = archiveStorage.StoredChunks.Values.Single(c => c.ContentType == "application/aes256cbc+gzip");
91+
largeChunk.Metadata.ShouldContainKey("OriginalContentLength");
92+
largeChunk.Metadata["OriginalContentLength"].ShouldBe(largeFile.OriginalContent.Length.ToString());
93+
94+
var smallPointerPath = Path.Combine(fixture.TestRunSourceFolder.FullName, "small.txt.pointer.arius");
95+
File.Exists(smallPointerPath).ShouldBeTrue();
96+
97+
var largePointerPath = Path.Combine(fixture.TestRunSourceFolder.FullName, "large.bin.pointer.arius");
98+
File.Exists(largePointerPath).ShouldBeTrue();
99+
100+
handlerContext.StateRepository.GetPointerFileEntry("/small.txt.pointer.arius", includeBinaryProperties: true)
101+
.ShouldNotBeNull();
102+
handlerContext.StateRepository.GetPointerFileEntry("/large.bin.pointer.arius", includeBinaryProperties: true)
103+
.ShouldNotBeNull();
104+
}
105+
106+
[Fact]
107+
public async Task Handle_WithDuplicateLargeFiles_ShouldUploadBinaryOnceAndCreateMultiplePointers()
108+
{
109+
// Arrange
110+
var originalLargeFile = new FakeFileBuilder(fixture)
111+
.WithActualFile(FilePairType.BinaryFileOnly, UPath.Root / "shared.bin")
112+
.WithRandomContent(4096, seed: 42)
113+
.Build();
114+
115+
_ = new FakeFileBuilder(fixture)
116+
.WithDuplicate(originalLargeFile, UPath.Root / "duplicates" / "shared-copy.bin")
117+
.Build();
118+
119+
var command = new ArchiveCommandBuilder(fixture)
120+
.WithHashingParallelism(1)
121+
.WithUploadParallelism(1)
122+
.WithSmallFileBoundary(1024)
123+
.Build();
124+
125+
var archiveStorage = new FakeArchiveStorage();
126+
var loggerFactory = new FakeLoggerFactory();
127+
128+
var handlerContext = await new HandlerContextBuilder(command, loggerFactory)
129+
.WithArchiveStorage(archiveStorage)
130+
.BuildAsync();
131+
132+
var expectedInitialFileCount = handlerContext.FileSystem
133+
.EnumerateFileEntries(UPath.Root, "*", SearchOption.AllDirectories)
134+
.Count();
135+
136+
// Act
137+
var result = await handler.Handle(handlerContext, CancellationToken.None);
138+
139+
// Assert
140+
result.IsSuccess.ShouldBeTrue();
141+
var summary = result.Value;
142+
143+
summary.TotalLocalFiles.ShouldBe(expectedInitialFileCount);
144+
summary.UniqueBinariesUploaded.ShouldBe(1);
145+
summary.UniqueChunksUploaded.ShouldBe(1);
146+
summary.PointerFilesCreated.ShouldBe(2);
147+
summary.BytesUploadedUncompressed.ShouldBe(originalLargeFile.OriginalContent.Length);
148+
149+
archiveStorage.StoredChunks.Count.ShouldBe(1);
150+
var chunk = archiveStorage.StoredChunks.Values.Single();
151+
chunk.Metadata.ShouldContainKey("OriginalContentLength");
152+
chunk.Metadata["OriginalContentLength"].ShouldBe(originalLargeFile.OriginalContent.Length.ToString());
153+
154+
File.Exists(Path.Combine(fixture.TestRunSourceFolder.FullName, "shared.bin.pointer.arius")).ShouldBeTrue();
155+
File.Exists(Path.Combine(fixture.TestRunSourceFolder.FullName, "duplicates", "shared-copy.bin.pointer.arius")).ShouldBeTrue();
156+
157+
handlerContext.StateRepository.GetPointerFileEntry("/shared.bin.pointer.arius", includeBinaryProperties: true)
158+
.ShouldNotBeNull();
159+
handlerContext.StateRepository.GetPointerFileEntry("/duplicates/shared-copy.bin.pointer.arius", includeBinaryProperties: true)
160+
.ShouldNotBeNull();
161+
}
162+
163+
[Fact]
164+
public async Task Handle_WhenStateRepositoryContainsStalePointer_ShouldRemoveIt()
165+
{
166+
// Arrange
167+
_ = new FakeFileBuilder(fixture)
168+
.WithActualFile(FilePairType.BinaryFileOnly, UPath.Root / "active.txt")
169+
.WithRandomContent(256, seed: 7)
170+
.Build();
171+
172+
var command = new ArchiveCommandBuilder(fixture)
173+
.WithHashingParallelism(1)
174+
.WithUploadParallelism(1)
175+
.WithSmallFileBoundary(1024)
176+
.Build();
177+
178+
var archiveStorage = new FakeArchiveStorage();
179+
var loggerFactory = new FakeLoggerFactory();
180+
181+
var handlerContext = await new HandlerContextBuilder(command, loggerFactory)
182+
.WithArchiveStorage(archiveStorage)
183+
.BuildAsync();
184+
185+
var expectedInitialFileCount = handlerContext.FileSystem
186+
.EnumerateFileEntries(UPath.Root, "*", SearchOption.AllDirectories)
187+
.Count();
188+
189+
var staleHash = FakeHashBuilder.GenerateValidHash(99);
190+
handlerContext.StateRepository.AddBinaryProperties(new BinaryProperties
191+
{
192+
Hash = staleHash,
193+
OriginalSize = 1,
194+
ArchivedSize = 1,
195+
StorageTier = StorageTier.Cool
196+
});
197+
handlerContext.StateRepository.UpsertPointerFileEntries(new PointerFileEntry
198+
{
199+
Hash = staleHash,
200+
RelativeName = "/stale.bin.pointer.arius",
201+
CreationTimeUtc = DateTime.UtcNow,
202+
LastWriteTimeUtc = DateTime.UtcNow
203+
});
204+
205+
// Act
206+
var result = await handler.Handle(handlerContext, CancellationToken.None);
207+
208+
// Assert
209+
result.IsSuccess.ShouldBeTrue();
210+
var summary = result.Value;
211+
212+
summary.TotalLocalFiles.ShouldBe(expectedInitialFileCount);
213+
summary.PointerFileEntriesDeleted.ShouldBe(1);
214+
handlerContext.StateRepository.GetPointerFileEntry("/stale.bin.pointer.arius")
215+
.ShouldBeNull();
216+
217+
archiveStorage.StoredChunks.Count.ShouldBe(1);
218+
archiveStorage.UploadedStates.ShouldNotBeEmpty();
219+
220+
File.Exists(Path.Combine(fixture.TestRunSourceFolder.FullName, "active.txt.pointer.arius")).ShouldBeTrue();
221+
}
222+
}

0 commit comments

Comments
 (0)