Files
CBDD/tests/CBDD.Tests/StorageEngineTransactionProtocolTests.cs
Joseph Doherty 3ffd468c79
All checks were successful
NuGet Publish / build-and-pack (push) Successful in 45s
NuGet Publish / publish-to-gitea (push) Successful in 52s
Fix audit findings for coverage, architecture checks, and XML docs
2026-02-20 15:43:25 -05:00

236 lines
7.0 KiB
C#

using ZB.MOM.WW.CBDD.Core.Storage;
using ZB.MOM.WW.CBDD.Core.Transactions;
namespace ZB.MOM.WW.CBDD.Tests;
public class StorageEngineTransactionProtocolTests
{
/// <summary>
/// Verifies preparing an unknown transaction returns false.
/// </summary>
[Fact]
public void PrepareTransaction_Should_ReturnFalse_For_Unknown_Transaction()
{
var dbPath = NewDbPath();
try
{
using var storage = new StorageEngine(dbPath, PageFileConfig.Default);
storage.PrepareTransaction(999_999).ShouldBeFalse();
}
finally
{
CleanupFiles(dbPath);
}
}
/// <summary>
/// Verifies committing a detached transaction object throws.
/// </summary>
[Fact]
public void CommitTransaction_With_TransactionObject_Should_Throw_When_Not_Active()
{
var dbPath = NewDbPath();
try
{
using var storage = new StorageEngine(dbPath, PageFileConfig.Default);
var detached = new Transaction(123, storage);
Should.Throw<InvalidOperationException>(() => storage.CommitTransaction(detached));
}
finally
{
CleanupFiles(dbPath);
}
}
/// <summary>
/// Verifies committing a transaction object persists writes and clears active state.
/// </summary>
[Fact]
public void CommitTransaction_With_TransactionObject_Should_Commit_Writes()
{
var dbPath = NewDbPath();
try
{
using var storage = new StorageEngine(dbPath, PageFileConfig.Default);
using var txn = storage.BeginTransaction();
var pageId = storage.AllocatePage();
var data = new byte[storage.PageSize];
data[0] = 0xAB;
storage.WritePage(pageId, txn.TransactionId, data);
storage.CommitTransaction(txn);
storage.ActiveTransactionCount.ShouldBe(0);
var readBuffer = new byte[storage.PageSize];
storage.ReadPage(pageId, 0, readBuffer);
readBuffer[0].ShouldBe((byte)0xAB);
}
finally
{
CleanupFiles(dbPath);
}
}
/// <summary>
/// Verifies committing by identifier with no writes does not throw.
/// </summary>
[Fact]
public void CommitTransaction_ById_With_NoWrites_Should_Not_Throw()
{
var dbPath = NewDbPath();
try
{
using var storage = new StorageEngine(dbPath, PageFileConfig.Default);
storage.CommitTransaction(424242);
}
finally
{
CleanupFiles(dbPath);
}
}
/// <summary>
/// Verifies committed transaction cache moves into readable state and active count is cleared.
/// </summary>
[Fact]
public void MarkTransactionCommitted_Should_Move_Cache_And_Clear_ActiveCount()
{
var dbPath = NewDbPath();
try
{
using var storage = new StorageEngine(dbPath, PageFileConfig.Default);
using var txn = storage.BeginTransaction();
var pageId = storage.AllocatePage();
var data = new byte[storage.PageSize];
data[5] = 0x5A;
storage.WritePage(pageId, txn.TransactionId, data);
storage.ActiveTransactionCount.ShouldBe(1);
storage.MarkTransactionCommitted(txn.TransactionId);
storage.ActiveTransactionCount.ShouldBe(0);
var readBuffer = new byte[storage.PageSize];
storage.ReadPage(pageId, 0, readBuffer);
readBuffer[5].ShouldBe((byte)0x5A);
}
finally
{
CleanupFiles(dbPath);
}
}
/// <summary>
/// Verifies rollback discards uncommitted page writes.
/// </summary>
[Fact]
public void RollbackTransaction_Should_Discard_Uncommitted_Write()
{
var dbPath = NewDbPath();
try
{
using var storage = new StorageEngine(dbPath, PageFileConfig.Default);
var pageId = storage.AllocatePage();
var baseline = new byte[storage.PageSize];
baseline[0] = 0x11;
storage.WritePageImmediate(pageId, baseline);
using var txn = storage.BeginTransaction();
var changed = new byte[storage.PageSize];
changed[0] = 0x99;
storage.WritePage(pageId, txn.TransactionId, changed);
storage.ActiveTransactionCount.ShouldBe(1);
storage.RollbackTransaction(txn.TransactionId);
storage.ActiveTransactionCount.ShouldBe(0);
var readBuffer = new byte[storage.PageSize];
storage.ReadPage(pageId, 0, readBuffer);
readBuffer[0].ShouldBe((byte)0x11);
}
finally
{
CleanupFiles(dbPath);
}
}
/// <summary>
/// Verifies marking a transaction committed transitions state correctly.
/// </summary>
[Fact]
public void Transaction_MarkCommitted_Should_Transition_State()
{
var dbPath = NewDbPath();
try
{
using var storage = new StorageEngine(dbPath, PageFileConfig.Default);
using var txn = storage.BeginTransaction();
var pageId = storage.AllocatePage();
var data = new byte[storage.PageSize];
data[3] = 0x33;
storage.WritePage(pageId, txn.TransactionId, data);
txn.MarkCommitted();
txn.State.ShouldBe(TransactionState.Committed);
storage.ActiveTransactionCount.ShouldBe(0);
var readBuffer = new byte[storage.PageSize];
storage.ReadPage(pageId, 0, readBuffer);
readBuffer[3].ShouldBe((byte)0x33);
}
finally
{
CleanupFiles(dbPath);
}
}
/// <summary>
/// Verifies preparing then committing writes WAL data and updates transaction state.
/// </summary>
[Fact]
public void Transaction_Prepare_Should_Write_Wal_And_Transition_State()
{
var dbPath = NewDbPath();
try
{
using var storage = new StorageEngine(dbPath, PageFileConfig.Default);
using var txn = storage.BeginTransaction();
var pageId = storage.AllocatePage();
var data = new byte[storage.PageSize];
data[11] = 0x7B;
storage.WritePage(pageId, txn.TransactionId, data);
txn.Prepare().ShouldBeTrue();
txn.State.ShouldBe(TransactionState.Preparing);
txn.Commit();
txn.State.ShouldBe(TransactionState.Committed);
}
finally
{
CleanupFiles(dbPath);
}
}
private static string NewDbPath()
=> Path.Combine(Path.GetTempPath(), $"storage_txn_{Guid.NewGuid():N}.db");
private static void CleanupFiles(string dbPath)
{
if (File.Exists(dbPath)) File.Delete(dbPath);
var walPath = Path.ChangeExtension(dbPath, ".wal");
if (File.Exists(walPath)) File.Delete(walPath);
var altWalPath = dbPath + "-wal";
if (File.Exists(altWalPath)) File.Delete(altWalPath);
}
}