test(sms): harden S3 adapter secret-redaction + coverage (review follow-up)
This commit is contained in:
+47
-1
@@ -72,7 +72,10 @@ public class SmsNotificationDeliveryAdapterTests
|
||||
|
||||
private SmsNotificationDeliveryAdapter CreateAdapter(HttpMessageHandler handler, SmsOptions? options = null)
|
||||
{
|
||||
_httpClientFactory.CreateClient(Arg.Any<string>()).Returns(_ => new HttpClient(handler));
|
||||
// Pin to the real named-client name so tests catch accidental renames.
|
||||
_httpClientFactory
|
||||
.CreateClient(SmsNotificationDeliveryAdapter.HttpClientName)
|
||||
.Returns(_ => new HttpClient(handler));
|
||||
return new SmsNotificationDeliveryAdapter(
|
||||
_repository,
|
||||
_httpClientFactory,
|
||||
@@ -145,6 +148,10 @@ public class SmsNotificationDeliveryAdapterTests
|
||||
Assert.Contains("To=", body);
|
||||
Assert.Contains("From=", body);
|
||||
Assert.Contains("Body=", body);
|
||||
|
||||
// The factory must be asked for the exact named client the adapter is documented
|
||||
// to use, not an arbitrary string.
|
||||
_httpClientFactory.Received(1).CreateClient(SmsNotificationDeliveryAdapter.HttpClientName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -360,6 +367,28 @@ public class SmsNotificationDeliveryAdapterTests
|
||||
Assert.Contains("incomplete", outcome.Error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Deliver_IncompleteConfig_NoFromAndNoMessagingServiceSid_ReturnsPermanent()
|
||||
{
|
||||
// AccountSid + AuthToken are present but BOTH FromNumber and MessagingServiceSid
|
||||
// are absent — the adapter cannot build a valid Twilio request. This is a
|
||||
// configuration error that cannot be resolved by retrying; expect Permanent.
|
||||
var config = new SmsConfiguration("AC_account_sid", "") // empty FromNumber
|
||||
{
|
||||
Id = 1,
|
||||
AuthToken = AuthToken,
|
||||
MessagingServiceSid = null, // no fallback either
|
||||
ApiBaseUrl = "https://fake-twilio.test",
|
||||
};
|
||||
SetupList(config: config);
|
||||
var adapter = CreateAdapter(ScriptedHttpMessageHandler.ForStatuses());
|
||||
|
||||
var outcome = await adapter.DeliverAsync(MakeNotification());
|
||||
|
||||
Assert.Equal(DeliveryResult.PermanentFailure, outcome.Result);
|
||||
Assert.Contains("incomplete", outcome.Error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Deliver_DefaultsToTwilioBaseUrl_WhenApiBaseUrlAbsent()
|
||||
{
|
||||
@@ -384,13 +413,30 @@ public class SmsNotificationDeliveryAdapterTests
|
||||
{
|
||||
SetupList();
|
||||
// A permanent failure (400) so the outcome carries an Error string.
|
||||
// NOTE: the HTTP-error detail string ("Twilio returned HTTP 400…") is built
|
||||
// without ever embedding the credential, so the primary guarantee here is
|
||||
// documentation of intent. The CredentialRedactor.Scrub call still runs as
|
||||
// defense-in-depth in case the detail format ever changes.
|
||||
var handler = ScriptedHttpMessageHandler.ForStatuses(HttpStatusCode.BadRequest);
|
||||
var adapter = CreateAdapter(handler);
|
||||
|
||||
var outcome = await adapter.DeliverAsync(MakeNotification());
|
||||
|
||||
Assert.NotNull(outcome.Error);
|
||||
|
||||
// Bare token must not appear.
|
||||
Assert.DoesNotContain(AuthToken, outcome.Error);
|
||||
|
||||
// The "AccountSid:AuthToken" composite (the Basic-auth credential before
|
||||
// base64 encoding) must not appear — tests the defense-in-depth Scrub.
|
||||
const string accountSid = "AC_account_sid";
|
||||
var composite = $"{accountSid}:{AuthToken}";
|
||||
Assert.DoesNotContain(composite, outcome.Error);
|
||||
|
||||
// The base64-encoded form of the composite must not appear either.
|
||||
var base64Credential = Convert.ToBase64String(
|
||||
System.Text.Encoding.ASCII.GetBytes(composite));
|
||||
Assert.DoesNotContain(base64Credential, outcome.Error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user