Harden Surreal migration with retry/coverage fixes and XML docs cleanup
All checks were successful
NuGet Package Publish / nuget (push) Successful in 1m17s

This commit is contained in:
Joseph Doherty
2026-02-22 05:39:00 -05:00
parent 9c2a77dc3c
commit bd10914828
27 changed files with 1402 additions and 19 deletions

View File

@@ -428,12 +428,12 @@ public class ClusterCrudSyncE2ETests
&& replicated.Address?.City == payload.Address?.City;
}, 60, "Node B did not converge after crash-window recovery.", () => BuildDiagnostics(recoveredNodeA, nodeB));
await AssertEventuallyAsync(
() => recoveredNodeA.GetOplogCountForKey("Users", userId) == 1 &&
nodeB.GetOplogCountForKey("Users", userId) == 1,
60,
"Crash-window recovery created duplicate oplog entries.",
() => BuildDiagnostics(recoveredNodeA, nodeB));
await AssertEventuallyAsync(
() => recoveredNodeA.GetOplogCountForKey("Users", userId) == 1 &&
nodeB.GetOplogCountForKey("Users", userId) == 1,
60,
"Crash-window recovery created duplicate oplog entries.",
() => BuildDiagnostics(recoveredNodeA, nodeB));
}
}
finally
@@ -569,6 +569,9 @@ public class ClusterCrudSyncE2ETests
/// <param name="tcpPort">The TCP port used by the node listener.</param>
/// <param name="authToken">The cluster authentication token.</param>
/// <param name="knownPeers">The known peers this node can connect to.</param>
/// <param name="workDirOverride">An optional working directory override for test artifacts.</param>
/// <param name="preserveWorkDirOnDispose">A value indicating whether to preserve the working directory on dispose.</param>
/// <param name="useFaultInjectedCheckpointStore">A value indicating whether to inject a checkpoint persistence that fails once.</param>
/// <returns>A configured <see cref="TestPeerNode" /> instance.</returns>
public static TestPeerNode Create(
string nodeId,
@@ -690,6 +693,12 @@ public class ClusterCrudSyncE2ETests
return Context.Users.Find(u => u.Id == userId).FirstOrDefault();
}
/// <summary>
/// Gets the local oplog entry count for a collection/key pair produced by this node.
/// </summary>
/// <param name="collection">The collection name.</param>
/// <param name="key">The document key.</param>
/// <returns>The number of local oplog entries matching the key.</returns>
public int GetLocalOplogCountForKey(string collection, string key)
{
return Context.OplogEntries.FindAll()
@@ -699,6 +708,12 @@ public class ClusterCrudSyncE2ETests
string.Equals(e.TimestampNodeId, _nodeId, StringComparison.Ordinal));
}
/// <summary>
/// Gets the total oplog entry count for a collection/key pair across nodes.
/// </summary>
/// <param name="collection">The collection name.</param>
/// <param name="key">The document key.</param>
/// <returns>The number of oplog entries matching the key.</returns>
public int GetOplogCountForKey(string collection, string key)
{
return Context.OplogEntries.FindAll()
@@ -824,6 +839,14 @@ public class ClusterCrudSyncE2ETests
private const string UsersCollection = "Users";
private const string TodoListsCollection = "TodoLists";
/// <summary>
/// Initializes a new instance of the <see cref="FaultInjectedSampleDocumentStore" /> class.
/// </summary>
/// <param name="context">The sample database context.</param>
/// <param name="configProvider">The peer node configuration provider.</param>
/// <param name="vectorClockService">The vector clock service.</param>
/// <param name="checkpointPersistence">The checkpoint persistence implementation.</param>
/// <param name="logger">The optional logger instance.</param>
public FaultInjectedSampleDocumentStore(
SampleDbContext context,
IPeerNodeConfigurationProvider configProvider,
@@ -849,6 +872,7 @@ public class ClusterCrudSyncE2ETests
WatchCollection(TodoListsCollection, context.TodoLists, t => t.Id);
}
/// <inheritdoc />
protected override async Task ApplyContentToEntityAsync(
string collection,
string key,
@@ -858,6 +882,7 @@ public class ClusterCrudSyncE2ETests
await UpsertEntityAsync(collection, key, content, cancellationToken);
}
/// <inheritdoc />
protected override async Task ApplyContentToEntitiesBatchAsync(
IEnumerable<(string Collection, string Key, JsonElement Content)> documents,
CancellationToken cancellationToken)
@@ -866,6 +891,7 @@ public class ClusterCrudSyncE2ETests
await UpsertEntityAsync(collection, key, content, cancellationToken);
}
/// <inheritdoc />
protected override async Task<JsonElement?> GetEntityAsJsonAsync(
string collection,
string key,
@@ -879,6 +905,7 @@ public class ClusterCrudSyncE2ETests
};
}
/// <inheritdoc />
protected override async Task RemoveEntityAsync(
string collection,
string key,
@@ -887,6 +914,7 @@ public class ClusterCrudSyncE2ETests
await DeleteEntityAsync(collection, key, cancellationToken);
}
/// <inheritdoc />
protected override async Task RemoveEntitiesBatchAsync(
IEnumerable<(string Collection, string Key)> documents,
CancellationToken cancellationToken)
@@ -895,6 +923,7 @@ public class ClusterCrudSyncE2ETests
await DeleteEntityAsync(collection, key, cancellationToken);
}
/// <inheritdoc />
protected override async Task<IEnumerable<(string Key, JsonElement Content)>> GetAllEntitiesAsJsonAsync(
string collection,
CancellationToken cancellationToken)
@@ -967,6 +996,7 @@ public class ClusterCrudSyncE2ETests
{
private int _failOnNextAdvance = 1;
/// <inheritdoc />
public Task<SurrealCdcCheckpoint?> GetCheckpointAsync(
string? consumerId = null,
CancellationToken cancellationToken = default)
@@ -974,6 +1004,7 @@ public class ClusterCrudSyncE2ETests
return Task.FromResult<SurrealCdcCheckpoint?>(null);
}
/// <inheritdoc />
public Task UpsertCheckpointAsync(
HlcTimestamp timestamp,
string lastHash,
@@ -984,6 +1015,7 @@ public class ClusterCrudSyncE2ETests
return Task.CompletedTask;
}
/// <inheritdoc />
public Task AdvanceCheckpointAsync(
OplogEntry entry,
string? consumerId = null,