refactor: rename ScadaLink → ZB.MOM.WW.ScadaBridge (code + projects + namespaces)

Solution + 23 src projects + 26 test projects renamed; folders, csproj,
namespaces, and ScadaLinkDbContext/ScadaBridgeDbContext class updated.
ActorSystem "scadalink" → "scadabridge", Akka seed-node URLs migrated.
SQL roles/logins, LDAP domains, CLI command name, and CLI config dir
(~/.scadalink → ~/.scadabridge) also renamed.

Build green; 5 Host.Tests fail awaiting SQL login rename in next commit.
Pre-existing StaleTagMonitor timing flakes unchanged.

Rename script committed at tools/rename-to-scadabridge.sh.
This commit is contained in:
Joseph Doherty
2026-05-28 09:37:45 -04:00
parent 6d87ee3c3b
commit 7b0b9c7365
1531 changed files with 11180 additions and 11054 deletions
@@ -0,0 +1,125 @@
using Google.Protobuf;
using Google.Protobuf.WellKnownTypes;
using ZB.MOM.WW.ScadaBridge.Communication.Grpc;
namespace ZB.MOM.WW.ScadaBridge.Communication.Tests.Protos;
/// <summary>
/// Wire-format round-trip tests for the Audit Log (#23) telemetry proto messages
/// (<see cref="AuditEventDto"/>, <see cref="AuditEventBatch"/>, <see cref="IngestAck"/>).
/// Locks the additive contract the site → central audit pipeline depends on.
/// </summary>
public class AuditEventProtoTests
{
[Fact]
public void AuditEventDto_RoundTrip_PreservesAllFields()
{
var occurredAt = Timestamp.FromDateTimeOffset(
new DateTimeOffset(2026, 5, 20, 10, 15, 30, 123, TimeSpan.Zero));
var original = new AuditEventDto
{
EventId = Guid.NewGuid().ToString(),
OccurredAtUtc = occurredAt,
Channel = "ApiOutbound",
Kind = "ApiCall",
CorrelationId = Guid.NewGuid().ToString(),
SourceSiteId = "site-1",
SourceNode = "node-a",
SourceInstanceId = "Pump01",
SourceScript = "OnDemand",
Actor = "design-key",
Target = "weather-api",
Status = "Delivered",
HttpStatus = 200,
DurationMs = 42,
ErrorMessage = "no error",
ErrorDetail = "stack",
RequestSummary = "GET /weather?city=brisbane",
ResponseSummary = "{ \"temp\": 22.5 }",
PayloadTruncated = true,
Extra = "{ \"retryCount\": 0 }"
};
var bytes = original.ToByteArray();
var deserialized = AuditEventDto.Parser.ParseFrom(bytes);
Assert.Equal(original.EventId, deserialized.EventId);
Assert.Equal(original.OccurredAtUtc, deserialized.OccurredAtUtc);
Assert.Equal(original.Channel, deserialized.Channel);
Assert.Equal(original.Kind, deserialized.Kind);
Assert.Equal(original.CorrelationId, deserialized.CorrelationId);
Assert.Equal(original.SourceSiteId, deserialized.SourceSiteId);
Assert.Equal(original.SourceNode, deserialized.SourceNode);
Assert.Equal(original.SourceInstanceId, deserialized.SourceInstanceId);
Assert.Equal(original.SourceScript, deserialized.SourceScript);
Assert.Equal(original.Actor, deserialized.Actor);
Assert.Equal(original.Target, deserialized.Target);
Assert.Equal(original.Status, deserialized.Status);
Assert.Equal(original.HttpStatus, deserialized.HttpStatus);
Assert.Equal(original.DurationMs, deserialized.DurationMs);
Assert.Equal(original.ErrorMessage, deserialized.ErrorMessage);
Assert.Equal(original.ErrorDetail, deserialized.ErrorDetail);
Assert.Equal(original.RequestSummary, deserialized.RequestSummary);
Assert.Equal(original.ResponseSummary, deserialized.ResponseSummary);
Assert.Equal(original.PayloadTruncated, deserialized.PayloadTruncated);
Assert.Equal(original.Extra, deserialized.Extra);
}
[Fact]
public void AuditEventDto_NullableInt_AbsentByDefault_NotIncludedInWire()
{
// Int32Value fields (http_status, duration_ms) are wrapper-typed in proto;
// when unset, the wrapper is absent, not serialized, and deserializes back to null.
var original = new AuditEventDto
{
EventId = Guid.NewGuid().ToString(),
OccurredAtUtc = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow),
Channel = "Notification",
Kind = "NotifySend",
Status = "Submitted"
};
Assert.Null(original.HttpStatus);
Assert.Null(original.DurationMs);
var bytes = original.ToByteArray();
var deserialized = AuditEventDto.Parser.ParseFrom(bytes);
Assert.Null(deserialized.HttpStatus);
Assert.Null(deserialized.DurationMs);
}
[Fact]
public void AuditEventBatch_Empty_RoundTrip_Yields_EmptyEvents()
{
var original = new AuditEventBatch();
Assert.Empty(original.Events);
var bytes = original.ToByteArray();
var deserialized = AuditEventBatch.Parser.ParseFrom(bytes);
Assert.Empty(deserialized.Events);
}
[Fact]
public void IngestAck_PreservesAcceptedEventIds()
{
var id1 = Guid.NewGuid().ToString();
var id2 = Guid.NewGuid().ToString();
var id3 = Guid.NewGuid().ToString();
var original = new IngestAck();
original.AcceptedEventIds.Add(id1);
original.AcceptedEventIds.Add(id2);
original.AcceptedEventIds.Add(id3);
var bytes = original.ToByteArray();
var deserialized = IngestAck.Parser.ParseFrom(bytes);
Assert.Equal(3, deserialized.AcceptedEventIds.Count);
Assert.Equal(id1, deserialized.AcceptedEventIds[0]);
Assert.Equal(id2, deserialized.AcceptedEventIds[1]);
Assert.Equal(id3, deserialized.AcceptedEventIds[2]);
}
}
@@ -0,0 +1,175 @@
using Google.Protobuf;
using Google.Protobuf.WellKnownTypes;
using ZB.MOM.WW.ScadaBridge.Communication.Grpc;
namespace ZB.MOM.WW.ScadaBridge.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",
SourceNode = "node-a",
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.SourceNode, deserialized.SourceNode);
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);
}
}
@@ -0,0 +1,83 @@
using Google.Protobuf;
using Google.Protobuf.WellKnownTypes;
using ZB.MOM.WW.ScadaBridge.Communication.Grpc;
namespace ZB.MOM.WW.ScadaBridge.Communication.Tests.Protos;
/// <summary>
/// Wire-format round-trip tests for the Audit Log (#23) M6 reconciliation
/// pull proto messages (<see cref="PullAuditEventsRequest"/>,
/// <see cref="PullAuditEventsResponse"/>). Locks the additive contract the
/// central→site reconciliation puller depends on.
/// </summary>
public class PullAuditEventsProtoTests
{
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 = "ApiCall",
Status = "Delivered",
SourceSiteId = "site-1",
};
[Fact]
public void PullAuditEventsRequest_RoundTrip()
{
var sinceUtc = Timestamp.FromDateTimeOffset(
new DateTimeOffset(2026, 5, 20, 9, 0, 0, TimeSpan.Zero));
var original = new PullAuditEventsRequest
{
SinceUtc = sinceUtc,
BatchSize = 250,
};
var bytes = original.ToByteArray();
var deserialized = PullAuditEventsRequest.Parser.ParseFrom(bytes);
Assert.Equal(sinceUtc, deserialized.SinceUtc);
Assert.Equal(250, deserialized.BatchSize);
}
[Fact]
public void PullAuditEventsResponse_RoundTrip_WithEvents_And_MoreAvailable()
{
var dtos = Enumerable.Range(0, 4).Select(_ => NewAuditDto()).ToList();
var original = new PullAuditEventsResponse
{
MoreAvailable = true,
};
original.Events.AddRange(dtos);
var bytes = original.ToByteArray();
var deserialized = PullAuditEventsResponse.Parser.ParseFrom(bytes);
Assert.True(deserialized.MoreAvailable);
Assert.Equal(4, deserialized.Events.Count);
for (int i = 0; i < dtos.Count; i++)
{
Assert.Equal(dtos[i].EventId, deserialized.Events[i].EventId);
Assert.Equal(dtos[i].Status, deserialized.Events[i].Status);
Assert.Equal(dtos[i].SourceSiteId, deserialized.Events[i].SourceSiteId);
Assert.Equal(dtos[i].OccurredAtUtc, deserialized.Events[i].OccurredAtUtc);
}
}
[Fact]
public void PullAuditEventsResponse_Empty_Yields_EmptyEvents()
{
var original = new PullAuditEventsResponse();
Assert.Empty(original.Events);
Assert.False(original.MoreAvailable);
var bytes = original.ToByteArray();
var deserialized = PullAuditEventsResponse.Parser.ParseFrom(bytes);
Assert.Empty(deserialized.Events);
Assert.False(deserialized.MoreAvailable);
}
}