Resolve all high-severity architecture review findings
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
using Grpc.Core;
|
||||
using ZB.MOM.WW.ScadaBridge.Communication.Grpc;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.Communication.Tests.Grpc;
|
||||
|
||||
public class AuditWireBudgetTests
|
||||
{
|
||||
[Fact]
|
||||
public void OversizeSoftBudgetRow_TravelsAloneWithoutTruncation()
|
||||
{
|
||||
var batch = new AuditEventBatch();
|
||||
var bytes = AuditWireBudget.InitialBytes;
|
||||
var row = new AuditEventDto { EventId = "large", ErrorDetail = new string('x', GrpcMessageLimits.AuditBatchBytes + 1) };
|
||||
Assert.True(AuditWireBudget.TryAppend(batch.Events, row, ref bytes, row.EventId));
|
||||
Assert.False(AuditWireBudget.TryAppend(batch.Events, new AuditEventDto { EventId = "next" }, ref bytes, "next"));
|
||||
Assert.Same(row, Assert.Single(batch.Events));
|
||||
Assert.Equal(row.ErrorDetail, batch.Events[0].ErrorDetail);
|
||||
Assert.True(bytes < GrpcMessageLimits.MaxMessageBytes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HardOversizeRow_DoesNotBlockGoodPrefix_AndFailsExplicitlyWhenFirst()
|
||||
{
|
||||
var good = new AuditEventDto { EventId = "good", ErrorMessage = "small" };
|
||||
var huge = new AuditEventDto { EventId = "retained-id", ErrorDetail = new string('x', GrpcMessageLimits.MaxMessageBytes) };
|
||||
var batch = new AuditEventBatch();
|
||||
var bytes = AuditWireBudget.InitialBytes;
|
||||
Assert.True(AuditWireBudget.TryAppend(batch.Events, good, ref bytes, good.EventId));
|
||||
Assert.False(AuditWireBudget.TryAppend(batch.Events, huge, ref bytes, huge.EventId));
|
||||
Assert.Equal("good", Assert.Single(batch.Events).EventId);
|
||||
var next = new AuditEventBatch();
|
||||
bytes = AuditWireBudget.InitialBytes;
|
||||
var ex = Assert.Throws<RpcException>(() => AuditWireBudget.TryAppend(next.Events, huge, ref bytes, huge.EventId));
|
||||
Assert.Equal(StatusCode.ResourceExhausted, ex.StatusCode);
|
||||
Assert.Contains("retained-id", ex.Status.Detail);
|
||||
Assert.Contains("retained locally", ex.Status.Detail);
|
||||
Assert.Empty(next.Events);
|
||||
}
|
||||
}
|
||||
+55
@@ -19,6 +19,61 @@ public class SitePairChannelProviderDisposalTests
|
||||
Options.Create(new CommunicationOptions()),
|
||||
NullLogger<SitePairChannelProvider>.Instance);
|
||||
|
||||
[Fact]
|
||||
public async Task ApplyThenUnavailable_DoesNotReplayAnAmbiguousOperation()
|
||||
{
|
||||
using var provider = Build();
|
||||
provider.UpdateSite("site-1", "http://localhost:15101", "http://localhost:15102");
|
||||
var effects = 0;
|
||||
var error = await Record.ExceptionAsync(() => provider.ExecuteAsync("site-1", (channel, ct) =>
|
||||
{
|
||||
effects++;
|
||||
if (effects == 1)
|
||||
throw new global::Grpc.Core.RpcException(new global::Grpc.Core.Status(global::Grpc.Core.StatusCode.Unavailable, "response connection lost after apply"));
|
||||
return Task.FromResult(42);
|
||||
}, CancellationToken.None));
|
||||
Assert.Equal(1, effects);
|
||||
Assert.NotNull(error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SecondaryAppliesThenLosesResponse_AlsoReportsUnknownWithoutAnotherReplay()
|
||||
{
|
||||
using var provider = Build();
|
||||
provider.UpdateSite("site-1", "http://localhost:15101", "http://localhost:15102");
|
||||
var attempts = 0;
|
||||
var effects = 0;
|
||||
var error = await Assert.ThrowsAsync<global::Grpc.Core.RpcException>(() => provider.ExecuteAsync<int>("site-1", (channel, ct) =>
|
||||
{
|
||||
if (++attempts == 1) throw GrpcRetrySafety.NotDispatched("starting");
|
||||
effects++;
|
||||
throw new global::Grpc.Core.RpcException(new global::Grpc.Core.Status(global::Grpc.Core.StatusCode.Unavailable, "applied, reply lost"));
|
||||
}, CancellationToken.None));
|
||||
Assert.Equal(global::Grpc.Core.StatusCode.Aborted, error.StatusCode);
|
||||
Assert.Contains("OUTCOME_UNKNOWN", error.Status.Detail);
|
||||
Assert.Equal(2, attempts);
|
||||
Assert.Equal(1, effects);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
public async Task SafeReadOrExplicitPreDispatchRejection_CanRetry(bool safeRead)
|
||||
{
|
||||
using var provider = Build();
|
||||
provider.UpdateSite("site-1", "http://localhost:15101", "http://localhost:15102");
|
||||
var attempts = 0;
|
||||
var result = await provider.ExecuteAsync("site-1", (channel, ct) =>
|
||||
{
|
||||
if (++attempts == 1)
|
||||
throw safeRead ? new global::Grpc.Core.RpcException(new global::Grpc.Core.Status(global::Grpc.Core.StatusCode.Unavailable, "lost reply"))
|
||||
: GrpcRetrySafety.NotDispatched("not ready");
|
||||
return Task.FromResult(42);
|
||||
}, CancellationToken.None, retrySafe: safeRead);
|
||||
Assert.Equal(42, result);
|
||||
Assert.Equal(2, attempts);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateSite_WhileCallInFlight_ParksTheOldChannelAndLetsTheCallComplete()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user