feat(notifications): OAuth2 authority/scope management + CLI + UI surfaces

Additive UpdateSmtpConfigCommand params (OAuth2Authority/OAuth2Scope),
preserve-on-null handler application, CLI --oauth2-authority/--oauth2-scope
on smtp update, Central UI text inputs shown only for the OAuth2 auth type,
and the Component-NotificationService doc paragraph with M365 defaults.

Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
This commit is contained in:
Joseph Doherty
2026-07-10 04:25:52 -04:00
parent 2e7be1e852
commit 0ee9975111
6 changed files with 126 additions and 5 deletions
@@ -1404,6 +1404,65 @@ public class ManagementActorTests : TestKit, IDisposable
Assert.Equal("Basic", existing.AuthType);
}
[Fact]
public void UpdateSmtpConfig_AppliesOAuth2AuthorityAndScope()
{
var notifRepo = Substitute.For<INotificationRepository>();
var existing = new Commons.Entities.Notifications.SmtpConfiguration(
"old.example.com", "OAuth2", "old@example.com")
{
Id = 1,
Port = 25,
OAuth2Authority = "https://old.authority/token",
OAuth2Scope = "old.scope/.default",
};
notifRepo.GetSmtpConfigurationByIdAsync(1, Arg.Any<CancellationToken>()).Returns(existing);
_services.AddScoped(_ => notifRepo);
var actor = CreateActor();
var envelope = Envelope(
new UpdateSmtpConfigCommand(
1, "new.example.com", 465, "OAuth2", "new@example.com",
OAuth2Authority: "https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token",
OAuth2Scope: "https://outlook.office365.com/.default"),
"Administrator");
actor.Tell(envelope);
var response = ExpectMsg<ManagementSuccess>(TimeSpan.FromSeconds(5));
Assert.Equal(envelope.CorrelationId, response.CorrelationId);
Assert.Equal("https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token", existing.OAuth2Authority);
Assert.Equal("https://outlook.office365.com/.default", existing.OAuth2Scope);
}
[Fact]
public void UpdateSmtpConfig_WithNullOAuth2AuthorityAndScope_PreservesExistingValues()
{
var notifRepo = Substitute.For<INotificationRepository>();
var existing = new Commons.Entities.Notifications.SmtpConfiguration(
"old.example.com", "OAuth2", "old@example.com")
{
Id = 1,
Port = 25,
OAuth2Authority = "https://old.authority/token",
OAuth2Scope = "old.scope/.default",
};
notifRepo.GetSmtpConfigurationByIdAsync(1, Arg.Any<CancellationToken>()).Returns(existing);
_services.AddScoped(_ => notifRepo);
var actor = CreateActor();
var envelope = Envelope(
new UpdateSmtpConfigCommand(1, "new.example.com", 465, "OAuth2", "new@example.com"),
"Administrator");
actor.Tell(envelope);
var response = ExpectMsg<ManagementSuccess>(TimeSpan.FromSeconds(5));
Assert.Equal(envelope.CorrelationId, response.CorrelationId);
Assert.Equal("https://old.authority/token", existing.OAuth2Authority);
Assert.Equal("old.scope/.default", existing.OAuth2Scope);
}
// ========================================================================
// SMS Notifications (S5) — list Type discriminator + SMS-config management
// ========================================================================