fix(notification-service): resolve NotificationService-002/003/004 — error classification by SMTP status code, single SMTP client
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
| Last reviewed | 2026-05-16 |
|
||||
| Reviewer | claude-agent |
|
||||
| Commit reviewed | `9c60592` |
|
||||
| Open findings | 11 |
|
||||
| Open findings | 8 |
|
||||
|
||||
## Summary
|
||||
|
||||
@@ -82,7 +82,7 @@ path. Fixed by the commit whose message references `NotificationService-001`.
|
||||
|--|--|
|
||||
| Severity | High |
|
||||
| Category | Error handling & resilience |
|
||||
| Status | Open |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.NotificationService/NotificationDeliveryService.cs:157-167` |
|
||||
|
||||
**Description**
|
||||
@@ -95,7 +95,14 @@ Re-throw `OperationCanceledException`/`TaskCanceledException` when `cancellation
|
||||
|
||||
**Resolution**
|
||||
|
||||
_Unresolved._
|
||||
Resolved 2026-05-16 (commit `<pending>`). Classification was rewritten around a typed
|
||||
`ClassifySmtpError` helper: a caller-requested cancellation (`OperationCanceledException`/
|
||||
`TaskCanceledException` while `cancellationToken.IsCancellationRequested`) now propagates
|
||||
out of both `SendAsync` and `DeliverAsync` via dedicated `catch` filters instead of being
|
||||
buffered. The broad `IOException` catch-all was dropped — only MailKit's typed exceptions
|
||||
plus `SocketException`/`TimeoutException` are treated as transient. Regression tests
|
||||
`Send_CancellationRequested_PropagatesAndDoesNotBuffer` and
|
||||
`Send_TaskCanceledException_WithCancellation_Propagates`.
|
||||
|
||||
### NotificationService-003 — Error classification by substring matching on exception messages is fragile
|
||||
|
||||
@@ -103,7 +110,7 @@ _Unresolved._
|
||||
|--|--|
|
||||
| Severity | High |
|
||||
| Category | Error handling & resilience |
|
||||
| Status | Open |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.NotificationService/NotificationDeliveryService.cs:144-147`, `src/ScadaLink.NotificationService/NotificationDeliveryService.cs:163-166` |
|
||||
|
||||
**Description**
|
||||
@@ -116,7 +123,16 @@ Classify on MailKit's typed exceptions and `SmtpCommandException.StatusCode` (4x
|
||||
|
||||
**Resolution**
|
||||
|
||||
_Unresolved._
|
||||
Resolved 2026-05-16 (commit `<pending>`). All `ex.Message.Contains(...)` checks were
|
||||
removed. The new `ClassifySmtpError` helper inspects `SmtpCommandException.StatusCode`
|
||||
(numeric SMTP code: 4xx → transient, 5xx → permanent) and treats `SmtpProtocolException`,
|
||||
`ServiceNotConnectedException`, `SocketException` and `TimeoutException` as transient;
|
||||
anything else is `Unknown` and propagates unclassified rather than being guessed. The
|
||||
permanent-promotion `catch` block in `DeliverAsync` now keys off this classification.
|
||||
Regression tests `Send_Smtp5xxCommandException_ClassifiedPermanent`,
|
||||
`Send_Smtp4xxCommandException_ClassifiedTransientAndBuffered`,
|
||||
`Send_SmtpProtocolException_ClassifiedTransient`, and
|
||||
`Send_NonSmtpExceptionWith5xxLookalikeText_NotPromotedToPermanent`.
|
||||
|
||||
### NotificationService-004 — `DeliverAsync` constructs two SMTP clients and leaks the used one
|
||||
|
||||
@@ -124,7 +140,7 @@ _Unresolved._
|
||||
|--|--|
|
||||
| Severity | High |
|
||||
| Category | Performance & resource management |
|
||||
| Status | Open |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.NotificationService/NotificationDeliveryService.cs:118-119` |
|
||||
|
||||
**Description**
|
||||
@@ -143,7 +159,12 @@ Create exactly one client and dispose the one that is actually used:
|
||||
|
||||
**Resolution**
|
||||
|
||||
_Unresolved._
|
||||
Resolved 2026-05-16 (commit `<pending>`). `DeliverAsync` now invokes `_smtpClientFactory()`
|
||||
exactly once and disposes the client actually used via `using var disposable = smtp as
|
||||
IDisposable;`. The previous code created two `MailKitSmtpClientWrapper` instances per send
|
||||
and disposed the unused one while leaking the connected one. Regression test
|
||||
`Send_CreatesExactlyOneSmtpClient_AndDisposesIt` verifies the factory is invoked once and
|
||||
the resulting client is disposed.
|
||||
|
||||
### NotificationService-005 — Non-TLS path uses `SecureSocketOptions.Auto`, contradicting the requested mode
|
||||
|
||||
|
||||
Reference in New Issue
Block a user