Files
EonaCat 2909500bb8
EonaCat/ci-workflows: SonarQube / sonar (push) Successful in 22s
Updated
2026-10-08 21:33:54 +02:00

148 lines
6.2 KiB
C#

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);
}
}