174 lines
6.7 KiB
C#
174 lines
6.7 KiB
C#
using Google.Protobuf;
|
|
using Google.Protobuf.WellKnownTypes;
|
|
using ScadaLink.Communication.Grpc;
|
|
|
|
namespace ScadaLink.Communication.Tests.Protos;
|
|
|
|
/// <summary>
|
|
/// Wire-format round-trip tests for the Audit Log (#23) M3 cached-telemetry
|
|
/// proto messages (<see cref="SiteCallOperationalDto"/>,
|
|
/// <see cref="CachedTelemetryPacket"/>, <see cref="CachedTelemetryBatch"/>).
|
|
/// Locks the additive contract the central dual-write transaction depends on.
|
|
/// </summary>
|
|
public class CachedTelemetryProtoTests
|
|
{
|
|
private static AuditEventDto NewAuditDto(Guid? id = null) => new()
|
|
{
|
|
EventId = (id ?? Guid.NewGuid()).ToString(),
|
|
OccurredAtUtc = Timestamp.FromDateTimeOffset(
|
|
new DateTimeOffset(2026, 5, 20, 10, 15, 30, 123, TimeSpan.Zero)),
|
|
Channel = "ApiOutbound",
|
|
Kind = "CachedSubmit",
|
|
Status = "Submitted",
|
|
SourceSiteId = "site-1",
|
|
};
|
|
|
|
[Fact]
|
|
public void SiteCallOperationalDto_RoundTrip_PreservesAllFields()
|
|
{
|
|
var createdAt = Timestamp.FromDateTimeOffset(
|
|
new DateTimeOffset(2026, 5, 20, 10, 0, 0, TimeSpan.Zero));
|
|
var updatedAt = Timestamp.FromDateTimeOffset(
|
|
new DateTimeOffset(2026, 5, 20, 10, 5, 0, TimeSpan.Zero));
|
|
var terminalAt = Timestamp.FromDateTimeOffset(
|
|
new DateTimeOffset(2026, 5, 20, 10, 10, 0, TimeSpan.Zero));
|
|
|
|
var original = new SiteCallOperationalDto
|
|
{
|
|
TrackedOperationId = Guid.NewGuid().ToString(),
|
|
Channel = "ApiOutbound",
|
|
Target = "ERP.GetOrder",
|
|
SourceSite = "site-melbourne",
|
|
Status = "Delivered",
|
|
RetryCount = 3,
|
|
LastError = "transient 503",
|
|
HttpStatus = 200,
|
|
CreatedAtUtc = createdAt,
|
|
UpdatedAtUtc = updatedAt,
|
|
TerminalAtUtc = terminalAt,
|
|
};
|
|
|
|
var bytes = original.ToByteArray();
|
|
var deserialized = SiteCallOperationalDto.Parser.ParseFrom(bytes);
|
|
|
|
Assert.Equal(original.TrackedOperationId, deserialized.TrackedOperationId);
|
|
Assert.Equal(original.Channel, deserialized.Channel);
|
|
Assert.Equal(original.Target, deserialized.Target);
|
|
Assert.Equal(original.SourceSite, deserialized.SourceSite);
|
|
Assert.Equal(original.Status, deserialized.Status);
|
|
Assert.Equal(original.RetryCount, deserialized.RetryCount);
|
|
Assert.Equal(original.LastError, deserialized.LastError);
|
|
Assert.Equal(original.HttpStatus, deserialized.HttpStatus);
|
|
Assert.Equal(original.CreatedAtUtc, deserialized.CreatedAtUtc);
|
|
Assert.Equal(original.UpdatedAtUtc, deserialized.UpdatedAtUtc);
|
|
Assert.Equal(original.TerminalAtUtc, deserialized.TerminalAtUtc);
|
|
}
|
|
|
|
[Fact]
|
|
public void SiteCallOperationalDto_TerminalAt_AbsentWhenNotTerminal()
|
|
{
|
|
// Lifecycle events prior to the terminal step leave TerminalAtUtc unset;
|
|
// the well-known Timestamp wrapper is absent on the wire (null in C#).
|
|
var dto = new SiteCallOperationalDto
|
|
{
|
|
TrackedOperationId = Guid.NewGuid().ToString(),
|
|
Channel = "DbOutbound",
|
|
Target = "warehouse.dbo.WriteOrder",
|
|
SourceSite = "site-brisbane",
|
|
Status = "Attempted",
|
|
RetryCount = 1,
|
|
CreatedAtUtc = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow),
|
|
UpdatedAtUtc = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow),
|
|
};
|
|
|
|
Assert.Null(dto.TerminalAtUtc);
|
|
|
|
var bytes = dto.ToByteArray();
|
|
var deserialized = SiteCallOperationalDto.Parser.ParseFrom(bytes);
|
|
|
|
Assert.Null(deserialized.TerminalAtUtc);
|
|
}
|
|
|
|
[Fact]
|
|
public void SiteCallOperationalDto_NullableHttpStatus_AbsentByDefault()
|
|
{
|
|
// Int32Value wrapper-typed http_status — unset round-trips as null,
|
|
// matching DB nullable column semantics for non-API cached writes.
|
|
var dto = new SiteCallOperationalDto
|
|
{
|
|
TrackedOperationId = Guid.NewGuid().ToString(),
|
|
Channel = "DbOutbound",
|
|
Target = "warehouse.dbo.WriteOrder",
|
|
SourceSite = "site-brisbane",
|
|
Status = "Submitted",
|
|
RetryCount = 0,
|
|
CreatedAtUtc = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow),
|
|
UpdatedAtUtc = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow),
|
|
};
|
|
|
|
Assert.Null(dto.HttpStatus);
|
|
|
|
var bytes = dto.ToByteArray();
|
|
var deserialized = SiteCallOperationalDto.Parser.ParseFrom(bytes);
|
|
|
|
Assert.Null(deserialized.HttpStatus);
|
|
}
|
|
|
|
[Fact]
|
|
public void CachedTelemetryPacket_RoundTrip_PreservesNestedEntities()
|
|
{
|
|
var trackedOpId = Guid.NewGuid().ToString();
|
|
var auditDto = NewAuditDto();
|
|
auditDto.Target = "ERP.GetOrder";
|
|
auditDto.Status = "Attempted";
|
|
|
|
var operationalDto = new SiteCallOperationalDto
|
|
{
|
|
TrackedOperationId = trackedOpId,
|
|
Channel = "ApiOutbound",
|
|
Target = "ERP.GetOrder",
|
|
SourceSite = "site-1",
|
|
Status = "Attempted",
|
|
RetryCount = 2,
|
|
HttpStatus = 503,
|
|
LastError = "Service unavailable",
|
|
CreatedAtUtc = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow),
|
|
UpdatedAtUtc = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow),
|
|
};
|
|
|
|
var original = new CachedTelemetryPacket
|
|
{
|
|
AuditEvent = auditDto,
|
|
Operational = operationalDto,
|
|
};
|
|
|
|
var bytes = original.ToByteArray();
|
|
var deserialized = CachedTelemetryPacket.Parser.ParseFrom(bytes);
|
|
|
|
Assert.NotNull(deserialized.AuditEvent);
|
|
Assert.Equal(auditDto.EventId, deserialized.AuditEvent.EventId);
|
|
Assert.Equal(auditDto.Target, deserialized.AuditEvent.Target);
|
|
Assert.Equal(auditDto.Status, deserialized.AuditEvent.Status);
|
|
|
|
Assert.NotNull(deserialized.Operational);
|
|
Assert.Equal(trackedOpId, deserialized.Operational.TrackedOperationId);
|
|
Assert.Equal(operationalDto.Channel, deserialized.Operational.Channel);
|
|
Assert.Equal(operationalDto.Status, deserialized.Operational.Status);
|
|
Assert.Equal(operationalDto.RetryCount, deserialized.Operational.RetryCount);
|
|
Assert.Equal(operationalDto.HttpStatus, deserialized.Operational.HttpStatus);
|
|
Assert.Equal(operationalDto.LastError, deserialized.Operational.LastError);
|
|
}
|
|
|
|
[Fact]
|
|
public void CachedTelemetryBatch_Empty_RoundTrip_Yields_EmptyPackets()
|
|
{
|
|
var original = new CachedTelemetryBatch();
|
|
Assert.Empty(original.Packets);
|
|
|
|
var bytes = original.ToByteArray();
|
|
var deserialized = CachedTelemetryBatch.Parser.ParseFrom(bytes);
|
|
|
|
Assert.Empty(deserialized.Packets);
|
|
}
|
|
}
|