Files
scadaproj/ZB.MOM.WW.Secrets/tests/ZB.MOM.WW.Secrets.Replicator.SqlServer.Tests/Fakes/SqliteBackedHub.cs
T
Joseph Doherty 64efb49996 fix(secrets): hub push sweep isolates each row (scadaproj#45)
Secrets-008: SqlServerSecretSyncService.SweepAsync now catches a per-row push failure, logs it
(EventId 4, name only) and continues, like the pull side. One row the hub refuses (a >450-char
name, an over-long explicit KEK id) no longer stops every later name from reaching the hub.

Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
2026-11-09 15:32:28 -05:00

60 lines
2.3 KiB
C#

using ZB.MOM.WW.Secrets.Abstractions;
using ZB.MOM.WW.Secrets.Replicator.SqlServer;
using ZB.MOM.WW.Secrets.Sqlite;
namespace ZB.MOM.WW.Secrets.Replicator.SqlServer.Tests.Fakes;
/// <summary>
/// An <see cref="ISecretReplicationHub"/> backed by a real SQLite store, standing in for the shared
/// SQL-Server hub in offline convergence tests.
/// </summary>
/// <remarks>
/// Deliberately backed by a real store rather than a dictionary: the behaviour under test is
/// last-writer-wins convergence, which lives in <c>ApplyReplicatedAsync</c>. A hand-rolled fake would
/// be re-implementing the very logic the tests exist to check, and would happily agree with a broken
/// reconciler. The live SQL-Server tests then confirm the T-SQL store behaves the same way.
/// </remarks>
/// <param name="store">The SQLite store standing in for the hub database.</param>
public sealed class SqliteBackedHub(SqliteSecretStore store) : ISecretReplicationHub
{
/// <summary>Number of times the sweep asked this hub for rows — asserts the fetch was skipped.</summary>
public int GetManyCallCount { get; private set; }
/// <inheritdoc />
public Task<IReadOnlyList<SecretManifestEntry>> GetManifestAsync(CancellationToken ct) =>
store.GetManifestAsync(ct);
/// <inheritdoc />
public async Task<IReadOnlyList<StoredSecret>> GetManyAsync(
IReadOnlyList<SecretName> names, CancellationToken ct)
{
GetManyCallCount++;
var rows = new List<StoredSecret>(names.Count);
foreach (SecretName name in names)
{
StoredSecret? row = await store.GetAsync(name, ct);
if (row is not null)
{
rows.Add(row);
}
}
return rows;
}
/// <summary>
/// A row name the hub refuses on push, as SQL Server refuses a name longer than its
/// <c>NVARCHAR(450)</c> column ("String or binary data would be truncated").
/// </summary>
public string? RejectPushOf { get; set; }
/// <inheritdoc />
public Task ApplyReplicatedAsync(StoredSecret row, CancellationToken ct) =>
row.Name.Value == RejectPushOf
? throw new InvalidOperationException("String or binary data would be truncated.")
: store.ApplyReplicatedAsync(row, ct);
}