Resolves StoreAndForward-001, ExternalSystemGateway-001, NotificationService-001 — one systemic gap where buffered messages were persisted but never delivered, and the active node never replicated its buffer to the standby. Delivery handlers (ExternalSystemGateway-001 / NotificationService-001): - AkkaHostedService registers delivery handlers for the ExternalSystem, CachedDbWrite and Notification categories after StoreAndForwardService starts; each resolves its scoped consumer in a fresh DI scope. - ExternalSystemClient, DatabaseGateway and NotificationDeliveryService each gain a DeliverBufferedAsync method: re-resolve the target and re-attempt delivery, returning true/false/throwing per the transient-vs-permanent contract. - EnqueueAsync gains an attemptImmediateDelivery flag; CachedCallAsync and NotificationDeliveryService.SendAsync pass false (they already attempted delivery themselves) so registering a handler does not dispatch twice. Replication (StoreAndForward-001): - ReplicationService is injected into StoreAndForwardService; a new BufferAsync helper replicates every enqueue, and successful-retry removes and parks are replicated too. Fire-and-forget, no-op when replication is disabled. Tests: StoreAndForwardReplicationTests (Add/Remove/Park observed), attemptImmediateDelivery behaviour, and DeliverBufferedAsync paths for each consumer. Full solution builds; StoreAndForward/ExternalSystemGateway/ NotificationService suites green.
79 lines
2.9 KiB
C#
79 lines
2.9 KiB
C#
using Microsoft.Extensions.Logging.Abstractions;
|
|
using NSubstitute;
|
|
using ScadaLink.Commons.Entities.ExternalSystems;
|
|
using ScadaLink.Commons.Interfaces.Repositories;
|
|
|
|
namespace ScadaLink.ExternalSystemGateway.Tests;
|
|
|
|
/// <summary>
|
|
/// WP-9: Tests for Database access — connection resolution, cached writes.
|
|
/// </summary>
|
|
public class DatabaseGatewayTests
|
|
{
|
|
private readonly IExternalSystemRepository _repository = Substitute.For<IExternalSystemRepository>();
|
|
|
|
[Fact]
|
|
public async Task GetConnection_NotFound_Throws()
|
|
{
|
|
_repository.GetAllDatabaseConnectionsAsync().Returns(new List<DatabaseConnectionDefinition>());
|
|
|
|
var gateway = new DatabaseGateway(
|
|
_repository,
|
|
NullLogger<DatabaseGateway>.Instance);
|
|
|
|
await Assert.ThrowsAsync<InvalidOperationException>(
|
|
() => gateway.GetConnectionAsync("nonexistent"));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CachedWrite_NoStoreAndForward_Throws()
|
|
{
|
|
var conn = new DatabaseConnectionDefinition("testDb", "Server=localhost;Database=test") { Id = 1 };
|
|
_repository.GetAllDatabaseConnectionsAsync()
|
|
.Returns(new List<DatabaseConnectionDefinition> { conn });
|
|
|
|
var gateway = new DatabaseGateway(
|
|
_repository,
|
|
NullLogger<DatabaseGateway>.Instance,
|
|
storeAndForward: null);
|
|
|
|
await Assert.ThrowsAsync<InvalidOperationException>(
|
|
() => gateway.CachedWriteAsync("testDb", "INSERT INTO t VALUES (1)"));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CachedWrite_ConnectionNotFound_Throws()
|
|
{
|
|
_repository.GetAllDatabaseConnectionsAsync().Returns(new List<DatabaseConnectionDefinition>());
|
|
|
|
var gateway = new DatabaseGateway(
|
|
_repository,
|
|
NullLogger<DatabaseGateway>.Instance);
|
|
|
|
await Assert.ThrowsAsync<InvalidOperationException>(
|
|
() => gateway.CachedWriteAsync("nonexistent", "INSERT INTO t VALUES (1)"));
|
|
}
|
|
|
|
// ── ExternalSystemGateway-001: buffered CachedDbWrite delivery handler ──
|
|
|
|
[Fact]
|
|
public async Task DeliverBuffered_ConnectionNoLongerExists_ReturnsFalseSoMessageParks()
|
|
{
|
|
_repository.GetAllDatabaseConnectionsAsync().Returns(new List<DatabaseConnectionDefinition>());
|
|
var gateway = new DatabaseGateway(_repository, NullLogger<DatabaseGateway>.Instance);
|
|
|
|
var message = new ScadaLink.StoreAndForward.StoreAndForwardMessage
|
|
{
|
|
Id = Guid.NewGuid().ToString("N"),
|
|
Category = ScadaLink.Commons.Types.Enums.StoreAndForwardCategory.CachedDbWrite,
|
|
Target = "gone-db",
|
|
PayloadJson =
|
|
"""{"ConnectionName":"gone-db","Sql":"INSERT INTO t VALUES (1)","Parameters":null}""",
|
|
};
|
|
|
|
var delivered = await gateway.DeliverBufferedAsync(message);
|
|
|
|
Assert.False(delivered); // permanent — the S&F engine parks the message
|
|
}
|
|
}
|