Resolve all 46 low-severity architecture review findings
This commit is contained in:
+58
@@ -19,6 +19,64 @@ public class SitePairChannelProviderDisposalTests
|
||||
Options.Create(new CommunicationOptions()),
|
||||
NullLogger<SitePairChannelProvider>.Instance);
|
||||
|
||||
[Fact]
|
||||
public async Task Shutdown_RetainsActiveChannelThenDisposesItAtCompletion()
|
||||
{
|
||||
using var provider = Build();
|
||||
provider.UpdateSite("site-1", "http://localhost:15101", null);
|
||||
var entered = new TaskCompletionSource<GrpcChannel>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
var release = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
var call = provider.ExecuteAsync("site-1", async (channel, ct) =>
|
||||
{
|
||||
entered.SetResult(channel);
|
||||
await release.Task;
|
||||
_ = channel.CreateCallInvoker();
|
||||
return 42;
|
||||
}, CancellationToken.None);
|
||||
var channel = await entered.Task;
|
||||
try
|
||||
{
|
||||
provider.Dispose();
|
||||
Assert.Equal(1, provider.ParkedChannelCount);
|
||||
_ = channel.CreateCallInvoker();
|
||||
Assert.Throws<ObjectDisposedException>(() => provider.UpdateSite("site-2", "http://localhost:15102", null));
|
||||
}
|
||||
finally { release.TrySetResult(); }
|
||||
Assert.Equal(42, await call);
|
||||
Assert.Equal(0, provider.ParkedChannelCount);
|
||||
Assert.Throws<ObjectDisposedException>(() => channel.CreateCallInvoker());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RetiredChannel_SweepKeepsItAliveUntilActualCallCompletion()
|
||||
{
|
||||
using var provider = Build();
|
||||
provider.UpdateSite("site-1", "http://localhost:15101", null);
|
||||
var entered = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
var release = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
GrpcChannel? held = null;
|
||||
var call = provider.ExecuteAsync("site-1", async (channel, ct) =>
|
||||
{
|
||||
held = channel;
|
||||
entered.SetResult();
|
||||
await release.Task;
|
||||
return 42;
|
||||
}, CancellationToken.None);
|
||||
try
|
||||
{
|
||||
await entered.Task;
|
||||
provider.UpdateSite("site-1", "http://localhost:15102", null);
|
||||
// Simulates any elapsed grace, including waits longer than five minutes.
|
||||
provider.SweepGraveyard(force: true);
|
||||
Assert.Equal(1, provider.ParkedChannelCount);
|
||||
_ = held!.CreateCallInvoker();
|
||||
}
|
||||
finally { release.TrySetResult(); await call; }
|
||||
provider.SweepGraveyard(force: true);
|
||||
Assert.Equal(0, provider.ParkedChannelCount);
|
||||
Assert.Throws<ObjectDisposedException>(() => held!.CreateCallInvoker());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ApplyThenUnavailable_DoesNotReplayAnAmbiguousOperation()
|
||||
{
|
||||
|
||||
@@ -8,6 +8,15 @@ namespace ZB.MOM.WW.ScadaBridge.Communication.Tests.Grpc;
|
||||
|
||||
public class SiteStreamGrpcClientTests
|
||||
{
|
||||
[Fact]
|
||||
public void ConnectedHeaderTimeout_IsImmutablePerClient()
|
||||
{
|
||||
var property = typeof(SiteStreamGrpcClient).GetProperty("ConnectedHeaderTimeout",
|
||||
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Static)!;
|
||||
Assert.False(property.GetMethod!.IsStatic);
|
||||
Assert.Null(property.SetMethod);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConvertToDomainEvent_AttributeChanged_MapsCorrectly()
|
||||
{
|
||||
@@ -436,32 +445,23 @@ public class SiteStreamGrpcClientTests
|
||||
// The bug: a header TIMEOUT used to be reported as connected. An unreachable/wedged
|
||||
// site produces exactly that shape, so the aggregator cleared _streamDown, consumed
|
||||
// its pending re-seed and fanned a full snapshot out at a site that never answered.
|
||||
var previous = SiteStreamGrpcClient.ConnectedHeaderTimeout;
|
||||
SiteStreamGrpcClient.ConnectedHeaderTimeout = TimeSpan.FromMilliseconds(50);
|
||||
try
|
||||
{
|
||||
var client = SiteStreamGrpcClient.CreateForTesting();
|
||||
var cts = new CancellationTokenSource();
|
||||
var connected = 0;
|
||||
var neverArrives = new TaskCompletionSource<Metadata>();
|
||||
var client = SiteStreamGrpcClient.CreateForTesting(new CommunicationOptions { ConnectedHeaderTimeout = TimeSpan.FromMilliseconds(50) });
|
||||
var cts = new CancellationTokenSource();
|
||||
var connected = 0;
|
||||
var neverArrives = new TaskCompletionSource<Metadata>();
|
||||
|
||||
// No headers AND no events: the site is dead. The stream ends (status OK) with
|
||||
// no connected signal ever raised.
|
||||
await client.ConsumeStreamAsync(
|
||||
"corr-dead",
|
||||
cts,
|
||||
() => FakeCall(new StubStreamReader(), neverArrives.Task),
|
||||
_ => { },
|
||||
_ => { },
|
||||
() => { },
|
||||
() => connected++);
|
||||
// No headers AND no events: the site is dead. The stream ends (status OK) with
|
||||
// no connected signal ever raised.
|
||||
await client.ConsumeStreamAsync(
|
||||
"corr-dead",
|
||||
cts,
|
||||
() => FakeCall(new StubStreamReader(), neverArrives.Task),
|
||||
_ => { },
|
||||
_ => { },
|
||||
() => { },
|
||||
() => connected++);
|
||||
|
||||
Assert.Equal(0, connected);
|
||||
}
|
||||
finally
|
||||
{
|
||||
SiteStreamGrpcClient.ConnectedHeaderTimeout = previous;
|
||||
}
|
||||
Assert.Equal(0, connected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -470,42 +470,33 @@ public class SiteStreamGrpcClientTests
|
||||
// The case the timeout was originally added for — a peer that defers its headers
|
||||
// until the first message — is now covered by the event itself, which is real proof
|
||||
// of a live peer. Connected must precede the event's delivery and fire only once.
|
||||
var previous = SiteStreamGrpcClient.ConnectedHeaderTimeout;
|
||||
SiteStreamGrpcClient.ConnectedHeaderTimeout = TimeSpan.FromMilliseconds(50);
|
||||
try
|
||||
{
|
||||
var client = SiteStreamGrpcClient.CreateForTesting();
|
||||
var cts = new CancellationTokenSource();
|
||||
var connected = 0;
|
||||
var connectedBeforeFirstEvent = false;
|
||||
var events = 0;
|
||||
var neverArrives = new TaskCompletionSource<Metadata>();
|
||||
var client = SiteStreamGrpcClient.CreateForTesting(new CommunicationOptions { ConnectedHeaderTimeout = TimeSpan.FromMilliseconds(50) });
|
||||
var cts = new CancellationTokenSource();
|
||||
var connected = 0;
|
||||
var connectedBeforeFirstEvent = false;
|
||||
var events = 0;
|
||||
var neverArrives = new TaskCompletionSource<Metadata>();
|
||||
|
||||
await client.ConsumeStreamAsync(
|
||||
"corr-late-headers",
|
||||
cts,
|
||||
() => FakeCall(
|
||||
new StubStreamReader(
|
||||
new SiteStreamEvent { CorrelationId = "corr-late-headers" },
|
||||
new SiteStreamEvent { CorrelationId = "corr-late-headers" }),
|
||||
neverArrives.Task),
|
||||
_ =>
|
||||
{
|
||||
if (events == 0) connectedBeforeFirstEvent = connected == 1;
|
||||
events++;
|
||||
},
|
||||
_ => { },
|
||||
() => { },
|
||||
() => connected++);
|
||||
await client.ConsumeStreamAsync(
|
||||
"corr-late-headers",
|
||||
cts,
|
||||
() => FakeCall(
|
||||
new StubStreamReader(
|
||||
new SiteStreamEvent { CorrelationId = "corr-late-headers" },
|
||||
new SiteStreamEvent { CorrelationId = "corr-late-headers" }),
|
||||
neverArrives.Task),
|
||||
_ =>
|
||||
{
|
||||
if (events == 0) connectedBeforeFirstEvent = connected == 1;
|
||||
events++;
|
||||
},
|
||||
_ => { },
|
||||
() => { },
|
||||
() => connected++);
|
||||
|
||||
Assert.Equal(2, events);
|
||||
Assert.Equal(1, connected);
|
||||
Assert.True(connectedBeforeFirstEvent);
|
||||
}
|
||||
finally
|
||||
{
|
||||
SiteStreamGrpcClient.ConnectedHeaderTimeout = previous;
|
||||
}
|
||||
Assert.Equal(2, events);
|
||||
Assert.Equal(1, connected);
|
||||
Assert.True(connectedBeforeFirstEvent);
|
||||
}
|
||||
|
||||
// ── R2: batch unpacking on the client ───────────────────────────────────────
|
||||
@@ -514,12 +505,12 @@ public class SiteStreamGrpcClientTests
|
||||
{
|
||||
AttributeChanged = new AttributeValueUpdate
|
||||
{
|
||||
InstanceUniqueName = "SiteA.Pump01",
|
||||
AttributePath = "Modules.IO",
|
||||
AttributeName = "Seq",
|
||||
Value = value,
|
||||
Quality = Quality.Good,
|
||||
Timestamp = Timestamp.FromDateTimeOffset(ts)
|
||||
InstanceUniqueName = "SiteA.Pump01",
|
||||
AttributePath = "Modules.IO",
|
||||
AttributeName = "Seq",
|
||||
Value = value,
|
||||
Quality = Quality.Good,
|
||||
Timestamp = Timestamp.FromDateTimeOffset(ts)
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
using Grpc.Core;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using NSubstitute;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces;
|
||||
using ZB.MOM.WW.ScadaBridge.Communication.Grpc;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.Communication.Tests;
|
||||
|
||||
public class LowReviewTransportTests
|
||||
{
|
||||
private static SiteStreamGrpcServer Server() => new(
|
||||
Substitute.For<ISiteStreamSubscriber>(), NullLogger<SiteStreamGrpcServer>.Instance);
|
||||
|
||||
[Theory]
|
||||
[InlineData(2001)]
|
||||
[InlineData(int.MaxValue)]
|
||||
public async Task Pulls_RejectOversizedRowRequestsBeforeReading(int size)
|
||||
{
|
||||
var server = Server();
|
||||
var queue = Substitute.For<ISiteAuditQueue>();
|
||||
var store = Substitute.For<IOperationTrackingStore>();
|
||||
server.SetSiteAuditQueue(queue);
|
||||
server.SetOperationTrackingStore(store);
|
||||
var context = Substitute.For<ServerCallContext>();
|
||||
var audit = await Assert.ThrowsAsync<RpcException>(() => server.PullAuditEvents(
|
||||
new PullAuditEventsRequest { BatchSize = size }, context));
|
||||
var calls = await Assert.ThrowsAsync<RpcException>(() => server.PullSiteCalls(
|
||||
new PullSiteCallsRequest { BatchSize = size }, context));
|
||||
Assert.Equal(StatusCode.InvalidArgument, audit.StatusCode);
|
||||
Assert.Equal(StatusCode.InvalidArgument, calls.StatusCode);
|
||||
Assert.Empty(queue.ReceivedCalls());
|
||||
Assert.Empty(store.ReceivedCalls());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RetiredSiteIngestRpcs_ReturnUnimplementedEvenForEmptyBatches()
|
||||
{
|
||||
var server = Server();
|
||||
var context = Substitute.For<ServerCallContext>();
|
||||
var audit = await Assert.ThrowsAsync<RpcException>(() => server.IngestAuditEvents(new(), context));
|
||||
var cached = await Assert.ThrowsAsync<RpcException>(() => server.IngestCachedTelemetry(new(), context));
|
||||
Assert.Equal(StatusCode.Unimplemented, audit.StatusCode);
|
||||
Assert.Equal(StatusCode.Unimplemented, cached.StatusCode);
|
||||
}
|
||||
}
|
||||
+13
-89
@@ -1,100 +1,24 @@
|
||||
using Akka.Actor;
|
||||
using Akka.TestKit.Xunit2;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using Grpc.Core;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using NSubstitute;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Audit;
|
||||
using ZB.MOM.WW.ScadaBridge.Communication.Grpc;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.Communication.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Bundle D D2 tests for <see cref="SiteStreamGrpcServer.IngestAuditEvents"/>.
|
||||
/// Verifies the DTO→entity→actor→ack round-trip through the gRPC handler.
|
||||
/// A tiny <c>StubIngestActor</c> stands in for the central
|
||||
/// <c>AuditLogIngestActor</c>, replying with the EventIds it received so the
|
||||
/// test asserts the wiring without depending on MSSQL.
|
||||
/// </summary>
|
||||
public class SiteStreamIngestAuditEventsTests : TestKit
|
||||
public class SiteStreamIngestAuditEventsTests
|
||||
{
|
||||
private readonly ISiteStreamSubscriber _subscriber = Substitute.For<ISiteStreamSubscriber>();
|
||||
|
||||
private SiteStreamGrpcServer CreateServer() =>
|
||||
new(_subscriber, NullLogger<SiteStreamGrpcServer>.Instance);
|
||||
|
||||
private static ServerCallContext NewContext(CancellationToken ct = default)
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
public async Task RetiredMethodRejectsEmptyAndNonEmptyBatches(bool nonEmpty)
|
||||
{
|
||||
var context = Substitute.For<ServerCallContext>();
|
||||
context.CancellationToken.Returns(ct);
|
||||
return context;
|
||||
}
|
||||
|
||||
private static AuditEventDto NewDto(Guid? id = null) => new()
|
||||
{
|
||||
EventId = (id ?? Guid.NewGuid()).ToString(),
|
||||
OccurredAtUtc = Timestamp.FromDateTime(
|
||||
DateTime.SpecifyKind(new DateTime(2026, 5, 20, 10, 0, 0), DateTimeKind.Utc)),
|
||||
Channel = "ApiOutbound",
|
||||
Kind = "ApiCall",
|
||||
Status = "Delivered",
|
||||
SourceSiteId = "site-1",
|
||||
};
|
||||
|
||||
[Fact]
|
||||
public async Task IngestAuditEvents_With_AuditIngestActor_Routes_To_Actor_Returns_Reply()
|
||||
{
|
||||
// Arrange — a stub actor that echoes every received EventId back.
|
||||
var stubActor = Sys.ActorOf(Props.Create(() => new EchoIngestActor()));
|
||||
|
||||
var server = CreateServer();
|
||||
server.SetAuditIngestActor(stubActor);
|
||||
|
||||
// Build a 3-event batch.
|
||||
var dtos = Enumerable.Range(0, 3).Select(_ => NewDto()).ToList();
|
||||
var batch = new AuditEventBatch();
|
||||
batch.Events.AddRange(dtos);
|
||||
|
||||
// Act
|
||||
var ack = await server.IngestAuditEvents(batch, NewContext());
|
||||
|
||||
// Assert — every dto's id appears in the ack, demonstrating end-to-
|
||||
// end routing through the actor.
|
||||
Assert.Equal(3, ack.AcceptedEventIds.Count);
|
||||
var expectedIds = dtos.Select(d => d.EventId).ToHashSet();
|
||||
Assert.True(expectedIds.SetEquals(ack.AcceptedEventIds.ToHashSet()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task IngestAuditEvents_NoActor_Wired_ReturnsEmptyAck()
|
||||
{
|
||||
var server = CreateServer();
|
||||
// Intentionally do NOT call SetAuditIngestActor — simulates host
|
||||
// startup race window.
|
||||
|
||||
var batch = new AuditEventBatch();
|
||||
batch.Events.Add(NewDto());
|
||||
|
||||
var ack = await server.IngestAuditEvents(batch, NewContext());
|
||||
|
||||
Assert.Empty(ack.AcceptedEventIds);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tiny ReceiveActor that echoes every EventId in an incoming
|
||||
/// <see cref="IngestAuditEventsCommand"/> back as an
|
||||
/// <see cref="IngestAuditEventsReply"/>. Stands in for the central
|
||||
/// AuditLogIngestActor so this test never touches MSSQL.
|
||||
/// </summary>
|
||||
private sealed class EchoIngestActor : ReceiveActor
|
||||
{
|
||||
public EchoIngestActor()
|
||||
{
|
||||
Receive<IngestAuditEventsCommand>(cmd =>
|
||||
{
|
||||
var ids = cmd.Events.Select(e => e.EventId).ToList();
|
||||
Sender.Tell(new IngestAuditEventsReply(ids));
|
||||
});
|
||||
}
|
||||
var server = new SiteStreamGrpcServer(Substitute.For<ISiteStreamSubscriber>(),
|
||||
NullLogger<SiteStreamGrpcServer>.Instance);
|
||||
var request = new AuditEventBatch();
|
||||
if (nonEmpty) request.Events.Add(new AuditEventDto());
|
||||
var error = await Assert.ThrowsAsync<RpcException>(() => server.IngestAuditEvents(
|
||||
request, Substitute.For<ServerCallContext>()));
|
||||
Assert.Equal(StatusCode.Unimplemented, error.StatusCode);
|
||||
Assert.Contains("CentralControlService", error.Status.Detail);
|
||||
}
|
||||
}
|
||||
|
||||
+13
-110
@@ -1,121 +1,24 @@
|
||||
using Akka.Actor;
|
||||
using Akka.TestKit.Xunit2;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
using Grpc.Core;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using NSubstitute;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Messages.Audit;
|
||||
using ZB.MOM.WW.ScadaBridge.Communication.Grpc;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.Communication.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Bundle D D2 tests for <see cref="SiteStreamGrpcServer.IngestCachedTelemetry"/>.
|
||||
/// Verifies the DTO→entity→actor→ack round-trip through the gRPC handler. A
|
||||
/// tiny <c>EchoCachedIngestActor</c> stands in for the central
|
||||
/// <c>AuditLogIngestActor</c>, replying with the EventIds it received so the
|
||||
/// test asserts the wiring without depending on MSSQL.
|
||||
/// </summary>
|
||||
public class SiteStreamIngestCachedTelemetryTests : TestKit
|
||||
public class SiteStreamIngestCachedTelemetryTests
|
||||
{
|
||||
private readonly ISiteStreamSubscriber _subscriber = Substitute.For<ISiteStreamSubscriber>();
|
||||
|
||||
private SiteStreamGrpcServer CreateServer() =>
|
||||
new(_subscriber, NullLogger<SiteStreamGrpcServer>.Instance);
|
||||
|
||||
private static ServerCallContext NewContext(CancellationToken ct = default)
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
public async Task RetiredMethodRejectsEmptyAndNonEmptyBatches(bool nonEmpty)
|
||||
{
|
||||
var context = Substitute.For<ServerCallContext>();
|
||||
context.CancellationToken.Returns(ct);
|
||||
return context;
|
||||
}
|
||||
|
||||
private static CachedTelemetryPacket NewPacket(Guid? eventId = null, Guid? trackedId = null)
|
||||
{
|
||||
var now = Timestamp.FromDateTime(
|
||||
DateTime.SpecifyKind(new DateTime(2026, 5, 20, 10, 0, 0), DateTimeKind.Utc));
|
||||
return new CachedTelemetryPacket
|
||||
{
|
||||
AuditEvent = new AuditEventDto
|
||||
{
|
||||
EventId = (eventId ?? Guid.NewGuid()).ToString(),
|
||||
OccurredAtUtc = now,
|
||||
Channel = "ApiOutbound",
|
||||
Kind = "CachedSubmit",
|
||||
Status = "Submitted",
|
||||
SourceSiteId = "site-1",
|
||||
CorrelationId = (trackedId ?? Guid.NewGuid()).ToString(),
|
||||
},
|
||||
Operational = new SiteCallOperationalDto
|
||||
{
|
||||
TrackedOperationId = (trackedId ?? Guid.NewGuid()).ToString(),
|
||||
Channel = "ApiOutbound",
|
||||
Target = "ERP.GetOrder",
|
||||
SourceSite = "site-1",
|
||||
Status = "Submitted",
|
||||
RetryCount = 0,
|
||||
CreatedAtUtc = now,
|
||||
UpdatedAtUtc = now,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task IngestCachedTelemetry_RoutesToActor_ReturnsReply()
|
||||
{
|
||||
// Arrange — stub actor that echoes every received EventId back.
|
||||
var stubActor = Sys.ActorOf(Props.Create(() => new EchoCachedIngestActor()));
|
||||
|
||||
var server = CreateServer();
|
||||
server.SetAuditIngestActor(stubActor);
|
||||
|
||||
var packets = Enumerable.Range(0, 3)
|
||||
.Select(_ => NewPacket())
|
||||
.ToList();
|
||||
|
||||
var batch = new CachedTelemetryBatch();
|
||||
batch.Packets.AddRange(packets);
|
||||
|
||||
// Act
|
||||
var ack = await server.IngestCachedTelemetry(batch, NewContext());
|
||||
|
||||
// Assert — every packet's EventId appears in the ack, demonstrating
|
||||
// end-to-end routing through the actor.
|
||||
Assert.Equal(3, ack.AcceptedEventIds.Count);
|
||||
var expectedIds = packets.Select(p => p.AuditEvent.EventId).ToHashSet();
|
||||
Assert.True(expectedIds.SetEquals(ack.AcceptedEventIds.ToHashSet()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task IngestCachedTelemetry_NoActorWired_ReturnsEmptyAck()
|
||||
{
|
||||
var server = CreateServer();
|
||||
// Intentionally do NOT call SetAuditIngestActor — simulates host
|
||||
// startup race window.
|
||||
|
||||
var batch = new CachedTelemetryBatch();
|
||||
batch.Packets.Add(NewPacket());
|
||||
|
||||
var ack = await server.IngestCachedTelemetry(batch, NewContext());
|
||||
|
||||
Assert.Empty(ack.AcceptedEventIds);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tiny ReceiveActor that echoes every EventId in an incoming
|
||||
/// <see cref="IngestCachedTelemetryCommand"/> back as an
|
||||
/// <see cref="IngestCachedTelemetryReply"/>. Stands in for the central
|
||||
/// AuditLogIngestActor so this test never touches MSSQL.
|
||||
/// </summary>
|
||||
private sealed class EchoCachedIngestActor : ReceiveActor
|
||||
{
|
||||
public EchoCachedIngestActor()
|
||||
{
|
||||
Receive<IngestCachedTelemetryCommand>(cmd =>
|
||||
{
|
||||
var ids = cmd.Entries.Select(e => e.Audit.EventId).ToList();
|
||||
Sender.Tell(new IngestCachedTelemetryReply(ids));
|
||||
});
|
||||
}
|
||||
var server = new SiteStreamGrpcServer(Substitute.For<ISiteStreamSubscriber>(),
|
||||
NullLogger<SiteStreamGrpcServer>.Instance);
|
||||
var request = new CachedTelemetryBatch();
|
||||
if (nonEmpty) request.Packets.Add(new CachedTelemetryPacket());
|
||||
var error = await Assert.ThrowsAsync<RpcException>(() => server.IngestCachedTelemetry(
|
||||
request, Substitute.For<ServerCallContext>()));
|
||||
Assert.Equal(StatusCode.Unimplemented, error.StatusCode);
|
||||
Assert.Contains("CentralControlService", error.Status.Detail);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user