mirror of
https://github.com/bitwarden/server
synced 2025-12-26 05:03:18 +00:00
Refactor IntegrationHandlerResult to provide more detail around failures (#6736)
* Refactor IntegrationHandlerResult to provide more detail around failures * ServiceUnavailable now retryable, more explicit http status handling, more consistency with different handlers, additional xmldocs * Address PR feedback
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.Models.Data.EventIntegrations;
|
||||
|
||||
public class IntegrationHandlerResultTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public void Succeed_SetsSuccessTrue_CategoryNull(IntegrationMessage message)
|
||||
{
|
||||
var result = IntegrationHandlerResult.Succeed(message);
|
||||
|
||||
Assert.True(result.Success);
|
||||
Assert.Null(result.Category);
|
||||
Assert.Equal(message, result.Message);
|
||||
Assert.Null(result.FailureReason);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void Fail_WithCategory_SetsSuccessFalse_CategorySet(IntegrationMessage message)
|
||||
{
|
||||
var category = IntegrationFailureCategory.AuthenticationFailed;
|
||||
var failureReason = "Invalid credentials";
|
||||
|
||||
var result = IntegrationHandlerResult.Fail(message, category, failureReason);
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.Equal(category, result.Category);
|
||||
Assert.Equal(failureReason, result.FailureReason);
|
||||
Assert.Equal(message, result.Message);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void Fail_WithDelayUntil_SetsDelayUntilDate(IntegrationMessage message)
|
||||
{
|
||||
var delayUntil = DateTime.UtcNow.AddMinutes(5);
|
||||
|
||||
var result = IntegrationHandlerResult.Fail(
|
||||
message,
|
||||
IntegrationFailureCategory.RateLimited,
|
||||
"Rate limited",
|
||||
delayUntil
|
||||
);
|
||||
|
||||
Assert.Equal(delayUntil, result.DelayUntilDate);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void Retryable_RateLimited_ReturnsTrue(IntegrationMessage message)
|
||||
{
|
||||
var result = IntegrationHandlerResult.Fail(
|
||||
message,
|
||||
IntegrationFailureCategory.RateLimited,
|
||||
"Rate limited"
|
||||
);
|
||||
|
||||
Assert.True(result.Retryable);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void Retryable_TransientError_ReturnsTrue(IntegrationMessage message)
|
||||
{
|
||||
var result = IntegrationHandlerResult.Fail(
|
||||
message,
|
||||
IntegrationFailureCategory.TransientError,
|
||||
"Temporary network issue"
|
||||
);
|
||||
|
||||
Assert.True(result.Retryable);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void Retryable_AuthenticationFailed_ReturnsFalse(IntegrationMessage message)
|
||||
{
|
||||
var result = IntegrationHandlerResult.Fail(
|
||||
message,
|
||||
IntegrationFailureCategory.AuthenticationFailed,
|
||||
"Invalid token"
|
||||
);
|
||||
|
||||
Assert.False(result.Retryable);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void Retryable_ConfigurationError_ReturnsFalse(IntegrationMessage message)
|
||||
{
|
||||
var result = IntegrationHandlerResult.Fail(
|
||||
message,
|
||||
IntegrationFailureCategory.ConfigurationError,
|
||||
"Channel not found"
|
||||
);
|
||||
|
||||
Assert.False(result.Retryable);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void Retryable_ServiceUnavailable_ReturnsTrue(IntegrationMessage message)
|
||||
{
|
||||
var result = IntegrationHandlerResult.Fail(
|
||||
message,
|
||||
IntegrationFailureCategory.ServiceUnavailable,
|
||||
"Service is down"
|
||||
);
|
||||
|
||||
Assert.True(result.Retryable);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void Retryable_PermanentFailure_ReturnsFalse(IntegrationMessage message)
|
||||
{
|
||||
var result = IntegrationHandlerResult.Fail(
|
||||
message,
|
||||
IntegrationFailureCategory.PermanentFailure,
|
||||
"Permanent failure"
|
||||
);
|
||||
|
||||
Assert.False(result.Retryable);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void Retryable_SuccessCase_ReturnsFalse(IntegrationMessage message)
|
||||
{
|
||||
var result = IntegrationHandlerResult.Succeed(message);
|
||||
|
||||
Assert.False(result.Retryable);
|
||||
}
|
||||
}
|
||||
@@ -78,8 +78,10 @@ public class AzureServiceBusIntegrationListenerServiceTests
|
||||
var sutProvider = GetSutProvider();
|
||||
message.RetryCount = 0;
|
||||
|
||||
var result = new IntegrationHandlerResult(false, message);
|
||||
result.Retryable = false;
|
||||
var result = IntegrationHandlerResult.Fail(
|
||||
message: message,
|
||||
category: IntegrationFailureCategory.AuthenticationFailed, // NOT retryable
|
||||
failureReason: "403");
|
||||
_handler.HandleAsync(Arg.Any<string>()).Returns(result);
|
||||
|
||||
var expected = IntegrationMessage<WebhookIntegrationConfiguration>.FromJson(message.ToJson());
|
||||
@@ -89,6 +91,12 @@ public class AzureServiceBusIntegrationListenerServiceTests
|
||||
|
||||
await _handler.Received(1).HandleAsync(Arg.Is(expected.ToJson()));
|
||||
await _serviceBusService.DidNotReceiveWithAnyArgs().PublishToRetryAsync(Arg.Any<IIntegrationMessage>());
|
||||
_logger.Received().Log(
|
||||
LogLevel.Warning,
|
||||
Arg.Any<EventId>(),
|
||||
Arg.Is<object>(o => (o.ToString() ?? "").Contains("Integration failure - non-recoverable error or max retries exceeded.")),
|
||||
Arg.Any<Exception?>(),
|
||||
Arg.Any<Func<object, Exception?, string>>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
@@ -96,9 +104,10 @@ public class AzureServiceBusIntegrationListenerServiceTests
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
message.RetryCount = _config.MaxRetries;
|
||||
var result = new IntegrationHandlerResult(false, message);
|
||||
result.Retryable = true;
|
||||
|
||||
var result = IntegrationHandlerResult.Fail(
|
||||
message: message,
|
||||
category: IntegrationFailureCategory.TransientError, // Retryable
|
||||
failureReason: "403");
|
||||
_handler.HandleAsync(Arg.Any<string>()).Returns(result);
|
||||
|
||||
var expected = IntegrationMessage<WebhookIntegrationConfiguration>.FromJson(message.ToJson());
|
||||
@@ -108,6 +117,12 @@ public class AzureServiceBusIntegrationListenerServiceTests
|
||||
|
||||
await _handler.Received(1).HandleAsync(Arg.Is(expected.ToJson()));
|
||||
await _serviceBusService.DidNotReceiveWithAnyArgs().PublishToRetryAsync(Arg.Any<IIntegrationMessage>());
|
||||
_logger.Received().Log(
|
||||
LogLevel.Warning,
|
||||
Arg.Any<EventId>(),
|
||||
Arg.Is<object>(o => (o.ToString() ?? "").Contains("Integration failure - non-recoverable error or max retries exceeded.")),
|
||||
Arg.Any<Exception?>(),
|
||||
Arg.Any<Func<object, Exception?, string>>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
@@ -116,8 +131,10 @@ public class AzureServiceBusIntegrationListenerServiceTests
|
||||
var sutProvider = GetSutProvider();
|
||||
message.RetryCount = 0;
|
||||
|
||||
var result = new IntegrationHandlerResult(false, message);
|
||||
result.Retryable = true;
|
||||
var result = IntegrationHandlerResult.Fail(
|
||||
message: message,
|
||||
category: IntegrationFailureCategory.TransientError, // Retryable
|
||||
failureReason: "403");
|
||||
_handler.HandleAsync(Arg.Any<string>()).Returns(result);
|
||||
|
||||
var expected = IntegrationMessage<WebhookIntegrationConfiguration>.FromJson(message.ToJson());
|
||||
@@ -133,7 +150,7 @@ public class AzureServiceBusIntegrationListenerServiceTests
|
||||
public async Task HandleMessageAsync_SuccessfulResult_Succeeds(IntegrationMessage<WebhookIntegrationConfiguration> message)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
var result = new IntegrationHandlerResult(true, message);
|
||||
var result = IntegrationHandlerResult.Succeed(message);
|
||||
_handler.HandleAsync(Arg.Any<string>()).Returns(result);
|
||||
|
||||
var expected = IntegrationMessage<WebhookIntegrationConfiguration>.FromJson(message.ToJson());
|
||||
@@ -156,7 +173,7 @@ public class AzureServiceBusIntegrationListenerServiceTests
|
||||
_logger.Received(1).Log(
|
||||
LogLevel.Error,
|
||||
Arg.Any<EventId>(),
|
||||
Arg.Any<object>(),
|
||||
Arg.Is<object>(o => (o.ToString() ?? "").Contains("Unhandled error processing ASB message")),
|
||||
Arg.Any<Exception>(),
|
||||
Arg.Any<Func<object, Exception?, string>>());
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ public class DatadogIntegrationHandlerTests
|
||||
|
||||
Assert.True(result.Success);
|
||||
Assert.Equal(result.Message, message);
|
||||
Assert.Empty(result.FailureReason);
|
||||
Assert.Null(result.FailureReason);
|
||||
|
||||
sutProvider.GetDependency<IHttpClientFactory>().Received(1).CreateClient(
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(DatadogIntegrationHandler.HttpClientName))
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||
using System.Net;
|
||||
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Services;
|
||||
using Xunit;
|
||||
@@ -7,7 +8,6 @@ namespace Bit.Core.Test.Services;
|
||||
|
||||
public class IntegrationHandlerTests
|
||||
{
|
||||
|
||||
[Fact]
|
||||
public async Task HandleAsync_ConvertsJsonToTypedIntegrationMessage()
|
||||
{
|
||||
@@ -33,13 +33,113 @@ public class IntegrationHandlerTests
|
||||
Assert.Equal(expected.IntegrationType, typedResult.IntegrationType);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(HttpStatusCode.Unauthorized)]
|
||||
[InlineData(HttpStatusCode.Forbidden)]
|
||||
public void ClassifyHttpStatusCode_AuthenticationFailed(HttpStatusCode code)
|
||||
{
|
||||
Assert.Equal(
|
||||
IntegrationFailureCategory.AuthenticationFailed,
|
||||
TestIntegrationHandler.Classify(code));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(HttpStatusCode.NotFound)]
|
||||
[InlineData(HttpStatusCode.Gone)]
|
||||
[InlineData(HttpStatusCode.MovedPermanently)]
|
||||
[InlineData(HttpStatusCode.TemporaryRedirect)]
|
||||
[InlineData(HttpStatusCode.PermanentRedirect)]
|
||||
public void ClassifyHttpStatusCode_ConfigurationError(HttpStatusCode code)
|
||||
{
|
||||
Assert.Equal(
|
||||
IntegrationFailureCategory.ConfigurationError,
|
||||
TestIntegrationHandler.Classify(code));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClassifyHttpStatusCode_TooManyRequests_IsRateLimited()
|
||||
{
|
||||
Assert.Equal(
|
||||
IntegrationFailureCategory.RateLimited,
|
||||
TestIntegrationHandler.Classify(HttpStatusCode.TooManyRequests));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClassifyHttpStatusCode_RequestTimeout_IsTransient()
|
||||
{
|
||||
Assert.Equal(
|
||||
IntegrationFailureCategory.TransientError,
|
||||
TestIntegrationHandler.Classify(HttpStatusCode.RequestTimeout));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(HttpStatusCode.InternalServerError)]
|
||||
[InlineData(HttpStatusCode.BadGateway)]
|
||||
[InlineData(HttpStatusCode.GatewayTimeout)]
|
||||
public void ClassifyHttpStatusCode_Common5xx_AreTransient(HttpStatusCode code)
|
||||
{
|
||||
Assert.Equal(
|
||||
IntegrationFailureCategory.TransientError,
|
||||
TestIntegrationHandler.Classify(code));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClassifyHttpStatusCode_ServiceUnavailable_IsServiceUnavailable()
|
||||
{
|
||||
Assert.Equal(
|
||||
IntegrationFailureCategory.ServiceUnavailable,
|
||||
TestIntegrationHandler.Classify(HttpStatusCode.ServiceUnavailable));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClassifyHttpStatusCode_NotImplemented_IsPermanentFailure()
|
||||
{
|
||||
Assert.Equal(
|
||||
IntegrationFailureCategory.PermanentFailure,
|
||||
TestIntegrationHandler.Classify(HttpStatusCode.NotImplemented));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FClassifyHttpStatusCode_Unhandled3xx_IsConfigurationError()
|
||||
{
|
||||
Assert.Equal(
|
||||
IntegrationFailureCategory.ConfigurationError,
|
||||
TestIntegrationHandler.Classify(HttpStatusCode.Found));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClassifyHttpStatusCode_Unhandled4xx_IsConfigurationError()
|
||||
{
|
||||
Assert.Equal(
|
||||
IntegrationFailureCategory.ConfigurationError,
|
||||
TestIntegrationHandler.Classify(HttpStatusCode.BadRequest));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClassifyHttpStatusCode_Unhandled5xx_IsServiceUnavailable()
|
||||
{
|
||||
Assert.Equal(
|
||||
IntegrationFailureCategory.ServiceUnavailable,
|
||||
TestIntegrationHandler.Classify(HttpStatusCode.HttpVersionNotSupported));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClassifyHttpStatusCode_UnknownCode_DefaultsToServiceUnavailable()
|
||||
{
|
||||
// cast an out-of-range value to ensure default path is stable
|
||||
Assert.Equal(
|
||||
IntegrationFailureCategory.ServiceUnavailable,
|
||||
TestIntegrationHandler.Classify((HttpStatusCode)799));
|
||||
}
|
||||
|
||||
private class TestIntegrationHandler : IntegrationHandlerBase<WebhookIntegrationConfigurationDetails>
|
||||
{
|
||||
public override Task<IntegrationHandlerResult> HandleAsync(
|
||||
IntegrationMessage<WebhookIntegrationConfigurationDetails> message)
|
||||
{
|
||||
var result = new IntegrationHandlerResult(success: true, message: message);
|
||||
return Task.FromResult(result);
|
||||
return Task.FromResult(IntegrationHandlerResult.Succeed(message: message));
|
||||
}
|
||||
|
||||
public static IntegrationFailureCategory Classify(HttpStatusCode code) => ClassifyHttpStatusCode(code);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,8 +86,10 @@ public class RabbitMqIntegrationListenerServiceTests
|
||||
new BasicProperties(),
|
||||
body: Encoding.UTF8.GetBytes(message.ToJson())
|
||||
);
|
||||
var result = new IntegrationHandlerResult(false, message);
|
||||
result.Retryable = false;
|
||||
var result = IntegrationHandlerResult.Fail(
|
||||
message: message,
|
||||
category: IntegrationFailureCategory.AuthenticationFailed, // NOT retryable
|
||||
failureReason: "403");
|
||||
_handler.HandleAsync(Arg.Any<string>()).Returns(result);
|
||||
|
||||
var expected = IntegrationMessage<WebhookIntegrationConfiguration>.FromJson(message.ToJson());
|
||||
@@ -105,7 +107,7 @@ public class RabbitMqIntegrationListenerServiceTests
|
||||
_logger.Received().Log(
|
||||
LogLevel.Warning,
|
||||
Arg.Any<EventId>(),
|
||||
Arg.Is<object>(o => (o.ToString() ?? "").Contains("Non-retryable failure")),
|
||||
Arg.Is<object>(o => (o.ToString() ?? "").Contains("Integration failure - non-retryable.")),
|
||||
Arg.Any<Exception?>(),
|
||||
Arg.Any<Func<object, Exception?, string>>());
|
||||
|
||||
@@ -133,8 +135,10 @@ public class RabbitMqIntegrationListenerServiceTests
|
||||
new BasicProperties(),
|
||||
body: Encoding.UTF8.GetBytes(message.ToJson())
|
||||
);
|
||||
var result = new IntegrationHandlerResult(false, message);
|
||||
result.Retryable = true;
|
||||
var result = IntegrationHandlerResult.Fail(
|
||||
message: message,
|
||||
category: IntegrationFailureCategory.TransientError, // Retryable
|
||||
failureReason: "403");
|
||||
_handler.HandleAsync(Arg.Any<string>()).Returns(result);
|
||||
|
||||
var expected = IntegrationMessage<WebhookIntegrationConfiguration>.FromJson(message.ToJson());
|
||||
@@ -151,7 +155,7 @@ public class RabbitMqIntegrationListenerServiceTests
|
||||
_logger.Received().Log(
|
||||
LogLevel.Warning,
|
||||
Arg.Any<EventId>(),
|
||||
Arg.Is<object>(o => (o.ToString() ?? "").Contains("Max retry attempts reached")),
|
||||
Arg.Is<object>(o => (o.ToString() ?? "").Contains("Integration failure - max retries exceeded.")),
|
||||
Arg.Any<Exception?>(),
|
||||
Arg.Any<Func<object, Exception?, string>>());
|
||||
|
||||
@@ -179,9 +183,10 @@ public class RabbitMqIntegrationListenerServiceTests
|
||||
new BasicProperties(),
|
||||
body: Encoding.UTF8.GetBytes(message.ToJson())
|
||||
);
|
||||
var result = new IntegrationHandlerResult(false, message);
|
||||
result.Retryable = true;
|
||||
result.DelayUntilDate = _now.AddMinutes(1);
|
||||
var result = IntegrationHandlerResult.Fail(
|
||||
message: message,
|
||||
category: IntegrationFailureCategory.TransientError, // Retryable
|
||||
failureReason: "403");
|
||||
_handler.HandleAsync(Arg.Any<string>()).Returns(result);
|
||||
|
||||
var expected = IntegrationMessage<WebhookIntegrationConfiguration>.FromJson(message.ToJson());
|
||||
@@ -220,7 +225,7 @@ public class RabbitMqIntegrationListenerServiceTests
|
||||
new BasicProperties(),
|
||||
body: Encoding.UTF8.GetBytes(message.ToJson())
|
||||
);
|
||||
var result = new IntegrationHandlerResult(true, message);
|
||||
var result = IntegrationHandlerResult.Succeed(message);
|
||||
_handler.HandleAsync(Arg.Any<string>()).Returns(result);
|
||||
|
||||
await sutProvider.Sut.ProcessReceivedMessageAsync(eventArgs, cancellationToken);
|
||||
|
||||
@@ -110,7 +110,7 @@ public class SlackIntegrationHandlerTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HandleAsync_NullResponse_ReturnsNonRetryableFailure()
|
||||
public async Task HandleAsync_NullResponse_ReturnsRetryableFailure()
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
var message = new IntegrationMessage<SlackIntegrationConfigurationDetails>()
|
||||
@@ -126,7 +126,7 @@ public class SlackIntegrationHandlerTests
|
||||
var result = await sutProvider.Sut.HandleAsync(message);
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.False(result.Retryable);
|
||||
Assert.True(result.Retryable); // Null response is classified as TransientError (retryable)
|
||||
Assert.Equal("Slack response was null", result.FailureReason);
|
||||
Assert.Equal(result.Message, message);
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||
using System.Text.Json;
|
||||
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
@@ -42,9 +43,77 @@ public class TeamsIntegrationHandlerTests
|
||||
);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleAsync_ArgumentException_ReturnsConfigurationError(IntegrationMessage<TeamsIntegrationConfigurationDetails> message)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
message.Configuration = new TeamsIntegrationConfigurationDetails(_channelId, _serviceUrl);
|
||||
|
||||
sutProvider.GetDependency<ITeamsService>()
|
||||
.SendMessageToChannelAsync(Arg.Any<Uri>(), Arg.Any<string>(), Arg.Any<string>())
|
||||
.ThrowsAsync(new ArgumentException("argument error"));
|
||||
var result = await sutProvider.Sut.HandleAsync(message);
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.Equal(IntegrationFailureCategory.ConfigurationError, result.Category);
|
||||
Assert.False(result.Retryable);
|
||||
Assert.Equal(result.Message, message);
|
||||
|
||||
await sutProvider.GetDependency<ITeamsService>().Received(1).SendMessageToChannelAsync(
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(_serviceUrl)),
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(_channelId)),
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(message.RenderedTemplate))
|
||||
);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleAsync_HttpExceptionNonRetryable_ReturnsFalseAndNotRetryable(IntegrationMessage<TeamsIntegrationConfigurationDetails> message)
|
||||
public async Task HandleAsync_JsonException_ReturnsPermanentFailure(IntegrationMessage<TeamsIntegrationConfigurationDetails> message)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
message.Configuration = new TeamsIntegrationConfigurationDetails(_channelId, _serviceUrl);
|
||||
|
||||
sutProvider.GetDependency<ITeamsService>()
|
||||
.SendMessageToChannelAsync(Arg.Any<Uri>(), Arg.Any<string>(), Arg.Any<string>())
|
||||
.ThrowsAsync(new JsonException("JSON error"));
|
||||
var result = await sutProvider.Sut.HandleAsync(message);
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.Equal(IntegrationFailureCategory.PermanentFailure, result.Category);
|
||||
Assert.False(result.Retryable);
|
||||
Assert.Equal(result.Message, message);
|
||||
|
||||
await sutProvider.GetDependency<ITeamsService>().Received(1).SendMessageToChannelAsync(
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(_serviceUrl)),
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(_channelId)),
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(message.RenderedTemplate))
|
||||
);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleAsync_UriFormatException_ReturnsConfigurationError(IntegrationMessage<TeamsIntegrationConfigurationDetails> message)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
message.Configuration = new TeamsIntegrationConfigurationDetails(_channelId, _serviceUrl);
|
||||
|
||||
sutProvider.GetDependency<ITeamsService>()
|
||||
.SendMessageToChannelAsync(Arg.Any<Uri>(), Arg.Any<string>(), Arg.Any<string>())
|
||||
.ThrowsAsync(new UriFormatException("Bad URI"));
|
||||
var result = await sutProvider.Sut.HandleAsync(message);
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.Equal(IntegrationFailureCategory.ConfigurationError, result.Category);
|
||||
Assert.False(result.Retryable);
|
||||
Assert.Equal(result.Message, message);
|
||||
|
||||
await sutProvider.GetDependency<ITeamsService>().Received(1).SendMessageToChannelAsync(
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(_serviceUrl)),
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(_channelId)),
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(message.RenderedTemplate))
|
||||
);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleAsync_HttpExceptionForbidden_ReturnsAuthenticationFailed(IntegrationMessage<TeamsIntegrationConfigurationDetails> message)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
message.Configuration = new TeamsIntegrationConfigurationDetails(_channelId, _serviceUrl);
|
||||
@@ -62,6 +131,7 @@ public class TeamsIntegrationHandlerTests
|
||||
var result = await sutProvider.Sut.HandleAsync(message);
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.Equal(IntegrationFailureCategory.AuthenticationFailed, result.Category);
|
||||
Assert.False(result.Retryable);
|
||||
Assert.Equal(result.Message, message);
|
||||
|
||||
@@ -73,7 +143,7 @@ public class TeamsIntegrationHandlerTests
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleAsync_HttpExceptionRetryable_ReturnsFalseAndRetryable(IntegrationMessage<TeamsIntegrationConfigurationDetails> message)
|
||||
public async Task HandleAsync_HttpExceptionTooManyRequests_ReturnsRateLimited(IntegrationMessage<TeamsIntegrationConfigurationDetails> message)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
message.Configuration = new TeamsIntegrationConfigurationDetails(_channelId, _serviceUrl);
|
||||
@@ -92,6 +162,7 @@ public class TeamsIntegrationHandlerTests
|
||||
var result = await sutProvider.Sut.HandleAsync(message);
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.Equal(IntegrationFailureCategory.RateLimited, result.Category);
|
||||
Assert.True(result.Retryable);
|
||||
Assert.Equal(result.Message, message);
|
||||
|
||||
@@ -103,7 +174,7 @@ public class TeamsIntegrationHandlerTests
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleAsync_UnknownException_ReturnsFalseAndNotRetryable(IntegrationMessage<TeamsIntegrationConfigurationDetails> message)
|
||||
public async Task HandleAsync_UnknownException_ReturnsTransientError(IntegrationMessage<TeamsIntegrationConfigurationDetails> message)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
message.Configuration = new TeamsIntegrationConfigurationDetails(_channelId, _serviceUrl);
|
||||
@@ -114,7 +185,8 @@ public class TeamsIntegrationHandlerTests
|
||||
var result = await sutProvider.Sut.HandleAsync(message);
|
||||
|
||||
Assert.False(result.Success);
|
||||
Assert.False(result.Retryable);
|
||||
Assert.Equal(IntegrationFailureCategory.TransientError, result.Category);
|
||||
Assert.True(result.Retryable);
|
||||
Assert.Equal(result.Message, message);
|
||||
|
||||
await sutProvider.GetDependency<ITeamsService>().Received(1).SendMessageToChannelAsync(
|
||||
|
||||
@@ -51,7 +51,7 @@ public class WebhookIntegrationHandlerTests
|
||||
|
||||
Assert.True(result.Success);
|
||||
Assert.Equal(result.Message, message);
|
||||
Assert.Empty(result.FailureReason);
|
||||
Assert.Null(result.FailureReason);
|
||||
|
||||
sutProvider.GetDependency<IHttpClientFactory>().Received(1).CreateClient(
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(WebhookIntegrationHandler.HttpClientName))
|
||||
@@ -79,7 +79,7 @@ public class WebhookIntegrationHandlerTests
|
||||
|
||||
Assert.True(result.Success);
|
||||
Assert.Equal(result.Message, message);
|
||||
Assert.Empty(result.FailureReason);
|
||||
Assert.Null(result.FailureReason);
|
||||
|
||||
sutProvider.GetDependency<IHttpClientFactory>().Received(1).CreateClient(
|
||||
Arg.Is(AssertHelper.AssertPropertyEqual(WebhookIntegrationHandler.HttpClientName))
|
||||
|
||||
Reference in New Issue
Block a user