mirror of
https://github.com/bitwarden/server.git
synced 2025-07-04 09:32:48 -05:00
SlackService improvements, testing, integration configurations
This commit is contained in:
@ -2,7 +2,16 @@
|
|||||||
|
|
||||||
public class SlackConfiguration
|
public class SlackConfiguration
|
||||||
{
|
{
|
||||||
public string Token { get; set; } = string.Empty;
|
public SlackConfiguration()
|
||||||
public List<string> Channels { get; set; } = new();
|
{
|
||||||
public List<string> UserEmails { get; set; } = new();
|
}
|
||||||
|
|
||||||
|
public SlackConfiguration(string channelId, string token)
|
||||||
|
{
|
||||||
|
ChannelId = channelId;
|
||||||
|
Token = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Token { get; set; } = string.Empty;
|
||||||
|
public string ChannelId { get; set; } = string.Empty;
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,8 @@ namespace Bit.Core.Repositories;
|
|||||||
|
|
||||||
public interface IOrganizationIntegrationConfigurationRepository
|
public interface IOrganizationIntegrationConfigurationRepository
|
||||||
{
|
{
|
||||||
Task<IntegrationConfiguration<T>?> GetConfigurationAsync<T>(Guid organizationId, IntegrationType integrationType, EventType eventType);
|
Task<List<IntegrationConfiguration<T>>> GetConfigurationsAsync<T>(IntegrationType integrationType,
|
||||||
|
Guid organizationId, EventType eventType);
|
||||||
Task<IEnumerable<IntegrationConfiguration<T>>> GetAllConfigurationsAsync<T>(Guid organizationId);
|
Task<IEnumerable<IntegrationConfiguration<T>>> GetAllConfigurationsAsync<T>(Guid organizationId);
|
||||||
Task AddConfigurationAsync<T>(Guid organizationId, IntegrationType integrationType, EventType eventType, IntegrationConfiguration<T> configuration);
|
Task AddConfigurationAsync<T>(Guid organizationId, IntegrationType integrationType, EventType eventType, IntegrationConfiguration<T> configuration);
|
||||||
Task UpdateConfigurationAsync<T>(IntegrationConfiguration<T> configuration);
|
Task UpdateConfigurationAsync<T>(IntegrationConfiguration<T> configuration);
|
||||||
|
@ -4,53 +4,39 @@ using Bit.Core.Settings;
|
|||||||
|
|
||||||
namespace Bit.Core.Repositories;
|
namespace Bit.Core.Repositories;
|
||||||
|
|
||||||
#nullable enable
|
|
||||||
|
|
||||||
public class OrganizationIntegrationConfigurationRepository(GlobalSettings globalSettings)
|
public class OrganizationIntegrationConfigurationRepository(GlobalSettings globalSettings)
|
||||||
: IOrganizationIntegrationConfigurationRepository
|
: IOrganizationIntegrationConfigurationRepository
|
||||||
{
|
{
|
||||||
private readonly string _slackToken = globalSettings.EventLogging.SlackToken;
|
public async Task<List<IntegrationConfiguration<T>>> GetConfigurationsAsync<T>(IntegrationType integrationType,
|
||||||
private readonly string _slackUserEmail = globalSettings.EventLogging.SlackUserEmail;
|
|
||||||
private readonly string _webhookUrl = globalSettings.EventLogging.WebhookUrl;
|
|
||||||
|
|
||||||
public async Task<IntegrationConfiguration<T>?> GetConfigurationAsync<T>(
|
|
||||||
Guid organizationId,
|
Guid organizationId,
|
||||||
IntegrationType integrationType,
|
|
||||||
EventType eventType)
|
EventType eventType)
|
||||||
{
|
{
|
||||||
|
var configurations = new List<IntegrationConfiguration<T>>();
|
||||||
switch (integrationType)
|
switch (integrationType)
|
||||||
{
|
{
|
||||||
case IntegrationType.Slack:
|
case IntegrationType.Slack:
|
||||||
if (string.IsNullOrWhiteSpace(_slackToken) || string.IsNullOrWhiteSpace(_slackUserEmail))
|
foreach (var configuration in globalSettings.EventLogging.SlackConfigurations)
|
||||||
{
|
{
|
||||||
return null;
|
configurations.Add(new IntegrationConfiguration<SlackConfiguration>
|
||||||
|
{
|
||||||
|
Configuration = configuration,
|
||||||
|
Template = "This is a test of the new Slack integration, #UserId#, #Type#, #Date#"
|
||||||
|
} as IntegrationConfiguration<T>);
|
||||||
}
|
}
|
||||||
return new IntegrationConfiguration<SlackConfiguration>()
|
break;
|
||||||
{
|
|
||||||
Configuration = new SlackConfiguration
|
|
||||||
{
|
|
||||||
Token = _slackToken,
|
|
||||||
Channels = new List<string> { },
|
|
||||||
UserEmails = new List<string> { _slackUserEmail }
|
|
||||||
},
|
|
||||||
Template = "This is a test of the new Slack integration"
|
|
||||||
} as IntegrationConfiguration<T>;
|
|
||||||
case IntegrationType.Webhook:
|
case IntegrationType.Webhook:
|
||||||
if (string.IsNullOrWhiteSpace(_webhookUrl))
|
foreach (var configuration in globalSettings.EventLogging.WebhookConfigurations)
|
||||||
{
|
{
|
||||||
return null;
|
configurations.Add(new IntegrationConfiguration<WebhookConfiguration>
|
||||||
}
|
|
||||||
return new IntegrationConfiguration<WebhookConfiguration>()
|
|
||||||
{
|
{
|
||||||
Configuration = new WebhookConfiguration()
|
Configuration = configuration,
|
||||||
{
|
|
||||||
Url = _webhookUrl,
|
|
||||||
},
|
|
||||||
Template = "{ \"Date\": \"#Date#\", \"Type\": \"#Type#\", \"UserId\": \"#UserId#\" }"
|
Template = "{ \"Date\": \"#Date#\", \"Type\": \"#Type#\", \"UserId\": \"#UserId#\" }"
|
||||||
} as IntegrationConfiguration<T>;
|
} as IntegrationConfiguration<T>);
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return configurations;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IEnumerable<IntegrationConfiguration<T>>> GetAllConfigurationsAsync<T>(Guid organizationId) => throw new NotImplementedException();
|
public async Task<IEnumerable<IntegrationConfiguration<T>>> GetAllConfigurationsAsync<T>(Guid organizationId) => throw new NotImplementedException();
|
||||||
|
9
src/Core/AdminConsole/Services/ISlackService.cs
Normal file
9
src/Core/AdminConsole/Services/ISlackService.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace Bit.Core.Services;
|
||||||
|
|
||||||
|
public interface ISlackService
|
||||||
|
{
|
||||||
|
Task<string> GetChannelIdAsync(string token, string channelName);
|
||||||
|
Task<List<string>> GetChannelIdsAsync(string token, List<string> channelNames);
|
||||||
|
Task<string> GetDmChannelByEmailAsync(string token, string email);
|
||||||
|
Task SendSlackMessageByChannelId(string token, string message, string channelId);
|
||||||
|
}
|
@ -7,23 +7,23 @@ namespace Bit.Core.Services;
|
|||||||
|
|
||||||
public class SlackEventHandler(
|
public class SlackEventHandler(
|
||||||
IOrganizationIntegrationConfigurationRepository configurationRepository,
|
IOrganizationIntegrationConfigurationRepository configurationRepository,
|
||||||
SlackMessageSender slackMessageSender
|
ISlackService slackService
|
||||||
) : IEventMessageHandler
|
) : IEventMessageHandler
|
||||||
{
|
{
|
||||||
public async Task HandleEventAsync(EventMessage eventMessage)
|
public async Task HandleEventAsync(EventMessage eventMessage)
|
||||||
{
|
{
|
||||||
Guid organizationId = eventMessage.OrganizationId ?? Guid.NewGuid();
|
var organizationId = eventMessage.OrganizationId ?? Guid.NewGuid();
|
||||||
|
var configurations = await configurationRepository.GetConfigurationsAsync<SlackConfiguration>(
|
||||||
var configuration = await configurationRepository.GetConfigurationAsync<SlackConfiguration>(
|
|
||||||
organizationId,
|
|
||||||
IntegrationType.Slack,
|
IntegrationType.Slack,
|
||||||
eventMessage.Type);
|
organizationId, eventMessage.Type
|
||||||
if (configuration is not null)
|
);
|
||||||
|
|
||||||
|
foreach (var configuration in configurations)
|
||||||
{
|
{
|
||||||
await slackMessageSender.SendDirectMessageByEmailAsync(
|
await slackService.SendSlackMessageByChannelId(
|
||||||
configuration.Configuration.Token,
|
configuration.Configuration.Token,
|
||||||
configuration.Template,
|
TemplateProcessor.ReplaceTokens(configuration.Template, eventMessage),
|
||||||
configuration.Configuration.UserEmails.First()
|
configuration.Configuration.ChannelId
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,77 +0,0 @@
|
|||||||
using System.Net.Http.Headers;
|
|
||||||
using System.Net.Http.Json;
|
|
||||||
using System.Text.Json;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
|
|
||||||
namespace Bit.Core.Services;
|
|
||||||
|
|
||||||
public class SlackMessageSender(
|
|
||||||
IHttpClientFactory httpClientFactory,
|
|
||||||
ILogger<SlackMessageSender> logger)
|
|
||||||
{
|
|
||||||
private HttpClient _httpClient = httpClientFactory.CreateClient(HttpClientName);
|
|
||||||
|
|
||||||
public const string HttpClientName = "SlackMessageSenderHttpClient";
|
|
||||||
|
|
||||||
public async Task SendDirectMessageByEmailAsync(string token, string message, string email)
|
|
||||||
{
|
|
||||||
var userId = await UserIdByEmail(token, email);
|
|
||||||
|
|
||||||
if (userId is not null)
|
|
||||||
{
|
|
||||||
await SendSlackDirectMessageByUserId(token, message, userId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<string> UserIdByEmail(string token, string email)
|
|
||||||
{
|
|
||||||
var request = new HttpRequestMessage(HttpMethod.Get, $"https://slack.com/api/users.lookupByEmail?email={email}");
|
|
||||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
|
|
||||||
var response = await _httpClient.SendAsync(request);
|
|
||||||
var content = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
|
|
||||||
var root = content.RootElement;
|
|
||||||
|
|
||||||
if (root.GetProperty("ok").GetBoolean())
|
|
||||||
{
|
|
||||||
return root.GetProperty("user").GetProperty("id").GetString();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
logger.LogError("Error retrieving slack userId: " + root.GetProperty("error").GetString());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task SendSlackDirectMessageByUserId(string token, string message, string userId)
|
|
||||||
{
|
|
||||||
var channelId = await OpenDmChannel(token, userId);
|
|
||||||
|
|
||||||
var payload = JsonContent.Create(new { channel = channelId, text = message });
|
|
||||||
var request = new HttpRequestMessage(HttpMethod.Post, "https://slack.com/api/chat.postMessage");
|
|
||||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
|
|
||||||
request.Content = payload;
|
|
||||||
|
|
||||||
await _httpClient.SendAsync(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<string> OpenDmChannel(string token, string userId)
|
|
||||||
{
|
|
||||||
var payload = JsonContent.Create(new { users = userId });
|
|
||||||
var request = new HttpRequestMessage(HttpMethod.Post, "https://slack.com/api/conversations.open");
|
|
||||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
|
|
||||||
request.Content = payload;
|
|
||||||
var response = await _httpClient.SendAsync(request);
|
|
||||||
var content = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
|
|
||||||
var root = content.RootElement;
|
|
||||||
|
|
||||||
if (root.GetProperty("ok").GetBoolean())
|
|
||||||
{
|
|
||||||
return content.RootElement.GetProperty("channel").GetProperty("id").GetString();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
logger.LogError("Error opening DM channel: " + root.GetProperty("error").GetString());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
143
src/Core/AdminConsole/Services/Implementations/SlackService.cs
Normal file
143
src/Core/AdminConsole/Services/Implementations/SlackService.cs
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
using System.Net.Http.Headers;
|
||||||
|
using System.Net.Http.Json;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Web;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace Bit.Core.Services;
|
||||||
|
|
||||||
|
public class SlackService(
|
||||||
|
IHttpClientFactory httpClientFactory,
|
||||||
|
ILogger<SlackService> logger) : ISlackService
|
||||||
|
{
|
||||||
|
private readonly HttpClient _httpClient = httpClientFactory.CreateClient(HttpClientName);
|
||||||
|
|
||||||
|
public const string HttpClientName = "SlackServiceHttpClient";
|
||||||
|
|
||||||
|
public async Task<string> GetChannelIdAsync(string token, string channelName)
|
||||||
|
{
|
||||||
|
return (await GetChannelIdsAsync(token, new List<string> { channelName }))?.FirstOrDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<string>> GetChannelIdsAsync(string token, List<string> channelNames)
|
||||||
|
{
|
||||||
|
var matchingChannelIds = new List<string>();
|
||||||
|
var baseUrl = "https://slack.com/api/conversations.list";
|
||||||
|
var nextCursor = string.Empty;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
var uriBuilder = new UriBuilder(baseUrl);
|
||||||
|
var queryParameters = HttpUtility.ParseQueryString(uriBuilder.Query);
|
||||||
|
|
||||||
|
queryParameters["types"] = "public_channel,private_channel";
|
||||||
|
queryParameters["limit"] = "1000";
|
||||||
|
if (!string.IsNullOrEmpty(nextCursor))
|
||||||
|
{
|
||||||
|
queryParameters["cursor"] = nextCursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
uriBuilder.Query = queryParameters.ToString();
|
||||||
|
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Get, uriBuilder.Uri);
|
||||||
|
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
|
||||||
|
|
||||||
|
var response = await _httpClient.SendAsync(request);
|
||||||
|
var jsonResponse = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
|
||||||
|
var root = jsonResponse.RootElement;
|
||||||
|
if (root.GetProperty("ok").GetBoolean())
|
||||||
|
{
|
||||||
|
var channelList = root.GetProperty("channels");
|
||||||
|
|
||||||
|
foreach (var channel in channelList.EnumerateArray())
|
||||||
|
{
|
||||||
|
if (channelNames.Contains(channel.GetProperty("name").GetString() ?? string.Empty))
|
||||||
|
{
|
||||||
|
matchingChannelIds.Add(channel.GetProperty("id").GetString() ?? string.Empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (root.TryGetProperty("response_metadata", out var metadata))
|
||||||
|
{
|
||||||
|
nextCursor = metadata.GetProperty("next_cursor").GetString() ?? string.Empty;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
nextCursor = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.LogError("Error retrieving slack userId: " + root.GetProperty("error").GetString());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} while (!string.IsNullOrEmpty(nextCursor));
|
||||||
|
|
||||||
|
return matchingChannelIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> GetDmChannelByEmailAsync(string token, string email)
|
||||||
|
{
|
||||||
|
var userId = await GetUserIdByEmailAsync(token, email);
|
||||||
|
return await OpenDmChannel(token, userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SendSlackMessageByChannelId(string token, string message, string channelId)
|
||||||
|
{
|
||||||
|
var payload = JsonContent.Create(new { channel = channelId, text = message });
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Post, "https://slack.com/api/chat.postMessage");
|
||||||
|
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
|
||||||
|
request.Content = payload;
|
||||||
|
|
||||||
|
await _httpClient.SendAsync(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SendDirectMessageByEmailAsync(string token, string message, string email)
|
||||||
|
{
|
||||||
|
var channelId = await GetDmChannelByEmailAsync(token, email);
|
||||||
|
if (!string.IsNullOrEmpty(channelId))
|
||||||
|
{
|
||||||
|
await SendSlackMessageByChannelId(token, message, channelId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<string> GetUserIdByEmailAsync(string token, string email)
|
||||||
|
{
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Get, $"https://slack.com/api/users.lookupByEmail?email={email}");
|
||||||
|
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
|
||||||
|
var response = await _httpClient.SendAsync(request);
|
||||||
|
var content = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
|
||||||
|
var root = content.RootElement;
|
||||||
|
|
||||||
|
if (root.GetProperty("ok").GetBoolean())
|
||||||
|
{
|
||||||
|
return root.GetProperty("user").GetProperty("id").GetString() ?? string.Empty;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.LogError("Error retrieving slack userId: " + root.GetProperty("error").GetString());
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<string> OpenDmChannel(string token, string userId)
|
||||||
|
{
|
||||||
|
var payload = JsonContent.Create(new { users = userId });
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Post, "https://slack.com/api/conversations.open");
|
||||||
|
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
|
||||||
|
request.Content = payload;
|
||||||
|
var response = await _httpClient.SendAsync(request);
|
||||||
|
var content = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
|
||||||
|
var root = content.RootElement;
|
||||||
|
|
||||||
|
if (root.GetProperty("ok").GetBoolean())
|
||||||
|
{
|
||||||
|
return content.RootElement.GetProperty("channel").GetProperty("id").GetString() ?? string.Empty;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.LogError("Error opening DM channel: " + root.GetProperty("error").GetString());
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -21,11 +21,13 @@ public class WebhookEventHandler(
|
|||||||
{
|
{
|
||||||
Guid organizationId = eventMessage.OrganizationId ?? Guid.NewGuid();
|
Guid organizationId = eventMessage.OrganizationId ?? Guid.NewGuid();
|
||||||
|
|
||||||
var configuration = await configurationRepository.GetConfigurationAsync<WebhookConfiguration>(
|
var configurations = await configurationRepository.GetConfigurationsAsync<WebhookConfiguration>(
|
||||||
organizationId,
|
|
||||||
IntegrationType.Webhook,
|
IntegrationType.Webhook,
|
||||||
eventMessage.Type);
|
organizationId,
|
||||||
if (configuration is not null)
|
eventMessage.Type
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach (var configuration in configurations)
|
||||||
{
|
{
|
||||||
var content = new StringContent(
|
var content = new StringContent(
|
||||||
TemplateProcessor.ReplaceTokens(configuration.Template, eventMessage),
|
TemplateProcessor.ReplaceTokens(configuration.Template, eventMessage),
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using Bit.Core.Auth.Settings;
|
using Bit.Core.Auth.Settings;
|
||||||
|
using Bit.Core.Models.Data.Integrations;
|
||||||
using Bit.Core.Settings.LoggingSettings;
|
using Bit.Core.Settings.LoggingSettings;
|
||||||
|
|
||||||
namespace Bit.Core.Settings;
|
namespace Bit.Core.Settings;
|
||||||
@ -283,8 +284,10 @@ public class GlobalSettings : IGlobalSettings
|
|||||||
public class EventLoggingSettings
|
public class EventLoggingSettings
|
||||||
{
|
{
|
||||||
public AzureServiceBusSettings AzureServiceBus { get; set; } = new AzureServiceBusSettings();
|
public AzureServiceBusSettings AzureServiceBus { get; set; } = new AzureServiceBusSettings();
|
||||||
|
public virtual List<SlackConfiguration> SlackConfigurations { get; set; } = new List<SlackConfiguration>();
|
||||||
|
public virtual List<WebhookConfiguration> WebhookConfigurations { get; set; } = new List<WebhookConfiguration>();
|
||||||
|
public virtual string SlackChannel { get; set; }
|
||||||
public virtual string SlackToken { get; set; }
|
public virtual string SlackToken { get; set; }
|
||||||
public virtual string SlackUserEmail { get; set; }
|
|
||||||
public virtual string WebhookUrl { get; set; }
|
public virtual string WebhookUrl { get; set; }
|
||||||
public RabbitMqSettings RabbitMq { get; set; } = new RabbitMqSettings();
|
public RabbitMqSettings RabbitMq { get; set; } = new RabbitMqSettings();
|
||||||
|
|
||||||
|
@ -120,8 +120,8 @@ public class Startup
|
|||||||
|
|
||||||
services.AddSingleton<IOrganizationIntegrationConfigurationRepository, OrganizationIntegrationConfigurationRepository>();
|
services.AddSingleton<IOrganizationIntegrationConfigurationRepository, OrganizationIntegrationConfigurationRepository>();
|
||||||
|
|
||||||
services.AddHttpClient(SlackMessageSender.HttpClientName);
|
services.AddHttpClient(SlackService.HttpClientName);
|
||||||
services.AddSingleton<SlackMessageSender>();
|
services.AddSingleton<ISlackService, SlackService>();
|
||||||
services.AddSingleton<SlackEventHandler>();
|
services.AddSingleton<SlackEventHandler>();
|
||||||
|
|
||||||
services.AddSingleton<IHostedService>(provider =>
|
services.AddSingleton<IHostedService>(provider =>
|
||||||
|
175
test/Core.Test/AdminConsole/Services/SlackEventHandlerTests.cs
Normal file
175
test/Core.Test/AdminConsole/Services/SlackEventHandlerTests.cs
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
using Bit.Core.Enums;
|
||||||
|
using Bit.Core.Models.Data;
|
||||||
|
using Bit.Core.Models.Data.Integrations;
|
||||||
|
using Bit.Core.Repositories;
|
||||||
|
using Bit.Core.Services;
|
||||||
|
using Bit.Test.Common.AutoFixture;
|
||||||
|
using Bit.Test.Common.AutoFixture.Attributes;
|
||||||
|
using Bit.Test.Common.Helpers;
|
||||||
|
using NSubstitute;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Bit.Core.Test.Services;
|
||||||
|
|
||||||
|
[SutProviderCustomize]
|
||||||
|
public class SlackEventHandlerTests
|
||||||
|
{
|
||||||
|
private readonly IOrganizationIntegrationConfigurationRepository _repository = Substitute.For<IOrganizationIntegrationConfigurationRepository>();
|
||||||
|
private readonly ISlackService _slackService = Substitute.For<ISlackService>();
|
||||||
|
private readonly string _channelId = "C12345";
|
||||||
|
private readonly string _channelId2 = "C67890";
|
||||||
|
private readonly string _token = "xoxb-test-token";
|
||||||
|
private readonly string _token2 = "xoxb-another-test-token";
|
||||||
|
|
||||||
|
private SutProvider<SlackEventHandler> GetSutProvider(
|
||||||
|
List<IntegrationConfiguration<SlackConfiguration>> integrationConfigurations)
|
||||||
|
{
|
||||||
|
_repository.GetConfigurationsAsync<SlackConfiguration>(
|
||||||
|
IntegrationType.Slack,
|
||||||
|
Arg.Any<Guid>(),
|
||||||
|
Arg.Any<EventType>())
|
||||||
|
.Returns(integrationConfigurations);
|
||||||
|
|
||||||
|
return new SutProvider<SlackEventHandler>()
|
||||||
|
.SetDependency(_repository)
|
||||||
|
.SetDependency(_slackService)
|
||||||
|
.Create();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<IntegrationConfiguration<SlackConfiguration>> NoConfigurations()
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<IntegrationConfiguration<SlackConfiguration>> OneConfiguration()
|
||||||
|
{
|
||||||
|
return
|
||||||
|
[
|
||||||
|
new IntegrationConfiguration<SlackConfiguration>
|
||||||
|
{
|
||||||
|
Configuration = new SlackConfiguration(channelId: _channelId, token: _token),
|
||||||
|
Template = "Date: #Date#, Type: #Type#, UserId: #UserId#"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<IntegrationConfiguration<SlackConfiguration>> TwoConfigurations()
|
||||||
|
{
|
||||||
|
return
|
||||||
|
[
|
||||||
|
new IntegrationConfiguration<SlackConfiguration>
|
||||||
|
{
|
||||||
|
Configuration = new SlackConfiguration(channelId: _channelId, token: _token),
|
||||||
|
Template = "Date: #Date#, Type: #Type#, UserId: #UserId#"
|
||||||
|
},
|
||||||
|
|
||||||
|
new IntegrationConfiguration<SlackConfiguration>
|
||||||
|
{
|
||||||
|
Configuration = new SlackConfiguration(channelId: _channelId2, token: _token2),
|
||||||
|
Template = "Date: #Date#, Type: #Type#, UserId: #UserId#"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task HandleEventAsync_DoesNothingWhenNoConfigurations(EventMessage eventMessage)
|
||||||
|
{
|
||||||
|
var sutProvider = GetSutProvider(NoConfigurations());
|
||||||
|
|
||||||
|
await sutProvider.Sut.HandleEventAsync(eventMessage);
|
||||||
|
sutProvider.GetDependency<ISlackService>().DidNotReceiveWithAnyArgs();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task HandleEventAsync_SendsEventViaSlackService(EventMessage eventMessage)
|
||||||
|
{
|
||||||
|
var sutProvider = GetSutProvider(OneConfiguration());
|
||||||
|
|
||||||
|
await sutProvider.Sut.HandleEventAsync(eventMessage);
|
||||||
|
sutProvider.GetDependency<ISlackService>().Received(1).SendSlackMessageByChannelId(
|
||||||
|
Arg.Is(AssertHelper.AssertPropertyEqual(_token)),
|
||||||
|
Arg.Is(AssertHelper.AssertPropertyEqual(
|
||||||
|
$"Date: {eventMessage.Date}, Type: {eventMessage.Type}, UserId: {eventMessage.UserId}")),
|
||||||
|
Arg.Is(AssertHelper.AssertPropertyEqual(_channelId))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task HandleEventAsync_SendsMultipleWhenConfigured(EventMessage eventMessage)
|
||||||
|
{
|
||||||
|
var sutProvider = GetSutProvider(TwoConfigurations());
|
||||||
|
|
||||||
|
await sutProvider.Sut.HandleEventAsync(eventMessage);
|
||||||
|
sutProvider.GetDependency<ISlackService>().Received(1).SendSlackMessageByChannelId(
|
||||||
|
Arg.Is(AssertHelper.AssertPropertyEqual(_token)),
|
||||||
|
Arg.Is(AssertHelper.AssertPropertyEqual(
|
||||||
|
$"Date: {eventMessage.Date}, Type: {eventMessage.Type}, UserId: {eventMessage.UserId}")),
|
||||||
|
Arg.Is(AssertHelper.AssertPropertyEqual(_channelId))
|
||||||
|
);
|
||||||
|
sutProvider.GetDependency<ISlackService>().Received(1).SendSlackMessageByChannelId(
|
||||||
|
Arg.Is(AssertHelper.AssertPropertyEqual(_token2)),
|
||||||
|
Arg.Is(AssertHelper.AssertPropertyEqual(
|
||||||
|
$"Date: {eventMessage.Date}, Type: {eventMessage.Type}, UserId: {eventMessage.UserId}")),
|
||||||
|
Arg.Is(AssertHelper.AssertPropertyEqual(_channelId2))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task HandleManyEventsAsync_SendsEventsViaSlackService(List<EventMessage> eventMessages)
|
||||||
|
{
|
||||||
|
var sutProvider = GetSutProvider(OneConfiguration());
|
||||||
|
|
||||||
|
await sutProvider.Sut.HandleManyEventsAsync(eventMessages);
|
||||||
|
|
||||||
|
var received = sutProvider.GetDependency<ISlackService>().ReceivedCalls();
|
||||||
|
using var calls = received.GetEnumerator();
|
||||||
|
|
||||||
|
Assert.Equal(eventMessages.Count, received.Count());
|
||||||
|
|
||||||
|
var index = 0;
|
||||||
|
foreach (var eventMessage in eventMessages)
|
||||||
|
{
|
||||||
|
Assert.True(calls.MoveNext());
|
||||||
|
var arguments = calls.Current.GetArguments();
|
||||||
|
Assert.Equal(_token, arguments[0] as string);
|
||||||
|
Assert.Equal($"Date: {eventMessage.Date}, Type: {eventMessage.Type}, UserId: {eventMessage.UserId}",
|
||||||
|
arguments[1] as string);
|
||||||
|
Assert.Equal(_channelId, arguments[2] as string);
|
||||||
|
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task HandleManyEventsAsync_SendsMultipleEventsAndMultipleConfigurations(List<EventMessage> eventMessages)
|
||||||
|
{
|
||||||
|
var sutProvider = GetSutProvider(TwoConfigurations());
|
||||||
|
|
||||||
|
await sutProvider.Sut.HandleManyEventsAsync(eventMessages);
|
||||||
|
|
||||||
|
var received = sutProvider.GetDependency<ISlackService>().ReceivedCalls();
|
||||||
|
var calls = received.GetEnumerator();
|
||||||
|
|
||||||
|
Assert.Equal(eventMessages.Count * 2, received.Count());
|
||||||
|
|
||||||
|
var index = 0;
|
||||||
|
foreach (var eventMessage in eventMessages)
|
||||||
|
{
|
||||||
|
Assert.True(calls.MoveNext());
|
||||||
|
var arguments = calls.Current.GetArguments();
|
||||||
|
Assert.Equal(_token, arguments[0] as string);
|
||||||
|
Assert.Equal($"Date: {eventMessage.Date}, Type: {eventMessage.Type}, UserId: {eventMessage.UserId}",
|
||||||
|
arguments[1] as string);
|
||||||
|
Assert.Equal(_channelId, arguments[2] as string);
|
||||||
|
|
||||||
|
Assert.True(calls.MoveNext());
|
||||||
|
var arguments2 = calls.Current.GetArguments();
|
||||||
|
Assert.Equal(_token2, arguments2[0] as string);
|
||||||
|
Assert.Equal($"Date: {eventMessage.Date}, Type: {eventMessage.Type}, UserId: {eventMessage.UserId}",
|
||||||
|
arguments2[1] as string);
|
||||||
|
Assert.Equal(_channelId2, arguments2[2] as string);
|
||||||
|
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
223
test/Core.Test/AdminConsole/Services/SlackServiceTests.cs
Normal file
223
test/Core.Test/AdminConsole/Services/SlackServiceTests.cs
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
using System.Net;
|
||||||
|
using System.Text.Json;
|
||||||
|
using Bit.Core.Services;
|
||||||
|
using Bit.Test.Common.AutoFixture;
|
||||||
|
using Bit.Test.Common.AutoFixture.Attributes;
|
||||||
|
using Bit.Test.Common.MockedHttpClient;
|
||||||
|
using NSubstitute;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Bit.Core.Test.Services;
|
||||||
|
|
||||||
|
[SutProviderCustomize]
|
||||||
|
public class SlackServiceTests
|
||||||
|
{
|
||||||
|
private readonly MockedHttpMessageHandler _handler;
|
||||||
|
private readonly HttpClient _httpClient;
|
||||||
|
private const string _token = "xoxb-test-token";
|
||||||
|
|
||||||
|
public SlackServiceTests()
|
||||||
|
{
|
||||||
|
_handler = new MockedHttpMessageHandler();
|
||||||
|
_httpClient = _handler.ToHttpClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
private SutProvider<SlackService> GetSutProvider()
|
||||||
|
{
|
||||||
|
var clientFactory = Substitute.For<IHttpClientFactory>();
|
||||||
|
clientFactory.CreateClient(SlackService.HttpClientName).Returns(_httpClient);
|
||||||
|
|
||||||
|
return new SutProvider<SlackService>()
|
||||||
|
.SetDependency(clientFactory)
|
||||||
|
.Create();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetChannelIdsAsync_Returns_Correct_ChannelIds()
|
||||||
|
{
|
||||||
|
var response = JsonSerializer.Serialize(
|
||||||
|
new
|
||||||
|
{
|
||||||
|
ok = true,
|
||||||
|
channels =
|
||||||
|
new[] {
|
||||||
|
new { id = "C12345", name = "general" },
|
||||||
|
new { id = "C67890", name = "random" }
|
||||||
|
},
|
||||||
|
response_metadata = new { next_cursor = "" }
|
||||||
|
}
|
||||||
|
);
|
||||||
|
_handler.When(HttpMethod.Get)
|
||||||
|
.RespondWith(HttpStatusCode.OK)
|
||||||
|
.WithContent(new StringContent(response));
|
||||||
|
|
||||||
|
var sutProvider = GetSutProvider();
|
||||||
|
var channelNames = new List<string> { "general", "random" };
|
||||||
|
var result = await sutProvider.Sut.GetChannelIdsAsync(_token, channelNames);
|
||||||
|
|
||||||
|
Assert.Equal(2, result.Count);
|
||||||
|
Assert.Contains("C12345", result);
|
||||||
|
Assert.Contains("C67890", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetChannelIdsAsync_Handles_Pagination_Correctly()
|
||||||
|
{
|
||||||
|
var firstPageResponse = JsonSerializer.Serialize(
|
||||||
|
new
|
||||||
|
{
|
||||||
|
ok = true,
|
||||||
|
channels = new[] { new { id = "C12345", name = "general" } },
|
||||||
|
response_metadata = new { next_cursor = "next_cursor_value" }
|
||||||
|
}
|
||||||
|
);
|
||||||
|
var secondPageResponse = JsonSerializer.Serialize(
|
||||||
|
new
|
||||||
|
{
|
||||||
|
ok = true,
|
||||||
|
channels = new[] { new { id = "C67890", name = "random" } },
|
||||||
|
response_metadata = new { next_cursor = "" }
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
_handler.When("https://slack.com/api/conversations.list?types=public_channel%2cprivate_channel&limit=1000")
|
||||||
|
.RespondWith(HttpStatusCode.OK)
|
||||||
|
.WithContent(new StringContent(firstPageResponse));
|
||||||
|
_handler.When("https://slack.com/api/conversations.list?types=public_channel%2cprivate_channel&limit=1000&cursor=next_cursor_value")
|
||||||
|
.RespondWith(HttpStatusCode.OK)
|
||||||
|
.WithContent(new StringContent(secondPageResponse));
|
||||||
|
|
||||||
|
var sutProvider = GetSutProvider();
|
||||||
|
var channelNames = new List<string> { "general", "random" };
|
||||||
|
|
||||||
|
var result = await sutProvider.Sut.GetChannelIdsAsync(_token, channelNames);
|
||||||
|
|
||||||
|
Assert.Equal(2, result.Count);
|
||||||
|
Assert.Contains("C12345", result);
|
||||||
|
Assert.Contains("C67890", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetChannelIdsAsync_Handles_Api_Error_Gracefully()
|
||||||
|
{
|
||||||
|
var errorResponse = JsonSerializer.Serialize(
|
||||||
|
new { ok = false, error = "rate_limited" }
|
||||||
|
);
|
||||||
|
|
||||||
|
_handler.When(HttpMethod.Get)
|
||||||
|
.RespondWith(HttpStatusCode.TooManyRequests)
|
||||||
|
.WithContent(new StringContent(errorResponse));
|
||||||
|
|
||||||
|
var sutProvider = GetSutProvider();
|
||||||
|
var channelNames = new List<string> { "general", "random" };
|
||||||
|
|
||||||
|
var result = await sutProvider.Sut.GetChannelIdsAsync(_token, channelNames);
|
||||||
|
|
||||||
|
Assert.Empty(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetChannelIdsAsync_Returns_Empty_When_No_Channels_Found()
|
||||||
|
{
|
||||||
|
var emptyResponse = JsonSerializer.Serialize(
|
||||||
|
new
|
||||||
|
{
|
||||||
|
ok = true,
|
||||||
|
channels = Array.Empty<string>(),
|
||||||
|
response_metadata = new { next_cursor = "" }
|
||||||
|
});
|
||||||
|
|
||||||
|
_handler.When(HttpMethod.Get)
|
||||||
|
.RespondWith(HttpStatusCode.OK)
|
||||||
|
.WithContent(new StringContent(emptyResponse));
|
||||||
|
|
||||||
|
var sutProvider = GetSutProvider();
|
||||||
|
var channelNames = new List<string> { "general", "random" };
|
||||||
|
var result = await sutProvider.Sut.GetChannelIdsAsync(_token, channelNames);
|
||||||
|
|
||||||
|
Assert.Empty(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetChannelIdAsync_Returns_Correct_ChannelId()
|
||||||
|
{
|
||||||
|
var sutProvider = GetSutProvider();
|
||||||
|
var response = new
|
||||||
|
{
|
||||||
|
ok = true,
|
||||||
|
channels = new[]
|
||||||
|
{
|
||||||
|
new { id = "C12345", name = "general" },
|
||||||
|
new { id = "C67890", name = "random" }
|
||||||
|
},
|
||||||
|
response_metadata = new { next_cursor = "" }
|
||||||
|
};
|
||||||
|
|
||||||
|
_handler.When(HttpMethod.Get)
|
||||||
|
.RespondWith(HttpStatusCode.OK)
|
||||||
|
.WithContent(new StringContent(JsonSerializer.Serialize(response)));
|
||||||
|
|
||||||
|
var result = await sutProvider.Sut.GetChannelIdAsync(_token, "general");
|
||||||
|
|
||||||
|
Assert.Equal("C12345", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task GetDmChannelByEmailAsync_Returns_Correct_DmChannelId()
|
||||||
|
{
|
||||||
|
var sutProvider = GetSutProvider();
|
||||||
|
var email = "user@example.com";
|
||||||
|
var userId = "U12345";
|
||||||
|
var dmChannelId = "D67890";
|
||||||
|
|
||||||
|
var userResponse = new
|
||||||
|
{
|
||||||
|
ok = true,
|
||||||
|
user = new { id = userId }
|
||||||
|
};
|
||||||
|
|
||||||
|
var dmResponse = new
|
||||||
|
{
|
||||||
|
ok = true,
|
||||||
|
channel = new { id = dmChannelId }
|
||||||
|
};
|
||||||
|
|
||||||
|
_handler.When($"https://slack.com/api/users.lookupByEmail?email={email}")
|
||||||
|
.RespondWith(HttpStatusCode.OK)
|
||||||
|
.WithContent(new StringContent(JsonSerializer.Serialize(userResponse)));
|
||||||
|
|
||||||
|
_handler.When("https://slack.com/api/conversations.open")
|
||||||
|
.RespondWith(HttpStatusCode.OK)
|
||||||
|
.WithContent(new StringContent(JsonSerializer.Serialize(dmResponse)));
|
||||||
|
|
||||||
|
var result = await sutProvider.Sut.GetDmChannelByEmailAsync(_token, email);
|
||||||
|
|
||||||
|
Assert.Equal(dmChannelId, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task SendSlackMessageByChannelId_Sends_Correct_Message()
|
||||||
|
{
|
||||||
|
var sutProvider = GetSutProvider();
|
||||||
|
var channelId = "C12345";
|
||||||
|
var message = "Hello, Slack!";
|
||||||
|
|
||||||
|
_handler.When(HttpMethod.Post)
|
||||||
|
.RespondWith(HttpStatusCode.OK)
|
||||||
|
.WithContent(new StringContent(string.Empty));
|
||||||
|
|
||||||
|
await sutProvider.Sut.SendSlackMessageByChannelId(_token, message, channelId);
|
||||||
|
|
||||||
|
Assert.Single(_handler.CapturedRequests);
|
||||||
|
var request = _handler.CapturedRequests[0];
|
||||||
|
Assert.NotNull(request);
|
||||||
|
Assert.Equal(HttpMethod.Post, request.Method);
|
||||||
|
Assert.NotNull(request.Headers.Authorization);
|
||||||
|
Assert.Equal($"Bearer {_token}", request.Headers.Authorization.ToString());
|
||||||
|
Assert.NotNull(request.Content);
|
||||||
|
var returned = (await request.Content.ReadAsStringAsync());
|
||||||
|
var json = JsonDocument.Parse(returned);
|
||||||
|
Assert.Equal(message, json.RootElement.GetProperty("text").GetString() ?? string.Empty);
|
||||||
|
Assert.Equal(channelId, json.RootElement.GetProperty("channel").GetString() ?? string.Empty);
|
||||||
|
}
|
||||||
|
}
|
@ -20,7 +20,16 @@ public class WebhookEventHandlerTests
|
|||||||
private readonly MockedHttpMessageHandler _handler;
|
private readonly MockedHttpMessageHandler _handler;
|
||||||
private readonly HttpClient _httpClient;
|
private readonly HttpClient _httpClient;
|
||||||
|
|
||||||
|
private const string _template =
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"Date": "#Date#",
|
||||||
|
"Type": "#Type#",
|
||||||
|
"UserId": "#UserId#"
|
||||||
|
}
|
||||||
|
""";
|
||||||
private const string _webhookUrl = "http://localhost/test/event";
|
private const string _webhookUrl = "http://localhost/test/event";
|
||||||
|
private const string _webhookUrl2 = "http://localhost/another/event";
|
||||||
|
|
||||||
public WebhookEventHandlerTests()
|
public WebhookEventHandlerTests()
|
||||||
{
|
{
|
||||||
@ -31,23 +40,18 @@ public class WebhookEventHandlerTests
|
|||||||
_httpClient = _handler.ToHttpClient();
|
_httpClient = _handler.ToHttpClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
private SutProvider<WebhookEventHandler> GetSutProvider()
|
private SutProvider<WebhookEventHandler> GetSutProvider(
|
||||||
|
List<IntegrationConfiguration<WebhookConfiguration>> configurations)
|
||||||
{
|
{
|
||||||
var clientFactory = Substitute.For<IHttpClientFactory>();
|
var clientFactory = Substitute.For<IHttpClientFactory>();
|
||||||
clientFactory.CreateClient(WebhookEventHandler.HttpClientName).Returns(_httpClient);
|
clientFactory.CreateClient(WebhookEventHandler.HttpClientName).Returns(_httpClient);
|
||||||
|
|
||||||
var repository = Substitute.For<IOrganizationIntegrationConfigurationRepository>();
|
var repository = Substitute.For<IOrganizationIntegrationConfigurationRepository>();
|
||||||
repository.GetConfigurationAsync<WebhookConfiguration>(
|
repository.GetConfigurationsAsync<WebhookConfiguration>(
|
||||||
Arg.Any<Guid>(),
|
|
||||||
IntegrationType.Webhook,
|
IntegrationType.Webhook,
|
||||||
|
Arg.Any<Guid>(),
|
||||||
Arg.Any<EventType>()
|
Arg.Any<EventType>()
|
||||||
).Returns(
|
).Returns(configurations);
|
||||||
new IntegrationConfiguration<WebhookConfiguration>
|
|
||||||
{
|
|
||||||
Configuration = new WebhookConfiguration { ApiKey = "", Url = _webhookUrl },
|
|
||||||
Template = "{ \"Date\": \"#Date#\", \"Type\": \"#Type#\", \"UserId\": \"#UserId#\" }"
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return new SutProvider<WebhookEventHandler>()
|
return new SutProvider<WebhookEventHandler>()
|
||||||
.SetDependency(repository)
|
.SetDependency(repository)
|
||||||
@ -55,10 +59,57 @@ public class WebhookEventHandlerTests
|
|||||||
.Create();
|
.Create();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<IntegrationConfiguration<WebhookConfiguration>> NoConfigurations()
|
||||||
|
{
|
||||||
|
return new List<IntegrationConfiguration<WebhookConfiguration>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<IntegrationConfiguration<WebhookConfiguration>> OneConfiguration()
|
||||||
|
{
|
||||||
|
return new List<IntegrationConfiguration<WebhookConfiguration>>
|
||||||
|
{
|
||||||
|
new IntegrationConfiguration<WebhookConfiguration>
|
||||||
|
{
|
||||||
|
Configuration = new WebhookConfiguration { Url = _webhookUrl },
|
||||||
|
Template = _template
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
List<IntegrationConfiguration<WebhookConfiguration>> TwoConfigurations()
|
||||||
|
{
|
||||||
|
return new List<IntegrationConfiguration<WebhookConfiguration>>
|
||||||
|
{
|
||||||
|
new IntegrationConfiguration<WebhookConfiguration>
|
||||||
|
{
|
||||||
|
Configuration = new WebhookConfiguration { Url = _webhookUrl },
|
||||||
|
Template = _template
|
||||||
|
},
|
||||||
|
new IntegrationConfiguration<WebhookConfiguration>
|
||||||
|
{
|
||||||
|
Configuration = new WebhookConfiguration { Url = _webhookUrl2 },
|
||||||
|
Template = _template
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task HandleEventAsync_DoesNothingWhenNoConfigurations(EventMessage eventMessage)
|
||||||
|
{
|
||||||
|
var sutProvider = GetSutProvider(NoConfigurations());
|
||||||
|
|
||||||
|
await sutProvider.Sut.HandleEventAsync(eventMessage);
|
||||||
|
sutProvider.GetDependency<IHttpClientFactory>().Received(1).CreateClient(
|
||||||
|
Arg.Is(AssertHelper.AssertPropertyEqual(WebhookEventHandler.HttpClientName))
|
||||||
|
);
|
||||||
|
|
||||||
|
Assert.Empty(_handler.CapturedRequests);
|
||||||
|
}
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task HandleEventAsync_PostsEventToUrl(EventMessage eventMessage)
|
public async Task HandleEventAsync_PostsEventToUrl(EventMessage eventMessage)
|
||||||
{
|
{
|
||||||
var sutProvider = GetSutProvider();
|
var sutProvider = GetSutProvider(OneConfiguration());
|
||||||
|
|
||||||
await sutProvider.Sut.HandleEventAsync(eventMessage);
|
await sutProvider.Sut.HandleEventAsync(eventMessage);
|
||||||
sutProvider.GetDependency<IHttpClientFactory>().Received(1).CreateClient(
|
sutProvider.GetDependency<IHttpClientFactory>().Received(1).CreateClient(
|
||||||
@ -77,23 +128,77 @@ public class WebhookEventHandlerTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task HandleEventManyAsync_PostsEventsToUrl(List<EventMessage> eventMessages)
|
public async Task HandleManyEventsAsync_DoesNothingWhenNoConfigurations(List<EventMessage> eventMessages)
|
||||||
{
|
{
|
||||||
var sutProvider = GetSutProvider();
|
var sutProvider = GetSutProvider(NoConfigurations());
|
||||||
|
|
||||||
await sutProvider.Sut.HandleManyEventsAsync(eventMessages);
|
await sutProvider.Sut.HandleManyEventsAsync(eventMessages);
|
||||||
sutProvider.GetDependency<IHttpClientFactory>().Received(1).CreateClient(
|
sutProvider.GetDependency<IHttpClientFactory>().Received(1).CreateClient(
|
||||||
Arg.Is(AssertHelper.AssertPropertyEqual<string>(WebhookEventHandler.HttpClientName))
|
Arg.Is(AssertHelper.AssertPropertyEqual(WebhookEventHandler.HttpClientName))
|
||||||
);
|
);
|
||||||
|
|
||||||
var request = _handler.CapturedRequests[0];
|
Assert.Empty(_handler.CapturedRequests);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task HandleManyEventsAsync_PostsEventsToUrl(List<EventMessage> eventMessages)
|
||||||
|
{
|
||||||
|
var sutProvider = GetSutProvider(OneConfiguration());
|
||||||
|
|
||||||
|
await sutProvider.Sut.HandleManyEventsAsync(eventMessages);
|
||||||
|
sutProvider.GetDependency<IHttpClientFactory>().Received(1).CreateClient(
|
||||||
|
Arg.Is(AssertHelper.AssertPropertyEqual(WebhookEventHandler.HttpClientName))
|
||||||
|
);
|
||||||
|
|
||||||
|
Assert.Equal(eventMessages.Count, _handler.CapturedRequests.Count);
|
||||||
|
var index = 0;
|
||||||
|
foreach (var request in _handler.CapturedRequests)
|
||||||
|
{
|
||||||
Assert.NotNull(request);
|
Assert.NotNull(request);
|
||||||
var returned = await request.Content.ReadFromJsonAsync<MockEvent>();
|
var returned = await request.Content.ReadFromJsonAsync<MockEvent>();
|
||||||
var expected = MockEvent.From(eventMessages.First());
|
var expected = MockEvent.From(eventMessages[index]);
|
||||||
|
|
||||||
Assert.Equal(HttpMethod.Post, request.Method);
|
Assert.Equal(HttpMethod.Post, request.Method);
|
||||||
Assert.Equal(_webhookUrl, request.RequestUri.ToString());
|
Assert.Equal(_webhookUrl, request.RequestUri.ToString());
|
||||||
AssertHelper.AssertPropertyEqual(expected, returned);
|
AssertHelper.AssertPropertyEqual(expected, returned);
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task HandleManyEventsAsync_PostsEventsToMultipleUrls(List<EventMessage> eventMessages)
|
||||||
|
{
|
||||||
|
var sutProvider = GetSutProvider(TwoConfigurations());
|
||||||
|
|
||||||
|
await sutProvider.Sut.HandleManyEventsAsync(eventMessages);
|
||||||
|
sutProvider.GetDependency<IHttpClientFactory>().Received(1).CreateClient(
|
||||||
|
Arg.Is(AssertHelper.AssertPropertyEqual(WebhookEventHandler.HttpClientName))
|
||||||
|
);
|
||||||
|
|
||||||
|
using var capturedRequests = _handler.CapturedRequests.GetEnumerator();
|
||||||
|
Assert.Equal(eventMessages.Count * 2, _handler.CapturedRequests.Count);
|
||||||
|
|
||||||
|
foreach (var eventMessage in eventMessages)
|
||||||
|
{
|
||||||
|
var expected = MockEvent.From(eventMessage);
|
||||||
|
|
||||||
|
Assert.True(capturedRequests.MoveNext());
|
||||||
|
var request = capturedRequests.Current;
|
||||||
|
Assert.NotNull(request);
|
||||||
|
Assert.Equal(HttpMethod.Post, request.Method);
|
||||||
|
Assert.Equal(_webhookUrl, request.RequestUri.ToString());
|
||||||
|
var returned = await request.Content.ReadFromJsonAsync<MockEvent>();
|
||||||
|
AssertHelper.AssertPropertyEqual(expected, returned);
|
||||||
|
|
||||||
|
Assert.True(capturedRequests.MoveNext());
|
||||||
|
request = capturedRequests.Current;
|
||||||
|
Assert.NotNull(request);
|
||||||
|
Assert.Equal(HttpMethod.Post, request.Method);
|
||||||
|
Assert.Equal(_webhookUrl2, request.RequestUri.ToString());
|
||||||
|
returned = await request.Content.ReadFromJsonAsync<MockEvent>();
|
||||||
|
AssertHelper.AssertPropertyEqual(expected, returned);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user