Harden Surreal migration with retry/coverage fixes and XML docs cleanup
All checks were successful
NuGet Package Publish / nuget (push) Successful in 1m17s
All checks were successful
NuGet Package Publish / nuget (push) Successful in 1m17s
This commit is contained in:
@@ -13,6 +13,9 @@ namespace ZB.MOM.WW.CBDDC.Sample.Console.Tests;
|
||||
[Collection("SurrealCdcDurability")]
|
||||
public class SurrealCdcDurabilityTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Verifies checkpoints persist latest local changes per consumer across restarts.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task CheckpointPersistence_ShouldTrackLatestLocalChange_AndPersistPerConsumer()
|
||||
{
|
||||
@@ -85,6 +88,9 @@ public class SurrealCdcDurabilityTests
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies recovery resumes from a persisted checkpoint and advances after catch-up.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task RestartRecovery_ShouldResumeCatchUpFromPersistedCheckpoint_InRocksDb()
|
||||
{
|
||||
@@ -153,6 +159,9 @@ public class SurrealCdcDurabilityTests
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies duplicate remote apply windows are idempotent without loopback entries.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task RemoteApply_ShouldBeIdempotentAcrossDuplicateWindow_WithoutLoopbackEntries()
|
||||
{
|
||||
@@ -207,6 +216,9 @@ public class SurrealCdcDurabilityTests
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies local deletes persist tombstone metadata and advance checkpoints.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task LocalDelete_ShouldPersistTombstoneMetadata_AndAdvanceCheckpoint()
|
||||
{
|
||||
@@ -358,21 +370,46 @@ internal sealed class CdcTestHarness : IAsyncDisposable
|
||||
NullLogger<SurrealDocumentMetadataStore>.Instance);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sample database context.
|
||||
/// </summary>
|
||||
public SampleDbContext Context { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the checkpointed sample document store.
|
||||
/// </summary>
|
||||
public CheckpointedSampleDocumentStore DocumentStore { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the oplog store used by the harness.
|
||||
/// </summary>
|
||||
public SurrealOplogStore OplogStore { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the document metadata store.
|
||||
/// </summary>
|
||||
public SurrealDocumentMetadataStore MetadataStore { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets checkpoint persistence used for CDC progress tracking.
|
||||
/// </summary>
|
||||
public ISurrealCdcCheckpointPersistence CheckpointPersistence { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Polls CDC once through the document store.
|
||||
/// </summary>
|
||||
public async Task PollAsync()
|
||||
{
|
||||
await DocumentStore.PollCdcOnceAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a harness instance with retries for transient RocksDB lock contention.
|
||||
/// </summary>
|
||||
/// <param name="databasePath">Database directory path.</param>
|
||||
/// <param name="nodeId">Node identifier.</param>
|
||||
/// <param name="consumerId">CDC consumer identifier.</param>
|
||||
/// <returns>Initialized test harness.</returns>
|
||||
public static async Task<CdcTestHarness> OpenWithRetriesAsync(
|
||||
string databasePath,
|
||||
string nodeId,
|
||||
@@ -391,6 +428,11 @@ internal sealed class CdcTestHarness : IAsyncDisposable
|
||||
throw new InvalidOperationException("Unable to acquire RocksDB lock for test harness.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets oplog entries for a collection ordered by timestamp.
|
||||
/// </summary>
|
||||
/// <param name="collection">Collection name.</param>
|
||||
/// <returns>Ordered oplog entries.</returns>
|
||||
public async Task<List<OplogEntry>> GetEntriesByCollectionAsync(string collection)
|
||||
{
|
||||
return (await OplogStore.ExportAsync())
|
||||
@@ -400,6 +442,12 @@ internal sealed class CdcTestHarness : IAsyncDisposable
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets oplog entries for a collection key ordered by timestamp.
|
||||
/// </summary>
|
||||
/// <param name="collection">Collection name.</param>
|
||||
/// <param name="key">Document key.</param>
|
||||
/// <returns>Ordered oplog entries.</returns>
|
||||
public async Task<List<OplogEntry>> GetEntriesByKeyAsync(string collection, string key)
|
||||
{
|
||||
return (await OplogStore.ExportAsync())
|
||||
@@ -410,6 +458,7 @@ internal sealed class CdcTestHarness : IAsyncDisposable
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
DocumentStore.Dispose();
|
||||
@@ -428,6 +477,15 @@ internal sealed class CheckpointedSampleDocumentStore : SurrealDocumentStore<Sam
|
||||
private const string UsersCollection = "Users";
|
||||
private const string TodoListsCollection = "TodoLists";
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CheckpointedSampleDocumentStore"/> class.
|
||||
/// </summary>
|
||||
/// <param name="context">Sample database context.</param>
|
||||
/// <param name="configProvider">Peer configuration provider.</param>
|
||||
/// <param name="vectorClockService">Vector clock service.</param>
|
||||
/// <param name="checkpointPersistence">Checkpoint persistence implementation.</param>
|
||||
/// <param name="surrealOptions">Optional Surreal embedded options.</param>
|
||||
/// <param name="logger">Optional logger.</param>
|
||||
public CheckpointedSampleDocumentStore(
|
||||
SampleDbContext context,
|
||||
IPeerNodeConfigurationProvider configProvider,
|
||||
@@ -450,6 +508,7 @@ internal sealed class CheckpointedSampleDocumentStore : SurrealDocumentStore<Sam
|
||||
WatchCollection(TodoListsCollection, context.TodoLists, t => t.Id, subscribeForInMemoryEvents: false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task ApplyContentToEntityAsync(
|
||||
string collection,
|
||||
string key,
|
||||
@@ -459,6 +518,7 @@ internal sealed class CheckpointedSampleDocumentStore : SurrealDocumentStore<Sam
|
||||
await UpsertEntityAsync(collection, key, content, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task ApplyContentToEntitiesBatchAsync(
|
||||
IEnumerable<(string Collection, string Key, JsonElement Content)> documents,
|
||||
CancellationToken cancellationToken)
|
||||
@@ -467,6 +527,7 @@ internal sealed class CheckpointedSampleDocumentStore : SurrealDocumentStore<Sam
|
||||
await UpsertEntityAsync(collection, key, content, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task<JsonElement?> GetEntityAsJsonAsync(
|
||||
string collection,
|
||||
string key,
|
||||
@@ -480,6 +541,7 @@ internal sealed class CheckpointedSampleDocumentStore : SurrealDocumentStore<Sam
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task RemoveEntityAsync(
|
||||
string collection,
|
||||
string key,
|
||||
@@ -488,6 +550,7 @@ internal sealed class CheckpointedSampleDocumentStore : SurrealDocumentStore<Sam
|
||||
await DeleteEntityAsync(collection, key, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task RemoveEntitiesBatchAsync(
|
||||
IEnumerable<(string Collection, string Key)> documents,
|
||||
CancellationToken cancellationToken)
|
||||
@@ -496,6 +559,7 @@ internal sealed class CheckpointedSampleDocumentStore : SurrealDocumentStore<Sam
|
||||
await DeleteEntityAsync(collection, key, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override async Task<IEnumerable<(string Key, JsonElement Content)>> GetAllEntitiesAsJsonAsync(
|
||||
string collection,
|
||||
CancellationToken cancellationToken)
|
||||
|
||||
Reference in New Issue
Block a user