test(sms): regression tests for code-review fixes

Lock the behaviors changed by the review-fix commit + the security invariants:

- ManagementActorTests: UpdateSms/SmtpConfig now require Administrator (updated the
  existing success cases from Designer); + UpdateSmsConfig_WithDesignerRole_Returns
  Unauthorized and _WithEmptyAuthToken_PreservesExistingToken regression tests.
- SecretEncryptionTests: SmsConfiguration.AuthToken stored-encrypted round-trip +
  null round-trip (AccountSid stays plaintext) — guards ApplySecretColumnEncryption.
- ArtifactDiffTests: CompareSmsConfiguration New/Identical/Modified + the secret
  presence-only invariant (value never echoed, presence-flip shows <present> only).
- UpdateCommandContractTests: notification sms update core fields Required, --auth-token optional.
- NotificationListsPageTests: SMS recipient badge shows phone, not "Name <>".
- NotificationOutboxActorDispatchTests: SMS-typed notification routes to the SMS
  adapter (StubAdapter.Type made configurable), not the Email adapter.
- NotificationRecipientTests (new): ForEmail/ForSms + public-ctor invariants.
This commit is contained in:
Joseph Doherty
2026-06-19 15:09:47 -04:00
parent cd8e4872f6
commit a9393c8913
7 changed files with 312 additions and 10 deletions
@@ -1,6 +1,7 @@
using System.Text.Json;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Instances;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.InboundApi;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Notifications;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Scripts;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Sites;
using ZB.MOM.WW.ScadaBridge.Commons.Entities.Templates;
@@ -268,6 +269,78 @@ public sealed class ArtifactDiffTests
Assert.DoesNotContain("4840", item.FieldDiffJson!);
}
// ============ SMS: CompareSmsConfiguration (S10b) ============
private static SmsConfiguration ExistingSms() =>
new("AC1", "+15550000000") { Id = 1, AuthToken = "stored-token" };
private static SmsConfigDto SmsDto(
string from = "+15550000000",
string? msgSid = null,
bool withSecret = true) =>
new("AC1", from, msgSid, ApiBaseUrl: null, ConnectionTimeoutSeconds: 30,
MaxRetries: 10, RetryDelay: TimeSpan.FromMinutes(1),
Secrets: withSecret
? new SecretsBlock(new Dictionary<string, string> { ["AuthToken"] = "wire-token" })
: null);
[Fact]
public void CompareSmsConfiguration_ExistingNull_New()
{
var item = _diff.CompareSmsConfiguration(SmsDto(), existing: null);
Assert.Equal(ConflictKind.New, item.Kind);
Assert.Equal("SmsConfiguration", item.EntityType);
Assert.Equal("AC1", item.Name);
}
[Fact]
public void CompareSmsConfiguration_AllFieldsAndPresenceMatch_Identical()
{
var item = _diff.CompareSmsConfiguration(SmsDto(), ExistingSms());
Assert.Equal(ConflictKind.Identical, item.Kind);
Assert.Null(item.FieldDiffJson);
}
[Fact]
public void CompareSmsConfiguration_FromNumberDiffers_Modified()
{
var item = _diff.CompareSmsConfiguration(SmsDto(from: "+15559999999"), ExistingSms());
Assert.Equal(ConflictKind.Modified, item.Kind);
var change = ChangeFor(item, "FromNumber");
Assert.Equal("+15550000000", change.GetProperty("oldValue").GetString());
Assert.Equal("+15559999999", change.GetProperty("newValue").GetString());
}
[Fact]
public void CompareSmsConfiguration_AuthTokenValueChangesButPresencePreserved_NoSecretEcho()
{
// Both sides carry a token; only the value differs. Presence-only comparison =>
// no Secrets.AuthToken change, and neither token value is ever echoed.
var item = _diff.CompareSmsConfiguration(SmsDto(withSecret: true), ExistingSms());
Assert.Equal(ConflictKind.Identical, item.Kind);
var json = item.FieldDiffJson ?? string.Empty;
Assert.DoesNotContain("stored-token", json);
Assert.DoesNotContain("wire-token", json);
}
[Fact]
public void CompareSmsConfiguration_AuthTokenPresenceFlips_ShowsPresentMarkerOnly()
{
// Existing has a token; incoming carries no secret => a presence change that
// surfaces only the "<present>" marker, never the token value.
var item = _diff.CompareSmsConfiguration(SmsDto(withSecret: false), ExistingSms());
Assert.Equal(ConflictKind.Modified, item.Kind);
var change = ChangeFor(item, "Secrets.AuthToken");
Assert.Equal("<present>", change.GetProperty("oldValue").GetString());
Assert.False(change.TryGetProperty("newValue", out _));
Assert.DoesNotContain("stored-token", item.FieldDiffJson!);
}
// ============ M8: CompareInstance ============
[Fact]