This commit is contained in:
1 parent
4081b5949d
commit
2909500bb8
14 files changed
+342
-114
No files matched your search
@@ -0,0 +1,15 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||
<PackageReference Include="xunit" Version="2.9.3" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" PrivateAssets="all" />
|
||||
<ProjectReference Include="..\..\EonaCat.LogStack\EonaCat.LogStack.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -0,0 +1,148 @@
|
||||
using EonaCat.LogStack.Core;
|
||||
using EonaCat.LogStack.Flows;
|
||||
using Xunit;
|
||||
|
||||
namespace EonaCat.LogStack.FileDeletionTests;
|
||||
|
||||
public sealed class FileDeletionTests : IDisposable
|
||||
{
|
||||
private readonly string _directory = Path.Combine(Path.GetTempPath(), "EonaCat.FileDeletionTests", Guid.NewGuid().ToString("N"));
|
||||
|
||||
public FileDeletionTests()
|
||||
{
|
||||
Directory.CreateDirectory(_directory);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EncryptedLogCanBeDeletedAndRecreatedWithAValidNewHeader()
|
||||
{
|
||||
await using var flow = new EncryptedFileFlow(_directory, "test-password", template: "{message}").WithDurableWrites();
|
||||
await WriteEncryptedAsync(flow, "before-deletion");
|
||||
string path = Assert.Single(Directory.GetFiles(_directory, "*.eona"));
|
||||
byte[] originalHeader = ReadHeader(path);
|
||||
|
||||
Assert.Throws<IOException>(() => File.Open(path, FileMode.Append, FileAccess.Write, FileShare.ReadWrite | FileShare.Delete));
|
||||
File.Delete(path);
|
||||
Assert.False(File.Exists(path));
|
||||
|
||||
await WriteEncryptedAsync(flow, "after-deletion");
|
||||
Assert.True(File.Exists(path));
|
||||
Assert.False(originalHeader.SequenceEqual(ReadHeader(path)));
|
||||
string outputPath = Path.Combine(_directory, "decrypted.txt");
|
||||
Assert.True(EncryptedFileFlow.DecryptToFile(path, outputPath, "test-password"));
|
||||
string contents = File.ReadAllText(outputPath);
|
||||
Assert.Contains("after-deletion", contents);
|
||||
Assert.DoesNotContain("before-deletion", contents);
|
||||
Assert.Equal(0, flow.GetTotalErrors());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EncryptedLogCanResumeAnExistingFile()
|
||||
{
|
||||
byte[] originalHeader;
|
||||
string path;
|
||||
await using (var first = new EncryptedFileFlow(_directory, "test-password", template: "{message}").WithDurableWrites())
|
||||
{
|
||||
await WriteEncryptedAsync(first, "first-session");
|
||||
path = Assert.Single(Directory.GetFiles(_directory, "*.eona"));
|
||||
originalHeader = ReadHeader(path);
|
||||
}
|
||||
|
||||
await using var second = new EncryptedFileFlow(_directory, "test-password", template: "{message}").WithDurableWrites();
|
||||
await WriteEncryptedAsync(second, "second-session");
|
||||
Assert.Equal(originalHeader, ReadHeader(path));
|
||||
string outputPath = Path.Combine(_directory, "decrypted.txt");
|
||||
Assert.True(EncryptedFileFlow.DecryptToFile(path, outputPath, "test-password"));
|
||||
string contents = File.ReadAllText(outputPath);
|
||||
Assert.Contains("first-session", contents);
|
||||
Assert.Contains("second-session", contents);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RotatingLogCanBeDeletedAndRecreated()
|
||||
{
|
||||
await using var flow = new RotatingFileFlow(_directory);
|
||||
Assert.Equal(WriteResult.Success, await flow.BlastAsync(CreateEvent("before-deletion")));
|
||||
string path = Assert.Single(Directory.GetFiles(_directory));
|
||||
File.Delete(path);
|
||||
Assert.False(File.Exists(path));
|
||||
|
||||
Assert.Equal(WriteResult.Success, await flow.BlastAsync(CreateEvent("after-deletion")));
|
||||
string contents = ReadSharedText(path);
|
||||
Assert.Contains("after-deletion", contents);
|
||||
Assert.DoesNotContain("before-deletion", contents);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AuditLogCanBeDeletedAndRecreatedWithAValidHashChain()
|
||||
{
|
||||
var flow = new AuditFlow(_directory);
|
||||
try
|
||||
{
|
||||
await flow.BlastAsync(CreateEvent("before-deletion"));
|
||||
Assert.Throws<IOException>(() => File.Open(flow.FilePath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite | FileShare.Delete));
|
||||
File.Delete(flow.FilePath);
|
||||
Assert.False(File.Exists(flow.FilePath));
|
||||
|
||||
await flow.BlastAsync(CreateEvent("after-deletion"));
|
||||
await flow.BlastAsync(CreateEvent("next-entry"));
|
||||
var liveVerification = AuditFlow.Verify(flow.FilePath);
|
||||
Assert.True(liveVerification.ok, liveVerification.reason);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await flow.DisposeAsync();
|
||||
}
|
||||
|
||||
var verification = AuditFlow.Verify(flow.FilePath);
|
||||
Assert.True(verification.ok, verification.reason);
|
||||
string contents = File.ReadAllText(flow.FilePath);
|
||||
Assert.Contains("after-deletion", contents);
|
||||
Assert.Contains("next-entry", contents);
|
||||
Assert.DoesNotContain("before-deletion", contents);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PlainLogCanBeDeletedWhileOpen()
|
||||
{
|
||||
await using var flow = new FileFlow(_directory);
|
||||
Assert.Equal(WriteResult.Success, await flow.BlastAsync(CreateEvent("before-deletion")));
|
||||
Assert.True(SpinWait.SpinUntil(() => flow.GetStats().BytesWritten > 0 || flow.GetTotalErrors() > 0, TimeSpan.FromSeconds(10)), "Plain writer did not complete.");
|
||||
Assert.Equal(0, flow.GetTotalErrors());
|
||||
await flow.FlushAsync();
|
||||
string path = Assert.Single(Directory.GetFiles(_directory, "*.log"));
|
||||
File.Delete(path);
|
||||
Assert.False(File.Exists(path));
|
||||
}
|
||||
|
||||
private static LogEvent CreateEvent(string message) => new LogEventBuilder().WithMessage(message).Build();
|
||||
|
||||
private static async Task WriteEncryptedAsync(EncryptedFileFlow flow, string message)
|
||||
{
|
||||
long previousBytes = flow.GetStats().BytesWritten;
|
||||
Assert.Equal(WriteResult.Success, await flow.BlastAsync(CreateEvent(message)));
|
||||
Assert.True(SpinWait.SpinUntil(() => flow.GetStats().BytesWritten > previousBytes || flow.GetTotalErrors() > 0, TimeSpan.FromSeconds(10)), "Encrypted writer did not complete.");
|
||||
Assert.Equal(0, flow.GetTotalErrors());
|
||||
await flow.FlushAsync();
|
||||
}
|
||||
|
||||
private static byte[] ReadHeader(string path)
|
||||
{
|
||||
using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete);
|
||||
byte[] header = new byte[52];
|
||||
stream.ReadExactly(header);
|
||||
return header;
|
||||
}
|
||||
|
||||
private static string ReadSharedText(string path)
|
||||
{
|
||||
using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete);
|
||||
using var reader = new StreamReader(stream);
|
||||
return reader.ReadToEnd();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Directory.Delete(_directory, recursive: true);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user