mirror of
https://github.com/bitwarden/server
synced 2025-12-20 02:03:46 +00:00
Add CQRS and caching support for OrganizationIntegrationConfigurations (#6690)
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
using Bit.Api.AdminConsole.Models.Request.Organizations;
|
||||
using Bit.Api.AdminConsole.Models.Response.Organizations;
|
||||
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
@@ -12,8 +12,10 @@ namespace Bit.Api.AdminConsole.Controllers;
|
||||
[Authorize("Application")]
|
||||
public class OrganizationIntegrationConfigurationController(
|
||||
ICurrentContext currentContext,
|
||||
IOrganizationIntegrationRepository integrationRepository,
|
||||
IOrganizationIntegrationConfigurationRepository integrationConfigurationRepository) : Controller
|
||||
ICreateOrganizationIntegrationConfigurationCommand createCommand,
|
||||
IUpdateOrganizationIntegrationConfigurationCommand updateCommand,
|
||||
IDeleteOrganizationIntegrationConfigurationCommand deleteCommand,
|
||||
IGetOrganizationIntegrationConfigurationsQuery getQuery) : Controller
|
||||
{
|
||||
[HttpGet("")]
|
||||
public async Task<List<OrganizationIntegrationConfigurationResponseModel>> GetAsync(
|
||||
@@ -24,13 +26,8 @@ public class OrganizationIntegrationConfigurationController(
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
var integration = await integrationRepository.GetByIdAsync(integrationId);
|
||||
if (integration == null || integration.OrganizationId != organizationId)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var configurations = await integrationConfigurationRepository.GetManyByIntegrationAsync(integrationId);
|
||||
var configurations = await getQuery.GetManyByIntegrationAsync(organizationId, integrationId);
|
||||
return configurations
|
||||
.Select(configuration => new OrganizationIntegrationConfigurationResponseModel(configuration))
|
||||
.ToList();
|
||||
@@ -46,19 +43,11 @@ public class OrganizationIntegrationConfigurationController(
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
var integration = await integrationRepository.GetByIdAsync(integrationId);
|
||||
if (integration == null || integration.OrganizationId != organizationId)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
if (!model.IsValidForType(integration.Type))
|
||||
{
|
||||
throw new BadRequestException($"Invalid Configuration and/or Template for integration type {integration.Type}");
|
||||
}
|
||||
|
||||
var organizationIntegrationConfiguration = model.ToOrganizationIntegrationConfiguration(integrationId);
|
||||
var configuration = await integrationConfigurationRepository.CreateAsync(organizationIntegrationConfiguration);
|
||||
return new OrganizationIntegrationConfigurationResponseModel(configuration);
|
||||
var configuration = model.ToOrganizationIntegrationConfiguration(integrationId);
|
||||
var created = await createCommand.CreateAsync(organizationId, integrationId, configuration);
|
||||
|
||||
return new OrganizationIntegrationConfigurationResponseModel(created);
|
||||
}
|
||||
|
||||
[HttpPut("{configurationId:guid}")]
|
||||
@@ -72,26 +61,11 @@ public class OrganizationIntegrationConfigurationController(
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
var integration = await integrationRepository.GetByIdAsync(integrationId);
|
||||
if (integration == null || integration.OrganizationId != organizationId)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
if (!model.IsValidForType(integration.Type))
|
||||
{
|
||||
throw new BadRequestException($"Invalid Configuration and/or Template for integration type {integration.Type}");
|
||||
}
|
||||
|
||||
var configuration = await integrationConfigurationRepository.GetByIdAsync(configurationId);
|
||||
if (configuration is null || configuration.OrganizationIntegrationId != integrationId)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
var configuration = model.ToOrganizationIntegrationConfiguration(integrationId);
|
||||
var updated = await updateCommand.UpdateAsync(organizationId, integrationId, configurationId, configuration);
|
||||
|
||||
var newConfiguration = model.ToOrganizationIntegrationConfiguration(configuration);
|
||||
await integrationConfigurationRepository.ReplaceAsync(newConfiguration);
|
||||
|
||||
return new OrganizationIntegrationConfigurationResponseModel(newConfiguration);
|
||||
return new OrganizationIntegrationConfigurationResponseModel(updated);
|
||||
}
|
||||
|
||||
[HttpDelete("{configurationId:guid}")]
|
||||
@@ -101,19 +75,8 @@ public class OrganizationIntegrationConfigurationController(
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
var integration = await integrationRepository.GetByIdAsync(integrationId);
|
||||
if (integration == null || integration.OrganizationId != organizationId)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var configuration = await integrationConfigurationRepository.GetByIdAsync(configurationId);
|
||||
if (configuration is null || configuration.OrganizationIntegrationId != integrationId)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
await integrationConfigurationRepository.DeleteAsync(configuration);
|
||||
await deleteCommand.DeleteAsync(organizationId, integrationId, configurationId);
|
||||
}
|
||||
|
||||
[HttpPost("{configurationId:guid}/delete")]
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System.Text.Json;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Enums;
|
||||
|
||||
|
||||
@@ -16,38 +14,6 @@ public class OrganizationIntegrationConfigurationRequestModel
|
||||
|
||||
public string? Template { get; set; }
|
||||
|
||||
public bool IsValidForType(IntegrationType integrationType)
|
||||
{
|
||||
switch (integrationType)
|
||||
{
|
||||
case IntegrationType.CloudBillingSync or IntegrationType.Scim:
|
||||
return false;
|
||||
case IntegrationType.Slack:
|
||||
return !string.IsNullOrWhiteSpace(Template) &&
|
||||
IsConfigurationValid<SlackIntegrationConfiguration>() &&
|
||||
IsFiltersValid();
|
||||
case IntegrationType.Webhook:
|
||||
return !string.IsNullOrWhiteSpace(Template) &&
|
||||
IsConfigurationValid<WebhookIntegrationConfiguration>() &&
|
||||
IsFiltersValid();
|
||||
case IntegrationType.Hec:
|
||||
return !string.IsNullOrWhiteSpace(Template) &&
|
||||
Configuration is null &&
|
||||
IsFiltersValid();
|
||||
case IntegrationType.Datadog:
|
||||
return !string.IsNullOrWhiteSpace(Template) &&
|
||||
Configuration is null &&
|
||||
IsFiltersValid();
|
||||
case IntegrationType.Teams:
|
||||
return !string.IsNullOrWhiteSpace(Template) &&
|
||||
Configuration is null &&
|
||||
IsFiltersValid();
|
||||
default:
|
||||
return false;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public OrganizationIntegrationConfiguration ToOrganizationIntegrationConfiguration(Guid organizationIntegrationId)
|
||||
{
|
||||
return new OrganizationIntegrationConfiguration()
|
||||
@@ -59,50 +25,4 @@ public class OrganizationIntegrationConfigurationRequestModel
|
||||
Template = Template
|
||||
};
|
||||
}
|
||||
|
||||
public OrganizationIntegrationConfiguration ToOrganizationIntegrationConfiguration(OrganizationIntegrationConfiguration currentConfiguration)
|
||||
{
|
||||
currentConfiguration.Configuration = Configuration;
|
||||
currentConfiguration.EventType = EventType;
|
||||
currentConfiguration.Filters = Filters;
|
||||
currentConfiguration.Template = Template;
|
||||
|
||||
return currentConfiguration;
|
||||
}
|
||||
|
||||
private bool IsConfigurationValid<T>()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(Configuration))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var config = JsonSerializer.Deserialize<T>(Configuration);
|
||||
return config is not null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsFiltersValid()
|
||||
{
|
||||
if (Filters is null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var filters = JsonSerializer.Deserialize<IntegrationFilterGroup>(Filters);
|
||||
return filters is not null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations;
|
||||
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations;
|
||||
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
|
||||
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations;
|
||||
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations.Interfaces;
|
||||
using Bit.Core.AdminConsole.Services;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Utilities;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
@@ -20,8 +23,12 @@ public static class EventIntegrationsServiceCollectionExtensions
|
||||
// This is idempotent for the same named cache, so it's safe to call.
|
||||
services.AddExtendedCache(EventIntegrationsCacheConstants.CacheName, globalSettings);
|
||||
|
||||
// Add Validator
|
||||
services.TryAddSingleton<IOrganizationIntegrationConfigurationValidator, OrganizationIntegrationConfigurationValidator>();
|
||||
|
||||
// Add all commands/queries
|
||||
services.AddOrganizationIntegrationCommandsQueries();
|
||||
services.AddOrganizationIntegrationConfigurationCommandsQueries();
|
||||
|
||||
return services;
|
||||
}
|
||||
@@ -35,4 +42,14 @@ public static class EventIntegrationsServiceCollectionExtensions
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
internal static IServiceCollection AddOrganizationIntegrationConfigurationCommandsQueries(this IServiceCollection services)
|
||||
{
|
||||
services.TryAddScoped<ICreateOrganizationIntegrationConfigurationCommand, CreateOrganizationIntegrationConfigurationCommand>();
|
||||
services.TryAddScoped<IUpdateOrganizationIntegrationConfigurationCommand, UpdateOrganizationIntegrationConfigurationCommand>();
|
||||
services.TryAddScoped<IDeleteOrganizationIntegrationConfigurationCommand, DeleteOrganizationIntegrationConfigurationCommand>();
|
||||
services.TryAddScoped<IGetOrganizationIntegrationConfigurationsQuery, GetOrganizationIntegrationConfigurationsQuery>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
|
||||
using Bit.Core.AdminConsole.Services;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Utilities;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ZiggyCreatures.Caching.Fusion;
|
||||
|
||||
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations;
|
||||
|
||||
/// <summary>
|
||||
/// Command implementation for creating organization integration configurations with validation and cache invalidation support.
|
||||
/// </summary>
|
||||
public class CreateOrganizationIntegrationConfigurationCommand(
|
||||
IOrganizationIntegrationRepository integrationRepository,
|
||||
IOrganizationIntegrationConfigurationRepository configurationRepository,
|
||||
[FromKeyedServices(EventIntegrationsCacheConstants.CacheName)] IFusionCache cache,
|
||||
IOrganizationIntegrationConfigurationValidator validator)
|
||||
: ICreateOrganizationIntegrationConfigurationCommand
|
||||
{
|
||||
public async Task<OrganizationIntegrationConfiguration> CreateAsync(
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
OrganizationIntegrationConfiguration configuration)
|
||||
{
|
||||
var integration = await integrationRepository.GetByIdAsync(integrationId);
|
||||
if (integration == null || integration.OrganizationId != organizationId)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
if (!validator.ValidateConfiguration(integration.Type, configuration))
|
||||
{
|
||||
throw new BadRequestException(
|
||||
$"Invalid Configuration and/or Filters for integration type {integration.Type}");
|
||||
}
|
||||
|
||||
var created = await configurationRepository.CreateAsync(configuration);
|
||||
|
||||
// Invalidate the cached configuration details
|
||||
// Even though this is a new record, the cache could hold a stale empty list for this
|
||||
if (created.EventType == null)
|
||||
{
|
||||
// Wildcard configuration - invalidate all cached results for this org/integration
|
||||
await cache.RemoveByTagAsync(
|
||||
EventIntegrationsCacheConstants.BuildCacheTagForOrganizationIntegration(
|
||||
organizationId: organizationId,
|
||||
integrationType: integration.Type
|
||||
));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Specific event type - only invalidate that specific cache entry
|
||||
await cache.RemoveAsync(
|
||||
EventIntegrationsCacheConstants.BuildCacheKeyForOrganizationIntegrationConfigurationDetails(
|
||||
organizationId: organizationId,
|
||||
integrationType: integration.Type,
|
||||
eventType: created.EventType.Value
|
||||
));
|
||||
}
|
||||
|
||||
return created;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Utilities;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ZiggyCreatures.Caching.Fusion;
|
||||
|
||||
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations;
|
||||
|
||||
/// <summary>
|
||||
/// Command implementation for deleting organization integration configurations with cache invalidation support.
|
||||
/// </summary>
|
||||
public class DeleteOrganizationIntegrationConfigurationCommand(
|
||||
IOrganizationIntegrationRepository integrationRepository,
|
||||
IOrganizationIntegrationConfigurationRepository configurationRepository,
|
||||
[FromKeyedServices(EventIntegrationsCacheConstants.CacheName)] IFusionCache cache)
|
||||
: IDeleteOrganizationIntegrationConfigurationCommand
|
||||
{
|
||||
public async Task DeleteAsync(Guid organizationId, Guid integrationId, Guid configurationId)
|
||||
{
|
||||
var integration = await integrationRepository.GetByIdAsync(integrationId);
|
||||
if (integration == null || integration.OrganizationId != organizationId)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
var configuration = await configurationRepository.GetByIdAsync(configurationId);
|
||||
if (configuration is null || configuration.OrganizationIntegrationId != integrationId)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
await configurationRepository.DeleteAsync(configuration);
|
||||
|
||||
if (configuration.EventType == null)
|
||||
{
|
||||
// Wildcard configuration - invalidate all cached results for this org/integration
|
||||
await cache.RemoveByTagAsync(
|
||||
EventIntegrationsCacheConstants.BuildCacheTagForOrganizationIntegration(
|
||||
organizationId: organizationId,
|
||||
integrationType: integration.Type
|
||||
));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Specific event type - only invalidate that specific cache entry
|
||||
await cache.RemoveAsync(
|
||||
EventIntegrationsCacheConstants.BuildCacheKeyForOrganizationIntegrationConfigurationDetails(
|
||||
organizationId: organizationId,
|
||||
integrationType: integration.Type,
|
||||
eventType: configuration.EventType.Value
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
|
||||
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations;
|
||||
|
||||
/// <summary>
|
||||
/// Query implementation for retrieving organization integration configurations.
|
||||
/// </summary>
|
||||
public class GetOrganizationIntegrationConfigurationsQuery(
|
||||
IOrganizationIntegrationRepository integrationRepository,
|
||||
IOrganizationIntegrationConfigurationRepository configurationRepository)
|
||||
: IGetOrganizationIntegrationConfigurationsQuery
|
||||
{
|
||||
public async Task<List<OrganizationIntegrationConfiguration>> GetManyByIntegrationAsync(
|
||||
Guid organizationId,
|
||||
Guid integrationId)
|
||||
{
|
||||
var integration = await integrationRepository.GetByIdAsync(integrationId);
|
||||
if (integration == null || integration.OrganizationId != organizationId)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
var configurations = await configurationRepository.GetManyByIntegrationAsync(integrationId);
|
||||
return configurations.ToList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
|
||||
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// Command interface for creating organization integration configurations.
|
||||
/// </summary>
|
||||
public interface ICreateOrganizationIntegrationConfigurationCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new configuration for an organization integration.
|
||||
/// </summary>
|
||||
/// <param name="organizationId">The unique identifier of the organization.</param>
|
||||
/// <param name="integrationId">The unique identifier of the integration.</param>
|
||||
/// <param name="configuration">The configuration to create.</param>
|
||||
/// <returns>The created configuration.</returns>
|
||||
/// <exception cref="Exceptions.NotFoundException">Thrown when the integration does not exist
|
||||
/// or does not belong to the specified organization.</exception>
|
||||
/// <exception cref="Exceptions.BadRequestException">Thrown when the configuration or filters
|
||||
/// are invalid for the integration type.</exception>
|
||||
Task<OrganizationIntegrationConfiguration> CreateAsync(Guid organizationId, Guid integrationId, OrganizationIntegrationConfiguration configuration);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// Command interface for deleting organization integration configurations.
|
||||
/// </summary>
|
||||
public interface IDeleteOrganizationIntegrationConfigurationCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Deletes a configuration from an organization integration.
|
||||
/// </summary>
|
||||
/// <param name="organizationId">The unique identifier of the organization.</param>
|
||||
/// <param name="integrationId">The unique identifier of the integration.</param>
|
||||
/// <param name="configurationId">The unique identifier of the configuration to delete.</param>
|
||||
/// <exception cref="Exceptions.NotFoundException">
|
||||
/// Thrown when the integration or configuration does not exist,
|
||||
/// or the integration does not belong to the specified organization,
|
||||
/// or the configuration does not belong to the specified integration.</exception>
|
||||
Task DeleteAsync(Guid organizationId, Guid integrationId, Guid configurationId);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
|
||||
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// Query interface for retrieving organization integration configurations.
|
||||
/// </summary>
|
||||
public interface IGetOrganizationIntegrationConfigurationsQuery
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieves all configurations for a specific organization integration.
|
||||
/// </summary>
|
||||
/// <param name="organizationId">The unique identifier of the organization.</param>
|
||||
/// <param name="integrationId">The unique identifier of the integration.</param>
|
||||
/// <returns>A list of configurations associated with the integration.</returns>
|
||||
/// <exception cref="Exceptions.NotFoundException">Thrown when the integration does not exist
|
||||
/// or does not belong to the specified organization.</exception>
|
||||
Task<List<OrganizationIntegrationConfiguration>> GetManyByIntegrationAsync(Guid organizationId, Guid integrationId);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
|
||||
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
|
||||
|
||||
/// <summary>
|
||||
/// Command interface for updating organization integration configurations.
|
||||
/// </summary>
|
||||
public interface IUpdateOrganizationIntegrationConfigurationCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Updates an existing configuration for an organization integration.
|
||||
/// </summary>
|
||||
/// <param name="organizationId">The unique identifier of the organization.</param>
|
||||
/// <param name="integrationId">The unique identifier of the integration.</param>
|
||||
/// <param name="configurationId">The unique identifier of the configuration to update.</param>
|
||||
/// <param name="updatedConfiguration">The updated configuration data.</param>
|
||||
/// <returns>The updated configuration.</returns>
|
||||
/// <exception cref="Exceptions.NotFoundException">
|
||||
/// Thrown when the integration or the configuration does not exist,
|
||||
/// or the integration does not belong to the specified organization,
|
||||
/// or the configuration does not belong to the specified integration.</exception>
|
||||
/// <exception cref="Exceptions.BadRequestException">Thrown when the configuration or filters
|
||||
/// are invalid for the integration type.</exception>
|
||||
Task<OrganizationIntegrationConfiguration> UpdateAsync(Guid organizationId, Guid integrationId, Guid configurationId, OrganizationIntegrationConfiguration updatedConfiguration);
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
|
||||
using Bit.Core.AdminConsole.Services;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Utilities;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using ZiggyCreatures.Caching.Fusion;
|
||||
|
||||
namespace Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations;
|
||||
|
||||
/// <summary>
|
||||
/// Command implementation for updating organization integration configurations with validation and cache invalidation support.
|
||||
/// </summary>
|
||||
public class UpdateOrganizationIntegrationConfigurationCommand(
|
||||
IOrganizationIntegrationRepository integrationRepository,
|
||||
IOrganizationIntegrationConfigurationRepository configurationRepository,
|
||||
[FromKeyedServices(EventIntegrationsCacheConstants.CacheName)] IFusionCache cache,
|
||||
IOrganizationIntegrationConfigurationValidator validator)
|
||||
: IUpdateOrganizationIntegrationConfigurationCommand
|
||||
{
|
||||
public async Task<OrganizationIntegrationConfiguration> UpdateAsync(
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
Guid configurationId,
|
||||
OrganizationIntegrationConfiguration updatedConfiguration)
|
||||
{
|
||||
var integration = await integrationRepository.GetByIdAsync(integrationId);
|
||||
if (integration == null || integration.OrganizationId != organizationId)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
var configuration = await configurationRepository.GetByIdAsync(configurationId);
|
||||
if (configuration is null || configuration.OrganizationIntegrationId != integrationId)
|
||||
{
|
||||
throw new NotFoundException();
|
||||
}
|
||||
if (!validator.ValidateConfiguration(integration.Type, updatedConfiguration))
|
||||
{
|
||||
throw new BadRequestException($"Invalid Configuration and/or Filters for integration type {integration.Type}");
|
||||
}
|
||||
|
||||
updatedConfiguration.Id = configuration.Id;
|
||||
updatedConfiguration.CreationDate = configuration.CreationDate;
|
||||
await configurationRepository.ReplaceAsync(updatedConfiguration);
|
||||
|
||||
// If either old or new EventType is null (wildcard), invalidate all cached results
|
||||
// for the specific integration
|
||||
if (configuration.EventType == null || updatedConfiguration.EventType == null)
|
||||
{
|
||||
// Wildcard involved - invalidate all cached results for this org/integration
|
||||
await cache.RemoveByTagAsync(
|
||||
EventIntegrationsCacheConstants.BuildCacheTagForOrganizationIntegration(
|
||||
organizationId: organizationId,
|
||||
integrationType: integration.Type
|
||||
));
|
||||
|
||||
return updatedConfiguration;
|
||||
}
|
||||
|
||||
// Both are specific event types - invalidate specific cache entries
|
||||
await cache.RemoveAsync(
|
||||
EventIntegrationsCacheConstants.BuildCacheKeyForOrganizationIntegrationConfigurationDetails(
|
||||
organizationId: organizationId,
|
||||
integrationType: integration.Type,
|
||||
eventType: configuration.EventType.Value
|
||||
));
|
||||
|
||||
// If event type changed, also clear the new event type's cache
|
||||
if (configuration.EventType != updatedConfiguration.EventType)
|
||||
{
|
||||
await cache.RemoveAsync(
|
||||
EventIntegrationsCacheConstants.BuildCacheKeyForOrganizationIntegrationConfigurationDetails(
|
||||
organizationId: organizationId,
|
||||
integrationType: integration.Type,
|
||||
eventType: updatedConfiguration.EventType.Value
|
||||
));
|
||||
}
|
||||
|
||||
return updatedConfiguration;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Enums;
|
||||
|
||||
namespace Bit.Core.AdminConsole.Services;
|
||||
|
||||
public interface IOrganizationIntegrationConfigurationValidator
|
||||
{
|
||||
/// <summary>
|
||||
/// Validates that the configuration is valid for the given integration type. The configuration must
|
||||
/// include a Configuration that is valid for the type, valid Filters, and a non-empty Template
|
||||
/// to pass validation.
|
||||
/// </summary>
|
||||
/// <param name="integrationType">The type of integration</param>
|
||||
/// <param name="configuration">The OrganizationIntegrationConfiguration to validate</param>
|
||||
/// <returns>True if valid, false otherwise</returns>
|
||||
bool ValidateConfiguration(IntegrationType integrationType, OrganizationIntegrationConfiguration configuration);
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
using System.Text.Json;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||
using Bit.Core.Enums;
|
||||
|
||||
namespace Bit.Core.AdminConsole.Services;
|
||||
|
||||
public class OrganizationIntegrationConfigurationValidator : IOrganizationIntegrationConfigurationValidator
|
||||
{
|
||||
public bool ValidateConfiguration(IntegrationType integrationType,
|
||||
OrganizationIntegrationConfiguration configuration)
|
||||
{
|
||||
// Validate template is present
|
||||
if (string.IsNullOrWhiteSpace(configuration.Template))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// If Filters are present, they must be valid
|
||||
if (!IsFiltersValid(configuration.Filters))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (integrationType)
|
||||
{
|
||||
case IntegrationType.CloudBillingSync or IntegrationType.Scim:
|
||||
return false;
|
||||
case IntegrationType.Slack:
|
||||
return IsConfigurationValid<SlackIntegrationConfiguration>(configuration.Configuration);
|
||||
case IntegrationType.Webhook:
|
||||
return IsConfigurationValid<WebhookIntegrationConfiguration>(configuration.Configuration);
|
||||
case IntegrationType.Hec:
|
||||
case IntegrationType.Datadog:
|
||||
case IntegrationType.Teams:
|
||||
return configuration.Configuration is null;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsConfigurationValid<T>(string? configuration)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(configuration))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var config = JsonSerializer.Deserialize<T>(configuration);
|
||||
return config is not null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsFiltersValid(string? filters)
|
||||
{
|
||||
if (filters is null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var filterGroup = JsonSerializer.Deserialize<IntegrationFilterGroup>(filters);
|
||||
return filterGroup is not null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,16 +55,16 @@ public static class EventIntegrationsCacheConstants
|
||||
/// Builds a deterministic cache key for an organization's integration configuration details
|
||||
/// <see cref="OrganizationIntegrationConfigurationDetails"/>.
|
||||
/// </summary>
|
||||
/// <param name="organizationId">The unique identifier of the organization to which the user belongs.</param>
|
||||
/// <param name="organizationId">The unique identifier of the organization.</param>
|
||||
/// <param name="integrationType">The <see cref="IntegrationType"/> of the integration.</param>
|
||||
/// <param name="eventType">The <see cref="EventType"/> of the event configured. Can be null to apply to all events.</param>
|
||||
/// <param name="eventType">The specific <see cref="EventType"/> of the event configured.</param>
|
||||
/// <returns>
|
||||
/// A cache key for the configuration details.
|
||||
/// </returns>
|
||||
public static string BuildCacheKeyForOrganizationIntegrationConfigurationDetails(
|
||||
Guid organizationId,
|
||||
IntegrationType integrationType,
|
||||
EventType? eventType
|
||||
EventType eventType
|
||||
) => $"OrganizationIntegrationConfigurationDetails:{organizationId:N}:{integrationType}:{eventType}";
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
using System.Text.Json;
|
||||
using Bit.Api.AdminConsole.Controllers;
|
||||
using Bit.Api.AdminConsole.Controllers;
|
||||
using Bit.Api.AdminConsole.Models.Request.Organizations;
|
||||
using Bit.Api.AdminConsole.Models.Response.Organizations;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NSubstitute;
|
||||
using NSubstitute.ReturnsExtensions;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Api.Test.AdminConsole.Controllers;
|
||||
@@ -25,823 +21,191 @@ public class OrganizationIntegrationsConfigurationControllerTests
|
||||
public async Task DeleteAsync_AllParamsProvided_Succeeds(
|
||||
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
||||
Guid organizationId,
|
||||
OrganizationIntegration organizationIntegration,
|
||||
OrganizationIntegrationConfiguration organizationIntegrationConfiguration)
|
||||
Guid integrationId,
|
||||
Guid configurationId)
|
||||
{
|
||||
organizationIntegration.OrganizationId = organizationId;
|
||||
organizationIntegrationConfiguration.OrganizationIntegrationId = organizationIntegration.Id;
|
||||
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.OrganizationOwner(organizationId)
|
||||
.Returns(true);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(organizationIntegration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(organizationIntegrationConfiguration);
|
||||
|
||||
await sutProvider.Sut.DeleteAsync(organizationId, organizationIntegration.Id, organizationIntegrationConfiguration.Id);
|
||||
await sutProvider.Sut.DeleteAsync(organizationId, integrationId, configurationId);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().Received(1)
|
||||
.GetByIdAsync(organizationIntegration.Id);
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.GetByIdAsync(organizationIntegrationConfiguration.Id);
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.DeleteAsync(organizationIntegrationConfiguration);
|
||||
await sutProvider.GetDependency<IDeleteOrganizationIntegrationConfigurationCommand>().Received(1)
|
||||
.DeleteAsync(organizationId, integrationId, configurationId);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
[Obsolete("Obsolete")]
|
||||
public async Task PostDeleteAsync_AllParamsProvided_Succeeds(
|
||||
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
||||
Guid organizationId,
|
||||
OrganizationIntegration organizationIntegration,
|
||||
OrganizationIntegrationConfiguration organizationIntegrationConfiguration)
|
||||
{
|
||||
organizationIntegration.OrganizationId = organizationId;
|
||||
organizationIntegrationConfiguration.OrganizationIntegrationId = organizationIntegration.Id;
|
||||
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.OrganizationOwner(organizationId)
|
||||
.Returns(true);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(organizationIntegration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(organizationIntegrationConfiguration);
|
||||
|
||||
await sutProvider.Sut.PostDeleteAsync(organizationId, organizationIntegration.Id, organizationIntegrationConfiguration.Id);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().Received(1)
|
||||
.GetByIdAsync(organizationIntegration.Id);
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.GetByIdAsync(organizationIntegrationConfiguration.Id);
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.DeleteAsync(organizationIntegrationConfiguration);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task DeleteAsync_IntegrationConfigurationDoesNotExist_ThrowsNotFound(
|
||||
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
||||
Guid organizationId,
|
||||
OrganizationIntegration organizationIntegration)
|
||||
{
|
||||
organizationIntegration.OrganizationId = organizationId;
|
||||
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.OrganizationOwner(organizationId)
|
||||
.Returns(true);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(organizationIntegration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.ReturnsNull();
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.DeleteAsync(organizationId, Guid.Empty, Guid.Empty));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task DeleteAsync_IntegrationDoesNotExist_ThrowsNotFound(
|
||||
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
||||
Guid organizationId)
|
||||
Guid integrationId,
|
||||
Guid configurationId)
|
||||
{
|
||||
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.OrganizationOwner(organizationId)
|
||||
.Returns(true);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.ReturnsNull();
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.DeleteAsync(organizationId, Guid.Empty, Guid.Empty));
|
||||
}
|
||||
await sutProvider.Sut.PostDeleteAsync(organizationId, integrationId, configurationId);
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task DeleteAsync_IntegrationDoesNotBelongToOrganization_ThrowsNotFound(
|
||||
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
||||
Guid organizationId,
|
||||
OrganizationIntegration organizationIntegration)
|
||||
{
|
||||
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.OrganizationOwner(organizationId)
|
||||
.Returns(true);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(organizationIntegration);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.DeleteAsync(organizationId, organizationIntegration.Id, Guid.Empty));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task DeleteAsync_IntegrationConfigDoesNotBelongToIntegration_ThrowsNotFound(
|
||||
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
||||
Guid organizationId,
|
||||
OrganizationIntegration organizationIntegration,
|
||||
OrganizationIntegrationConfiguration organizationIntegrationConfiguration)
|
||||
{
|
||||
organizationIntegration.OrganizationId = organizationId;
|
||||
organizationIntegrationConfiguration.OrganizationIntegrationId = Guid.Empty;
|
||||
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.OrganizationOwner(organizationId)
|
||||
.Returns(true);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(organizationIntegration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(organizationIntegrationConfiguration);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.DeleteAsync(organizationId, organizationIntegration.Id, Guid.Empty));
|
||||
await sutProvider.GetDependency<IDeleteOrganizationIntegrationConfigurationCommand>().Received(1)
|
||||
.DeleteAsync(organizationId, integrationId, configurationId);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task DeleteAsync_UserIsNotOrganizationAdmin_ThrowsNotFound(
|
||||
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
||||
Guid organizationId)
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
Guid configurationId)
|
||||
{
|
||||
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.OrganizationOwner(organizationId)
|
||||
.Returns(false);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.DeleteAsync(organizationId, Guid.Empty, Guid.Empty));
|
||||
await Assert.ThrowsAsync<NotFoundException>(async () =>
|
||||
await sutProvider.Sut.DeleteAsync(organizationId, integrationId, configurationId));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetAsync_ConfigurationsExist_Succeeds(
|
||||
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
||||
Guid organizationId,
|
||||
OrganizationIntegration organizationIntegration,
|
||||
List<OrganizationIntegrationConfiguration> organizationIntegrationConfigurations)
|
||||
Guid integrationId,
|
||||
List<OrganizationIntegrationConfiguration> configurations)
|
||||
{
|
||||
organizationIntegration.OrganizationId = organizationId;
|
||||
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.OrganizationOwner(organizationId)
|
||||
.Returns(true);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(organizationIntegration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetManyByIntegrationAsync(Arg.Any<Guid>())
|
||||
.Returns(organizationIntegrationConfigurations);
|
||||
sutProvider.GetDependency<IGetOrganizationIntegrationConfigurationsQuery>()
|
||||
.GetManyByIntegrationAsync(organizationId, integrationId)
|
||||
.Returns(configurations);
|
||||
|
||||
var result = await sutProvider.Sut.GetAsync(organizationId, integrationId);
|
||||
|
||||
var result = await sutProvider.Sut.GetAsync(organizationId, organizationIntegration.Id);
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(organizationIntegrationConfigurations.Count, result.Count);
|
||||
Assert.Equal(configurations.Count, result.Count);
|
||||
Assert.All(result, r => Assert.IsType<OrganizationIntegrationConfigurationResponseModel>(r));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().Received(1)
|
||||
.GetByIdAsync(organizationIntegration.Id);
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.GetManyByIntegrationAsync(organizationIntegration.Id);
|
||||
await sutProvider.GetDependency<IGetOrganizationIntegrationConfigurationsQuery>().Received(1)
|
||||
.GetManyByIntegrationAsync(organizationId, integrationId);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetAsync_NoConfigurationsExist_ReturnsEmptyList(
|
||||
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
||||
Guid organizationId,
|
||||
OrganizationIntegration organizationIntegration)
|
||||
Guid integrationId)
|
||||
{
|
||||
organizationIntegration.OrganizationId = organizationId;
|
||||
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.OrganizationOwner(organizationId)
|
||||
.Returns(true);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(organizationIntegration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetManyByIntegrationAsync(Arg.Any<Guid>())
|
||||
sutProvider.GetDependency<IGetOrganizationIntegrationConfigurationsQuery>()
|
||||
.GetManyByIntegrationAsync(organizationId, integrationId)
|
||||
.Returns([]);
|
||||
|
||||
var result = await sutProvider.Sut.GetAsync(organizationId, organizationIntegration.Id);
|
||||
var result = await sutProvider.Sut.GetAsync(organizationId, integrationId);
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Empty(result);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().Received(1)
|
||||
.GetByIdAsync(organizationIntegration.Id);
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.GetManyByIntegrationAsync(organizationIntegration.Id);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetAsync_IntegrationDoesNotExist_ThrowsNotFound(
|
||||
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
||||
Guid organizationId)
|
||||
{
|
||||
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.OrganizationOwner(organizationId)
|
||||
.Returns(true);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.ReturnsNull();
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.GetAsync(organizationId, Guid.NewGuid()));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetAsync_IntegrationDoesNotBelongToOrganization_ThrowsNotFound(
|
||||
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
||||
Guid organizationId,
|
||||
OrganizationIntegration organizationIntegration)
|
||||
{
|
||||
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.OrganizationOwner(organizationId)
|
||||
.Returns(true);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(organizationIntegration);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.GetAsync(organizationId, organizationIntegration.Id));
|
||||
await sutProvider.GetDependency<IGetOrganizationIntegrationConfigurationsQuery>().Received(1)
|
||||
.GetManyByIntegrationAsync(organizationId, integrationId);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetAsync_UserIsNotOrganizationAdmin_ThrowsNotFound(
|
||||
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
||||
Guid organizationId)
|
||||
Guid organizationId,
|
||||
Guid integrationId)
|
||||
{
|
||||
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.OrganizationOwner(organizationId)
|
||||
.Returns(false);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.GetAsync(organizationId, Guid.NewGuid()));
|
||||
await Assert.ThrowsAsync<NotFoundException>(async () =>
|
||||
await sutProvider.Sut.GetAsync(organizationId, integrationId));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task PostAsync_AllParamsProvided_Slack_Succeeds(
|
||||
public async Task PostAsync_AllParamsProvided_Succeeds(
|
||||
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
||||
Guid organizationId,
|
||||
OrganizationIntegration organizationIntegration,
|
||||
OrganizationIntegrationConfiguration organizationIntegrationConfiguration,
|
||||
Guid integrationId,
|
||||
OrganizationIntegrationConfiguration configuration,
|
||||
OrganizationIntegrationConfigurationRequestModel model)
|
||||
{
|
||||
organizationIntegration.OrganizationId = organizationId;
|
||||
organizationIntegration.Type = IntegrationType.Slack;
|
||||
var slackConfig = new SlackIntegrationConfiguration(ChannelId: "C123456");
|
||||
model.Configuration = JsonSerializer.Serialize(slackConfig);
|
||||
model.Template = "Template String";
|
||||
model.Filters = null;
|
||||
|
||||
var expected = new OrganizationIntegrationConfigurationResponseModel(organizationIntegrationConfiguration);
|
||||
|
||||
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.OrganizationOwner(organizationId)
|
||||
.Returns(true);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(organizationIntegration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.CreateAsync(Arg.Any<OrganizationIntegrationConfiguration>())
|
||||
.Returns(organizationIntegrationConfiguration);
|
||||
var createResponse = await sutProvider.Sut.CreateAsync(organizationId, organizationIntegration.Id, model);
|
||||
sutProvider.GetDependency<ICreateOrganizationIntegrationConfigurationCommand>()
|
||||
.CreateAsync(organizationId, integrationId, Arg.Any<OrganizationIntegrationConfiguration>())
|
||||
.Returns(configuration);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.CreateAsync(Arg.Any<OrganizationIntegrationConfiguration>());
|
||||
var createResponse = await sutProvider.Sut.CreateAsync(organizationId, integrationId, model);
|
||||
|
||||
await sutProvider.GetDependency<ICreateOrganizationIntegrationConfigurationCommand>().Received(1)
|
||||
.CreateAsync(organizationId, integrationId, Arg.Any<OrganizationIntegrationConfiguration>());
|
||||
Assert.IsType<OrganizationIntegrationConfigurationResponseModel>(createResponse);
|
||||
Assert.Equal(expected.Id, createResponse.Id);
|
||||
Assert.Equal(expected.Configuration, createResponse.Configuration);
|
||||
Assert.Equal(expected.EventType, createResponse.EventType);
|
||||
Assert.Equal(expected.Filters, createResponse.Filters);
|
||||
Assert.Equal(expected.Template, createResponse.Template);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task PostAsync_AllParamsProvided_Webhook_Succeeds(
|
||||
public async Task PostAsync_UserIsNotOrganizationAdmin_ThrowsNotFound(
|
||||
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
||||
Guid organizationId,
|
||||
OrganizationIntegration organizationIntegration,
|
||||
OrganizationIntegrationConfiguration organizationIntegrationConfiguration,
|
||||
OrganizationIntegrationConfigurationRequestModel model)
|
||||
{
|
||||
organizationIntegration.OrganizationId = organizationId;
|
||||
organizationIntegration.Type = IntegrationType.Webhook;
|
||||
var webhookConfig = new WebhookIntegrationConfiguration(Uri: new Uri("https://localhost"), Scheme: "Bearer", Token: "AUTH-TOKEN");
|
||||
model.Configuration = JsonSerializer.Serialize(webhookConfig);
|
||||
model.Template = "Template String";
|
||||
model.Filters = null;
|
||||
|
||||
var expected = new OrganizationIntegrationConfigurationResponseModel(organizationIntegrationConfiguration);
|
||||
|
||||
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.OrganizationOwner(organizationId)
|
||||
.Returns(true);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(organizationIntegration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.CreateAsync(Arg.Any<OrganizationIntegrationConfiguration>())
|
||||
.Returns(organizationIntegrationConfiguration);
|
||||
var createResponse = await sutProvider.Sut.CreateAsync(organizationId, organizationIntegration.Id, model);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.CreateAsync(Arg.Any<OrganizationIntegrationConfiguration>());
|
||||
Assert.IsType<OrganizationIntegrationConfigurationResponseModel>(createResponse);
|
||||
Assert.Equal(expected.Id, createResponse.Id);
|
||||
Assert.Equal(expected.Configuration, createResponse.Configuration);
|
||||
Assert.Equal(expected.EventType, createResponse.EventType);
|
||||
Assert.Equal(expected.Filters, createResponse.Filters);
|
||||
Assert.Equal(expected.Template, createResponse.Template);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task PostAsync_OnlyUrlProvided_Webhook_Succeeds(
|
||||
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
||||
Guid organizationId,
|
||||
OrganizationIntegration organizationIntegration,
|
||||
OrganizationIntegrationConfiguration organizationIntegrationConfiguration,
|
||||
OrganizationIntegrationConfigurationRequestModel model)
|
||||
{
|
||||
organizationIntegration.OrganizationId = organizationId;
|
||||
organizationIntegration.Type = IntegrationType.Webhook;
|
||||
var webhookConfig = new WebhookIntegrationConfiguration(Uri: new Uri("https://localhost"));
|
||||
model.Configuration = JsonSerializer.Serialize(webhookConfig);
|
||||
model.Template = "Template String";
|
||||
model.Filters = null;
|
||||
|
||||
var expected = new OrganizationIntegrationConfigurationResponseModel(organizationIntegrationConfiguration);
|
||||
|
||||
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.OrganizationOwner(organizationId)
|
||||
.Returns(true);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(organizationIntegration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.CreateAsync(Arg.Any<OrganizationIntegrationConfiguration>())
|
||||
.Returns(organizationIntegrationConfiguration);
|
||||
var createResponse = await sutProvider.Sut.CreateAsync(organizationId, organizationIntegration.Id, model);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.CreateAsync(Arg.Any<OrganizationIntegrationConfiguration>());
|
||||
Assert.IsType<OrganizationIntegrationConfigurationResponseModel>(createResponse);
|
||||
Assert.Equal(expected.Id, createResponse.Id);
|
||||
Assert.Equal(expected.Configuration, createResponse.Configuration);
|
||||
Assert.Equal(expected.EventType, createResponse.EventType);
|
||||
Assert.Equal(expected.Filters, createResponse.Filters);
|
||||
Assert.Equal(expected.Template, createResponse.Template);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task PostAsync_IntegrationTypeCloudBillingSync_ThrowsBadRequestException(
|
||||
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
||||
Guid organizationId,
|
||||
OrganizationIntegration organizationIntegration,
|
||||
OrganizationIntegrationConfiguration organizationIntegrationConfiguration,
|
||||
OrganizationIntegrationConfigurationRequestModel model)
|
||||
{
|
||||
organizationIntegration.OrganizationId = organizationId;
|
||||
organizationIntegration.Type = IntegrationType.CloudBillingSync;
|
||||
|
||||
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.OrganizationOwner(organizationId)
|
||||
.Returns(true);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(organizationIntegration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.CreateAsync(Arg.Any<OrganizationIntegrationConfiguration>())
|
||||
.Returns(organizationIntegrationConfiguration);
|
||||
|
||||
await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.CreateAsync(
|
||||
organizationId,
|
||||
organizationIntegration.Id,
|
||||
model));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task PostAsync_IntegrationTypeScim_ThrowsBadRequestException(
|
||||
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
||||
Guid organizationId,
|
||||
OrganizationIntegration organizationIntegration,
|
||||
OrganizationIntegrationConfiguration organizationIntegrationConfiguration,
|
||||
OrganizationIntegrationConfigurationRequestModel model)
|
||||
{
|
||||
organizationIntegration.OrganizationId = organizationId;
|
||||
organizationIntegration.Type = IntegrationType.Scim;
|
||||
|
||||
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.OrganizationOwner(organizationId)
|
||||
.Returns(true);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(organizationIntegration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.CreateAsync(Arg.Any<OrganizationIntegrationConfiguration>())
|
||||
.Returns(organizationIntegrationConfiguration);
|
||||
|
||||
await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.CreateAsync(
|
||||
organizationId,
|
||||
organizationIntegration.Id,
|
||||
model));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task PostAsync_IntegrationDoesNotExist_ThrowsNotFound(
|
||||
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
||||
Guid organizationId)
|
||||
{
|
||||
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.OrganizationOwner(organizationId)
|
||||
.Returns(true);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.ReturnsNull();
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.CreateAsync(
|
||||
organizationId,
|
||||
Guid.Empty,
|
||||
new OrganizationIntegrationConfigurationRequestModel()));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task PostAsync_IntegrationDoesNotBelongToOrganization_ThrowsNotFound(
|
||||
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
||||
Guid organizationId,
|
||||
OrganizationIntegration organizationIntegration)
|
||||
{
|
||||
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.OrganizationOwner(organizationId)
|
||||
.Returns(true);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(organizationIntegration);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.CreateAsync(
|
||||
organizationId,
|
||||
organizationIntegration.Id,
|
||||
new OrganizationIntegrationConfigurationRequestModel()));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task PostAsync_InvalidConfiguration_ThrowsBadRequestException(
|
||||
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
||||
Guid organizationId,
|
||||
OrganizationIntegration organizationIntegration,
|
||||
OrganizationIntegrationConfiguration organizationIntegrationConfiguration,
|
||||
OrganizationIntegrationConfigurationRequestModel model)
|
||||
{
|
||||
organizationIntegration.OrganizationId = organizationId;
|
||||
organizationIntegration.Type = IntegrationType.Webhook;
|
||||
model.Configuration = null;
|
||||
model.Template = "Template String";
|
||||
|
||||
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.OrganizationOwner(organizationId)
|
||||
.Returns(true);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(organizationIntegration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.CreateAsync(Arg.Any<OrganizationIntegrationConfiguration>())
|
||||
.Returns(organizationIntegrationConfiguration);
|
||||
|
||||
await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.CreateAsync(
|
||||
organizationId,
|
||||
organizationIntegration.Id,
|
||||
model));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task PostAsync_InvalidTemplate_ThrowsBadRequestException(
|
||||
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
||||
Guid organizationId,
|
||||
OrganizationIntegration organizationIntegration,
|
||||
OrganizationIntegrationConfiguration organizationIntegrationConfiguration,
|
||||
OrganizationIntegrationConfigurationRequestModel model)
|
||||
{
|
||||
organizationIntegration.OrganizationId = organizationId;
|
||||
organizationIntegration.Type = IntegrationType.Webhook;
|
||||
var webhookConfig = new WebhookIntegrationConfiguration(Uri: new Uri("https://localhost"), Scheme: "Bearer", Token: "AUTH-TOKEN");
|
||||
model.Configuration = JsonSerializer.Serialize(webhookConfig);
|
||||
model.Template = null;
|
||||
|
||||
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.OrganizationOwner(organizationId)
|
||||
.Returns(true);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(organizationIntegration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.CreateAsync(Arg.Any<OrganizationIntegrationConfiguration>())
|
||||
.Returns(organizationIntegrationConfiguration);
|
||||
|
||||
await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.CreateAsync(
|
||||
organizationId,
|
||||
organizationIntegration.Id,
|
||||
model));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task PostAsync_UserIsNotOrganizationAdmin_ThrowsNotFound(SutProvider<OrganizationIntegrationConfigurationController> sutProvider, Guid organizationId)
|
||||
Guid integrationId)
|
||||
{
|
||||
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.OrganizationOwner(organizationId)
|
||||
.Returns(false);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.CreateAsync(organizationId, Guid.Empty, new OrganizationIntegrationConfigurationRequestModel()));
|
||||
await Assert.ThrowsAsync<NotFoundException>(async () =>
|
||||
await sutProvider.Sut.CreateAsync(organizationId, integrationId, new OrganizationIntegrationConfigurationRequestModel()));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_AllParamsProvided_Slack_Succeeds(
|
||||
public async Task UpdateAsync_AllParamsProvided_Succeeds(
|
||||
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
||||
Guid organizationId,
|
||||
OrganizationIntegration organizationIntegration,
|
||||
OrganizationIntegrationConfiguration organizationIntegrationConfiguration,
|
||||
Guid integrationId,
|
||||
Guid configurationId,
|
||||
OrganizationIntegrationConfiguration configuration,
|
||||
OrganizationIntegrationConfigurationRequestModel model)
|
||||
{
|
||||
organizationIntegration.OrganizationId = organizationId;
|
||||
organizationIntegrationConfiguration.OrganizationIntegrationId = organizationIntegration.Id;
|
||||
organizationIntegration.Type = IntegrationType.Slack;
|
||||
var slackConfig = new SlackIntegrationConfiguration(ChannelId: "C123456");
|
||||
model.Configuration = JsonSerializer.Serialize(slackConfig);
|
||||
model.Template = "Template String";
|
||||
model.Filters = null;
|
||||
|
||||
var expected = new OrganizationIntegrationConfigurationResponseModel(model.ToOrganizationIntegrationConfiguration(organizationIntegrationConfiguration));
|
||||
|
||||
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.OrganizationOwner(organizationId)
|
||||
.Returns(true);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(organizationIntegration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(organizationIntegrationConfiguration);
|
||||
var updateResponse = await sutProvider.Sut.UpdateAsync(
|
||||
organizationId,
|
||||
organizationIntegration.Id,
|
||||
organizationIntegrationConfiguration.Id,
|
||||
model);
|
||||
sutProvider.GetDependency<IUpdateOrganizationIntegrationConfigurationCommand>()
|
||||
.UpdateAsync(organizationId, integrationId, configurationId, Arg.Any<OrganizationIntegrationConfiguration>())
|
||||
.Returns(configuration);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.ReplaceAsync(Arg.Any<OrganizationIntegrationConfiguration>());
|
||||
var updateResponse = await sutProvider.Sut.UpdateAsync(organizationId, integrationId, configurationId, model);
|
||||
|
||||
await sutProvider.GetDependency<IUpdateOrganizationIntegrationConfigurationCommand>().Received(1)
|
||||
.UpdateAsync(organizationId, integrationId, configurationId, Arg.Any<OrganizationIntegrationConfiguration>());
|
||||
Assert.IsType<OrganizationIntegrationConfigurationResponseModel>(updateResponse);
|
||||
Assert.Equal(expected.Id, updateResponse.Id);
|
||||
Assert.Equal(expected.Configuration, updateResponse.Configuration);
|
||||
Assert.Equal(expected.EventType, updateResponse.EventType);
|
||||
Assert.Equal(expected.Filters, updateResponse.Filters);
|
||||
Assert.Equal(expected.Template, updateResponse.Template);
|
||||
}
|
||||
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_AllParamsProvided_Webhook_Succeeds(
|
||||
public async Task UpdateAsync_UserIsNotOrganizationAdmin_ThrowsNotFound(
|
||||
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
||||
Guid organizationId,
|
||||
OrganizationIntegration organizationIntegration,
|
||||
OrganizationIntegrationConfiguration organizationIntegrationConfiguration,
|
||||
OrganizationIntegrationConfigurationRequestModel model)
|
||||
{
|
||||
organizationIntegration.OrganizationId = organizationId;
|
||||
organizationIntegrationConfiguration.OrganizationIntegrationId = organizationIntegration.Id;
|
||||
organizationIntegration.Type = IntegrationType.Webhook;
|
||||
var webhookConfig = new WebhookIntegrationConfiguration(Uri: new Uri("https://localhost"), Scheme: "Bearer", Token: "AUTH-TOKEN");
|
||||
model.Configuration = JsonSerializer.Serialize(webhookConfig);
|
||||
model.Template = "Template String";
|
||||
model.Filters = null;
|
||||
|
||||
var expected = new OrganizationIntegrationConfigurationResponseModel(model.ToOrganizationIntegrationConfiguration(organizationIntegrationConfiguration));
|
||||
|
||||
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.OrganizationOwner(organizationId)
|
||||
.Returns(true);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(organizationIntegration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(organizationIntegrationConfiguration);
|
||||
var updateResponse = await sutProvider.Sut.UpdateAsync(
|
||||
organizationId,
|
||||
organizationIntegration.Id,
|
||||
organizationIntegrationConfiguration.Id,
|
||||
model);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.ReplaceAsync(Arg.Any<OrganizationIntegrationConfiguration>());
|
||||
Assert.IsType<OrganizationIntegrationConfigurationResponseModel>(updateResponse);
|
||||
Assert.Equal(expected.Id, updateResponse.Id);
|
||||
Assert.Equal(expected.Configuration, updateResponse.Configuration);
|
||||
Assert.Equal(expected.EventType, updateResponse.EventType);
|
||||
Assert.Equal(expected.Filters, updateResponse.Filters);
|
||||
Assert.Equal(expected.Template, updateResponse.Template);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_OnlyUrlProvided_Webhook_Succeeds(
|
||||
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
||||
Guid organizationId,
|
||||
OrganizationIntegration organizationIntegration,
|
||||
OrganizationIntegrationConfiguration organizationIntegrationConfiguration,
|
||||
OrganizationIntegrationConfigurationRequestModel model)
|
||||
{
|
||||
organizationIntegration.OrganizationId = organizationId;
|
||||
organizationIntegrationConfiguration.OrganizationIntegrationId = organizationIntegration.Id;
|
||||
organizationIntegration.Type = IntegrationType.Webhook;
|
||||
var webhookConfig = new WebhookIntegrationConfiguration(Uri: new Uri("https://localhost"));
|
||||
model.Configuration = JsonSerializer.Serialize(webhookConfig);
|
||||
model.Template = "Template String";
|
||||
model.Filters = null;
|
||||
|
||||
var expected = new OrganizationIntegrationConfigurationResponseModel(model.ToOrganizationIntegrationConfiguration(organizationIntegrationConfiguration));
|
||||
|
||||
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.OrganizationOwner(organizationId)
|
||||
.Returns(true);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(organizationIntegration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(organizationIntegrationConfiguration);
|
||||
var updateResponse = await sutProvider.Sut.UpdateAsync(
|
||||
organizationId,
|
||||
organizationIntegration.Id,
|
||||
organizationIntegrationConfiguration.Id,
|
||||
model);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.ReplaceAsync(Arg.Any<OrganizationIntegrationConfiguration>());
|
||||
Assert.IsType<OrganizationIntegrationConfigurationResponseModel>(updateResponse);
|
||||
Assert.Equal(expected.Id, updateResponse.Id);
|
||||
Assert.Equal(expected.Configuration, updateResponse.Configuration);
|
||||
Assert.Equal(expected.EventType, updateResponse.EventType);
|
||||
Assert.Equal(expected.Filters, updateResponse.Filters);
|
||||
Assert.Equal(expected.Template, updateResponse.Template);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_IntegrationConfigurationDoesNotExist_ThrowsNotFound(
|
||||
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
||||
Guid organizationId,
|
||||
OrganizationIntegration organizationIntegration,
|
||||
OrganizationIntegrationConfigurationRequestModel model)
|
||||
{
|
||||
organizationIntegration.OrganizationId = organizationId;
|
||||
organizationIntegration.Type = IntegrationType.Webhook;
|
||||
var webhookConfig = new WebhookIntegrationConfiguration(Uri: new Uri("https://localhost"), Scheme: "Bearer", Token: "AUTH-TOKEN");
|
||||
model.Configuration = JsonSerializer.Serialize(webhookConfig);
|
||||
model.Template = "Template String";
|
||||
model.Filters = null;
|
||||
|
||||
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.OrganizationOwner(organizationId)
|
||||
.Returns(true);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(organizationIntegration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.ReturnsNull();
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.UpdateAsync(
|
||||
organizationId,
|
||||
organizationIntegration.Id,
|
||||
Guid.Empty,
|
||||
model));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_IntegrationDoesNotExist_ThrowsNotFound(
|
||||
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
||||
Guid organizationId)
|
||||
{
|
||||
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.OrganizationOwner(organizationId)
|
||||
.Returns(true);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.ReturnsNull();
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.UpdateAsync(
|
||||
organizationId,
|
||||
Guid.Empty,
|
||||
Guid.Empty,
|
||||
new OrganizationIntegrationConfigurationRequestModel()));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_IntegrationDoesNotBelongToOrganization_ThrowsNotFound(
|
||||
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
||||
Guid organizationId,
|
||||
OrganizationIntegration organizationIntegration)
|
||||
{
|
||||
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.OrganizationOwner(organizationId)
|
||||
.Returns(true);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(organizationIntegration);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.UpdateAsync(
|
||||
organizationId,
|
||||
organizationIntegration.Id,
|
||||
Guid.Empty,
|
||||
new OrganizationIntegrationConfigurationRequestModel()));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_InvalidConfiguration_ThrowsBadRequestException(
|
||||
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
||||
Guid organizationId,
|
||||
OrganizationIntegration organizationIntegration,
|
||||
OrganizationIntegrationConfiguration organizationIntegrationConfiguration,
|
||||
OrganizationIntegrationConfigurationRequestModel model)
|
||||
{
|
||||
organizationIntegration.OrganizationId = organizationId;
|
||||
organizationIntegrationConfiguration.OrganizationIntegrationId = organizationIntegration.Id;
|
||||
organizationIntegration.Type = IntegrationType.Slack;
|
||||
model.Configuration = null;
|
||||
model.Template = "Template String";
|
||||
|
||||
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.OrganizationOwner(organizationId)
|
||||
.Returns(true);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(organizationIntegration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(organizationIntegrationConfiguration);
|
||||
|
||||
await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.UpdateAsync(
|
||||
organizationId,
|
||||
organizationIntegration.Id,
|
||||
organizationIntegrationConfiguration.Id,
|
||||
model));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_InvalidTemplate_ThrowsBadRequestException(
|
||||
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
||||
Guid organizationId,
|
||||
OrganizationIntegration organizationIntegration,
|
||||
OrganizationIntegrationConfiguration organizationIntegrationConfiguration,
|
||||
OrganizationIntegrationConfigurationRequestModel model)
|
||||
{
|
||||
organizationIntegration.OrganizationId = organizationId;
|
||||
organizationIntegrationConfiguration.OrganizationIntegrationId = organizationIntegration.Id;
|
||||
organizationIntegration.Type = IntegrationType.Slack;
|
||||
var slackConfig = new SlackIntegrationConfiguration(ChannelId: "C123456");
|
||||
model.Configuration = JsonSerializer.Serialize(slackConfig);
|
||||
model.Template = null;
|
||||
|
||||
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.OrganizationOwner(organizationId)
|
||||
.Returns(true);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(organizationIntegration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetByIdAsync(Arg.Any<Guid>())
|
||||
.Returns(organizationIntegrationConfiguration);
|
||||
|
||||
await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.UpdateAsync(
|
||||
organizationId,
|
||||
organizationIntegration.Id,
|
||||
organizationIntegrationConfiguration.Id,
|
||||
model));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_UserIsNotOrganizationAdmin_ThrowsNotFound(SutProvider<OrganizationIntegrationConfigurationController> sutProvider, Guid organizationId)
|
||||
Guid integrationId,
|
||||
Guid configurationId)
|
||||
{
|
||||
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
|
||||
sutProvider.GetDependency<ICurrentContext>()
|
||||
.OrganizationOwner(organizationId)
|
||||
.Returns(false);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.UpdateAsync(
|
||||
organizationId,
|
||||
Guid.Empty,
|
||||
Guid.Empty,
|
||||
new OrganizationIntegrationConfigurationRequestModel()));
|
||||
await Assert.ThrowsAsync<NotFoundException>(async () =>
|
||||
await sutProvider.Sut.UpdateAsync(organizationId, integrationId, configurationId, new OrganizationIntegrationConfigurationRequestModel()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,248 +0,0 @@
|
||||
using System.Text.Json;
|
||||
using Bit.Api.AdminConsole.Models.Request.Organizations;
|
||||
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||
using Bit.Core.Enums;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Api.Test.AdminConsole.Models.Request.Organizations;
|
||||
|
||||
public class OrganizationIntegrationConfigurationRequestModelTests
|
||||
{
|
||||
[Fact]
|
||||
public void IsValidForType_CloudBillingSyncIntegration_ReturnsFalse()
|
||||
{
|
||||
var model = new OrganizationIntegrationConfigurationRequestModel
|
||||
{
|
||||
Configuration = "{}",
|
||||
Template = "template"
|
||||
};
|
||||
|
||||
Assert.False(condition: model.IsValidForType(IntegrationType.CloudBillingSync));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(data: null)]
|
||||
[InlineData(data: "")]
|
||||
[InlineData(data: " ")]
|
||||
public void IsValidForType_EmptyConfiguration_ReturnsFalse(string? config)
|
||||
{
|
||||
var model = new OrganizationIntegrationConfigurationRequestModel
|
||||
{
|
||||
Configuration = config,
|
||||
Template = "template"
|
||||
};
|
||||
|
||||
Assert.False(condition: model.IsValidForType(IntegrationType.Slack));
|
||||
Assert.False(condition: model.IsValidForType(IntegrationType.Webhook));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(data: "")]
|
||||
[InlineData(data: " ")]
|
||||
public void IsValidForType_EmptyNonNullConfiguration_ReturnsFalse(string? config)
|
||||
{
|
||||
var model = new OrganizationIntegrationConfigurationRequestModel
|
||||
{
|
||||
Configuration = config,
|
||||
Template = "template"
|
||||
};
|
||||
|
||||
Assert.False(condition: model.IsValidForType(IntegrationType.Hec));
|
||||
Assert.False(condition: model.IsValidForType(IntegrationType.Datadog));
|
||||
Assert.False(condition: model.IsValidForType(IntegrationType.Teams));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValidForType_NullConfiguration_ReturnsTrue()
|
||||
{
|
||||
var model = new OrganizationIntegrationConfigurationRequestModel
|
||||
{
|
||||
Configuration = null,
|
||||
Template = "template"
|
||||
};
|
||||
|
||||
Assert.True(condition: model.IsValidForType(IntegrationType.Hec));
|
||||
Assert.True(condition: model.IsValidForType(IntegrationType.Datadog));
|
||||
Assert.True(condition: model.IsValidForType(IntegrationType.Teams));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(data: null)]
|
||||
[InlineData(data: "")]
|
||||
[InlineData(data: " ")]
|
||||
public void IsValidForType_EmptyTemplate_ReturnsFalse(string? template)
|
||||
{
|
||||
var config = JsonSerializer.Serialize(value: new WebhookIntegrationConfiguration(
|
||||
Uri: new Uri("https://localhost"),
|
||||
Scheme: "Bearer",
|
||||
Token: "AUTH-TOKEN"));
|
||||
var model = new OrganizationIntegrationConfigurationRequestModel
|
||||
{
|
||||
Configuration = config,
|
||||
Template = template
|
||||
};
|
||||
|
||||
Assert.False(condition: model.IsValidForType(IntegrationType.Slack));
|
||||
Assert.False(condition: model.IsValidForType(IntegrationType.Webhook));
|
||||
Assert.False(condition: model.IsValidForType(IntegrationType.Hec));
|
||||
Assert.False(condition: model.IsValidForType(IntegrationType.Datadog));
|
||||
Assert.False(condition: model.IsValidForType(IntegrationType.Teams));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValidForType_InvalidJsonConfiguration_ReturnsFalse()
|
||||
{
|
||||
var model = new OrganizationIntegrationConfigurationRequestModel
|
||||
{
|
||||
Configuration = "{not valid json}",
|
||||
Template = "template"
|
||||
};
|
||||
|
||||
Assert.False(condition: model.IsValidForType(IntegrationType.Slack));
|
||||
Assert.False(condition: model.IsValidForType(IntegrationType.Webhook));
|
||||
Assert.False(condition: model.IsValidForType(IntegrationType.Hec));
|
||||
Assert.False(condition: model.IsValidForType(IntegrationType.Datadog));
|
||||
Assert.False(condition: model.IsValidForType(IntegrationType.Teams));
|
||||
}
|
||||
|
||||
|
||||
[Fact]
|
||||
public void IsValidForType_InvalidJsonFilters_ReturnsFalse()
|
||||
{
|
||||
var config = JsonSerializer.Serialize(new WebhookIntegrationConfiguration(Uri: new Uri("https://example.com")));
|
||||
var model = new OrganizationIntegrationConfigurationRequestModel
|
||||
{
|
||||
Configuration = config,
|
||||
Filters = "{Not valid json",
|
||||
Template = "template"
|
||||
};
|
||||
|
||||
Assert.False(model.IsValidForType(IntegrationType.Webhook));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValidForType_ScimIntegration_ReturnsFalse()
|
||||
{
|
||||
var model = new OrganizationIntegrationConfigurationRequestModel
|
||||
{
|
||||
Configuration = "{}",
|
||||
Template = "template"
|
||||
};
|
||||
|
||||
Assert.False(condition: model.IsValidForType(IntegrationType.Scim));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValidForType_ValidSlackConfiguration_ReturnsTrue()
|
||||
{
|
||||
var config = JsonSerializer.Serialize(value: new SlackIntegrationConfiguration(ChannelId: "C12345"));
|
||||
|
||||
var model = new OrganizationIntegrationConfigurationRequestModel
|
||||
{
|
||||
Configuration = config,
|
||||
Template = "template"
|
||||
};
|
||||
|
||||
Assert.True(condition: model.IsValidForType(IntegrationType.Slack));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValidForType_ValidSlackConfigurationWithFilters_ReturnsTrue()
|
||||
{
|
||||
var config = JsonSerializer.Serialize(new SlackIntegrationConfiguration("C12345"));
|
||||
var filters = JsonSerializer.Serialize(new IntegrationFilterGroup()
|
||||
{
|
||||
AndOperator = true,
|
||||
Rules = [
|
||||
new IntegrationFilterRule()
|
||||
{
|
||||
Operation = IntegrationFilterOperation.Equals,
|
||||
Property = "CollectionId",
|
||||
Value = Guid.NewGuid()
|
||||
}
|
||||
],
|
||||
Groups = []
|
||||
});
|
||||
var model = new OrganizationIntegrationConfigurationRequestModel
|
||||
{
|
||||
Configuration = config,
|
||||
Filters = filters,
|
||||
Template = "template"
|
||||
};
|
||||
|
||||
Assert.True(model.IsValidForType(IntegrationType.Slack));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValidForType_ValidNoAuthWebhookConfiguration_ReturnsTrue()
|
||||
{
|
||||
var config = JsonSerializer.Serialize(value: new WebhookIntegrationConfiguration(Uri: new Uri("https://localhost")));
|
||||
var model = new OrganizationIntegrationConfigurationRequestModel
|
||||
{
|
||||
Configuration = config,
|
||||
Template = "template"
|
||||
};
|
||||
|
||||
Assert.True(condition: model.IsValidForType(IntegrationType.Webhook));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValidForType_ValidWebhookConfiguration_ReturnsTrue()
|
||||
{
|
||||
var config = JsonSerializer.Serialize(value: new WebhookIntegrationConfiguration(
|
||||
Uri: new Uri("https://localhost"),
|
||||
Scheme: "Bearer",
|
||||
Token: "AUTH-TOKEN"));
|
||||
var model = new OrganizationIntegrationConfigurationRequestModel
|
||||
{
|
||||
Configuration = config,
|
||||
Template = "template"
|
||||
};
|
||||
|
||||
Assert.True(condition: model.IsValidForType(IntegrationType.Webhook));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValidForType_ValidWebhookConfigurationWithFilters_ReturnsTrue()
|
||||
{
|
||||
var config = JsonSerializer.Serialize(new WebhookIntegrationConfiguration(
|
||||
Uri: new Uri("https://example.com"),
|
||||
Scheme: "Bearer",
|
||||
Token: "AUTH-TOKEN"));
|
||||
var filters = JsonSerializer.Serialize(new IntegrationFilterGroup()
|
||||
{
|
||||
AndOperator = true,
|
||||
Rules = [
|
||||
new IntegrationFilterRule()
|
||||
{
|
||||
Operation = IntegrationFilterOperation.Equals,
|
||||
Property = "CollectionId",
|
||||
Value = Guid.NewGuid()
|
||||
}
|
||||
],
|
||||
Groups = []
|
||||
});
|
||||
var model = new OrganizationIntegrationConfigurationRequestModel
|
||||
{
|
||||
Configuration = config,
|
||||
Filters = filters,
|
||||
Template = "template"
|
||||
};
|
||||
|
||||
Assert.True(model.IsValidForType(IntegrationType.Webhook));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValidForType_UnknownIntegrationType_ReturnsFalse()
|
||||
{
|
||||
var model = new OrganizationIntegrationConfigurationRequestModel
|
||||
{
|
||||
Configuration = "{}",
|
||||
Template = "template"
|
||||
};
|
||||
|
||||
var unknownType = (IntegrationType)999;
|
||||
|
||||
Assert.False(condition: model.IsValidForType(unknownType));
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations.Interfaces;
|
||||
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
|
||||
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations.Interfaces;
|
||||
using Bit.Core.AdminConsole.Services;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Utilities;
|
||||
@@ -32,6 +34,7 @@ public class EventIntegrationServiceCollectionExtensionsTests
|
||||
|
||||
// Mock required repository dependencies for commands
|
||||
_services.TryAddScoped(_ => Substitute.For<IOrganizationIntegrationRepository>());
|
||||
_services.TryAddScoped(_ => Substitute.For<IOrganizationIntegrationConfigurationRepository>());
|
||||
_services.TryAddScoped(_ => Substitute.For<IOrganizationRepository>());
|
||||
}
|
||||
|
||||
@@ -45,6 +48,9 @@ public class EventIntegrationServiceCollectionExtensionsTests
|
||||
var cache = provider.GetRequiredKeyedService<IFusionCache>(EventIntegrationsCacheConstants.CacheName);
|
||||
Assert.NotNull(cache);
|
||||
|
||||
var validator = provider.GetRequiredService<IOrganizationIntegrationConfigurationValidator>();
|
||||
Assert.NotNull(validator);
|
||||
|
||||
using var scope = provider.CreateScope();
|
||||
var sp = scope.ServiceProvider;
|
||||
|
||||
@@ -52,6 +58,11 @@ public class EventIntegrationServiceCollectionExtensionsTests
|
||||
Assert.NotNull(sp.GetService<IUpdateOrganizationIntegrationCommand>());
|
||||
Assert.NotNull(sp.GetService<IDeleteOrganizationIntegrationCommand>());
|
||||
Assert.NotNull(sp.GetService<IGetOrganizationIntegrationsQuery>());
|
||||
|
||||
Assert.NotNull(sp.GetService<ICreateOrganizationIntegrationConfigurationCommand>());
|
||||
Assert.NotNull(sp.GetService<IUpdateOrganizationIntegrationConfigurationCommand>());
|
||||
Assert.NotNull(sp.GetService<IDeleteOrganizationIntegrationConfigurationCommand>());
|
||||
Assert.NotNull(sp.GetService<IGetOrganizationIntegrationConfigurationsQuery>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -61,8 +72,11 @@ public class EventIntegrationServiceCollectionExtensionsTests
|
||||
|
||||
var createIntegrationDescriptor = _services.First(s =>
|
||||
s.ServiceType == typeof(ICreateOrganizationIntegrationCommand));
|
||||
var createConfigDescriptor = _services.First(s =>
|
||||
s.ServiceType == typeof(ICreateOrganizationIntegrationConfigurationCommand));
|
||||
|
||||
Assert.Equal(ServiceLifetime.Scoped, createIntegrationDescriptor.Lifetime);
|
||||
Assert.Equal(ServiceLifetime.Scoped, createConfigDescriptor.Lifetime);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -117,7 +131,7 @@ public class EventIntegrationServiceCollectionExtensionsTests
|
||||
_services.AddEventIntegrationsCommandsQueries(_globalSettings);
|
||||
|
||||
var createConfigCmdDescriptors = _services.Where(s =>
|
||||
s.ServiceType == typeof(ICreateOrganizationIntegrationCommand)).ToList();
|
||||
s.ServiceType == typeof(ICreateOrganizationIntegrationConfigurationCommand)).ToList();
|
||||
Assert.Single(createConfigCmdDescriptors);
|
||||
|
||||
var updateIntegrationCmdDescriptors = _services.Where(s =>
|
||||
@@ -148,6 +162,29 @@ public class EventIntegrationServiceCollectionExtensionsTests
|
||||
Assert.Single(createCmdDescriptors);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddOrganizationIntegrationConfigurationCommandsQueries_RegistersAllConfigurationServices()
|
||||
{
|
||||
_services.AddOrganizationIntegrationConfigurationCommandsQueries();
|
||||
|
||||
Assert.Contains(_services, s => s.ServiceType == typeof(ICreateOrganizationIntegrationConfigurationCommand));
|
||||
Assert.Contains(_services, s => s.ServiceType == typeof(IUpdateOrganizationIntegrationConfigurationCommand));
|
||||
Assert.Contains(_services, s => s.ServiceType == typeof(IDeleteOrganizationIntegrationConfigurationCommand));
|
||||
Assert.Contains(_services, s => s.ServiceType == typeof(IGetOrganizationIntegrationConfigurationsQuery));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddOrganizationIntegrationConfigurationCommandsQueries_MultipleCalls_IsIdempotent()
|
||||
{
|
||||
_services.AddOrganizationIntegrationConfigurationCommandsQueries();
|
||||
_services.AddOrganizationIntegrationConfigurationCommandsQueries();
|
||||
_services.AddOrganizationIntegrationConfigurationCommandsQueries();
|
||||
|
||||
var createCmdDescriptors = _services.Where(s =>
|
||||
s.ServiceType == typeof(ICreateOrganizationIntegrationConfigurationCommand)).ToList();
|
||||
Assert.Single(createCmdDescriptors);
|
||||
}
|
||||
|
||||
private static GlobalSettings CreateGlobalSettings(Dictionary<string, string?> data)
|
||||
{
|
||||
var config = new ConfigurationBuilder()
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations;
|
||||
using Bit.Core.AdminConsole.Services;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
using ZiggyCreatures.Caching.Fusion;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class CreateOrganizationIntegrationConfigurationCommandTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task CreateAsync_Success_CreatesConfigurationAndInvalidatesCache(
|
||||
SutProvider<CreateOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
OrganizationIntegration integration,
|
||||
OrganizationIntegrationConfiguration configuration)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = organizationId;
|
||||
integration.Type = IntegrationType.Webhook;
|
||||
configuration.OrganizationIntegrationId = integrationId;
|
||||
configuration.EventType = EventType.User_LoggedIn;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.CreateAsync(configuration)
|
||||
.Returns(configuration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationValidator>()
|
||||
.ValidateConfiguration(Arg.Any<IntegrationType>(), Arg.Any<OrganizationIntegrationConfiguration>())
|
||||
.Returns(true);
|
||||
|
||||
var result = await sutProvider.Sut.CreateAsync(organizationId, integrationId, configuration);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().Received(1)
|
||||
.GetByIdAsync(integrationId);
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.CreateAsync(configuration);
|
||||
await sutProvider.GetDependency<IFusionCache>().Received(1)
|
||||
.RemoveAsync(EventIntegrationsCacheConstants.BuildCacheKeyForOrganizationIntegrationConfigurationDetails(
|
||||
organizationId,
|
||||
integration.Type,
|
||||
configuration.EventType.Value));
|
||||
// Also verify RemoveByTagAsync was NOT called
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
Assert.Equal(configuration, result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task CreateAsync_WildcardSuccess_CreatesConfigurationAndInvalidatesCache(
|
||||
SutProvider<CreateOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
OrganizationIntegration integration,
|
||||
OrganizationIntegrationConfiguration configuration)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = organizationId;
|
||||
integration.Type = IntegrationType.Webhook;
|
||||
configuration.OrganizationIntegrationId = integrationId;
|
||||
configuration.EventType = null;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.CreateAsync(configuration)
|
||||
.Returns(configuration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationValidator>()
|
||||
.ValidateConfiguration(Arg.Any<IntegrationType>(), Arg.Any<OrganizationIntegrationConfiguration>())
|
||||
.Returns(true);
|
||||
|
||||
var result = await sutProvider.Sut.CreateAsync(organizationId, integrationId, configuration);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().Received(1)
|
||||
.GetByIdAsync(integrationId);
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.CreateAsync(configuration);
|
||||
await sutProvider.GetDependency<IFusionCache>().Received(1)
|
||||
.RemoveByTagAsync(EventIntegrationsCacheConstants.BuildCacheTagForOrganizationIntegration(
|
||||
organizationId,
|
||||
integration.Type));
|
||||
// Also verify RemoveAsync was NOT called
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
Assert.Equal(configuration, result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task CreateAsync_IntegrationDoesNotExist_ThrowsNotFound(
|
||||
SutProvider<CreateOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
OrganizationIntegrationConfiguration configuration)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns((OrganizationIntegration)null);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.CreateAsync(organizationId, integrationId, configuration));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.CreateAsync(Arg.Any<OrganizationIntegrationConfiguration>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task CreateAsync_IntegrationDoesNotBelongToOrganization_ThrowsNotFound(
|
||||
SutProvider<CreateOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
OrganizationIntegration integration,
|
||||
OrganizationIntegrationConfiguration configuration)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = Guid.NewGuid(); // Different organization
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.CreateAsync(organizationId, integrationId, configuration));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.CreateAsync(Arg.Any<OrganizationIntegrationConfiguration>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task CreateAsync_ValidationFails_ThrowsBadRequest(
|
||||
SutProvider<CreateOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
OrganizationIntegration integration,
|
||||
OrganizationIntegrationConfiguration configuration)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationValidator>()
|
||||
.ValidateConfiguration(Arg.Any<IntegrationType>(), Arg.Any<OrganizationIntegrationConfiguration>())
|
||||
.Returns(false);
|
||||
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = organizationId;
|
||||
configuration.OrganizationIntegrationId = integrationId;
|
||||
configuration.Template = "template";
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
|
||||
await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.CreateAsync(organizationId, integrationId, configuration));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.CreateAsync(Arg.Any<OrganizationIntegrationConfiguration>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
using ZiggyCreatures.Caching.Fusion;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class DeleteOrganizationIntegrationConfigurationCommandTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task DeleteAsync_Success_DeletesConfigurationAndInvalidatesCache(
|
||||
SutProvider<DeleteOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
Guid configurationId,
|
||||
OrganizationIntegration integration,
|
||||
OrganizationIntegrationConfiguration configuration)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = organizationId;
|
||||
integration.Type = IntegrationType.Webhook;
|
||||
configuration.Id = configurationId;
|
||||
configuration.OrganizationIntegrationId = integrationId;
|
||||
configuration.EventType = EventType.User_LoggedIn;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetByIdAsync(configurationId)
|
||||
.Returns(configuration);
|
||||
|
||||
await sutProvider.Sut.DeleteAsync(organizationId, integrationId, configurationId);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().Received(1)
|
||||
.GetByIdAsync(integrationId);
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.GetByIdAsync(configurationId);
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.DeleteAsync(configuration);
|
||||
await sutProvider.GetDependency<IFusionCache>().Received(1)
|
||||
.RemoveAsync(EventIntegrationsCacheConstants.BuildCacheKeyForOrganizationIntegrationConfigurationDetails(
|
||||
organizationId,
|
||||
integration.Type,
|
||||
configuration.EventType.Value));
|
||||
// Also verify RemoveByTagAsync was NOT called
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task DeleteAsync_WildcardSuccess_DeletesConfigurationAndInvalidatesCache(
|
||||
SutProvider<DeleteOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
Guid configurationId,
|
||||
OrganizationIntegration integration,
|
||||
OrganizationIntegrationConfiguration configuration)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = organizationId;
|
||||
integration.Type = IntegrationType.Webhook;
|
||||
configuration.Id = configurationId;
|
||||
configuration.OrganizationIntegrationId = integrationId;
|
||||
configuration.EventType = null;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetByIdAsync(configurationId)
|
||||
.Returns(configuration);
|
||||
|
||||
await sutProvider.Sut.DeleteAsync(organizationId, integrationId, configurationId);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().Received(1)
|
||||
.GetByIdAsync(integrationId);
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.GetByIdAsync(configurationId);
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.DeleteAsync(configuration);
|
||||
await sutProvider.GetDependency<IFusionCache>().Received(1)
|
||||
.RemoveByTagAsync(EventIntegrationsCacheConstants.BuildCacheTagForOrganizationIntegration(
|
||||
organizationId,
|
||||
integration.Type));
|
||||
// Also verify RemoveAsync was NOT called
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task DeleteAsync_IntegrationDoesNotExist_ThrowsNotFound(
|
||||
SutProvider<DeleteOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
Guid configurationId)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns((OrganizationIntegration)null);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.DeleteAsync(organizationId, integrationId, configurationId));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.GetByIdAsync(Arg.Any<Guid>());
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.DeleteAsync(Arg.Any<OrganizationIntegrationConfiguration>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task DeleteAsync_IntegrationDoesNotBelongToOrganization_ThrowsNotFound(
|
||||
SutProvider<DeleteOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
Guid configurationId,
|
||||
OrganizationIntegration integration)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = Guid.NewGuid(); // Different organization
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.DeleteAsync(organizationId, integrationId, configurationId));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.GetByIdAsync(Arg.Any<Guid>());
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.DeleteAsync(Arg.Any<OrganizationIntegrationConfiguration>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task DeleteAsync_ConfigurationDoesNotExist_ThrowsNotFound(
|
||||
SutProvider<DeleteOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
Guid configurationId,
|
||||
OrganizationIntegration integration)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = organizationId;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetByIdAsync(configurationId)
|
||||
.Returns((OrganizationIntegrationConfiguration)null);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.DeleteAsync(organizationId, integrationId, configurationId));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.DeleteAsync(Arg.Any<OrganizationIntegrationConfiguration>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task DeleteAsync_ConfigurationDoesNotBelongToIntegration_ThrowsNotFound(
|
||||
SutProvider<DeleteOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
Guid configurationId,
|
||||
OrganizationIntegration integration,
|
||||
OrganizationIntegrationConfiguration configuration)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = organizationId;
|
||||
configuration.Id = configurationId;
|
||||
configuration.OrganizationIntegrationId = Guid.NewGuid(); // Different integration
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetByIdAsync(configurationId)
|
||||
.Returns(configuration);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.DeleteAsync(organizationId, integrationId, configurationId));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.DeleteAsync(Arg.Any<OrganizationIntegrationConfiguration>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class GetOrganizationIntegrationConfigurationsQueryTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetManyByIntegrationAsync_Success_ReturnsConfigurations(
|
||||
SutProvider<GetOrganizationIntegrationConfigurationsQuery> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
OrganizationIntegration integration,
|
||||
List<OrganizationIntegrationConfiguration> configurations)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = organizationId;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetManyByIntegrationAsync(integrationId)
|
||||
.Returns(configurations);
|
||||
|
||||
var result = await sutProvider.Sut.GetManyByIntegrationAsync(organizationId, integrationId);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().Received(1)
|
||||
.GetByIdAsync(integrationId);
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.GetManyByIntegrationAsync(integrationId);
|
||||
Assert.Equal(configurations.Count, result.Count);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetManyByIntegrationAsync_NoConfigurations_ReturnsEmptyList(
|
||||
SutProvider<GetOrganizationIntegrationConfigurationsQuery> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
OrganizationIntegration integration)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = organizationId;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetManyByIntegrationAsync(integrationId)
|
||||
.Returns([]);
|
||||
|
||||
var result = await sutProvider.Sut.GetManyByIntegrationAsync(organizationId, integrationId);
|
||||
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetManyByIntegrationAsync_IntegrationDoesNotExist_ThrowsNotFound(
|
||||
SutProvider<GetOrganizationIntegrationConfigurationsQuery> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns((OrganizationIntegration)null);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.GetManyByIntegrationAsync(organizationId, integrationId));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.GetManyByIntegrationAsync(Arg.Any<Guid>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task GetManyByIntegrationAsync_IntegrationDoesNotBelongToOrganization_ThrowsNotFound(
|
||||
SutProvider<GetOrganizationIntegrationConfigurationsQuery> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
OrganizationIntegration integration)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = Guid.NewGuid(); // Different organization
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.GetManyByIntegrationAsync(organizationId, integrationId));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.GetManyByIntegrationAsync(Arg.Any<Guid>());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,390 @@
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations;
|
||||
using Bit.Core.AdminConsole.Services;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
using ZiggyCreatures.Caching.Fusion;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class UpdateOrganizationIntegrationConfigurationCommandTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_Success_UpdatesConfigurationAndInvalidatesCache(
|
||||
SutProvider<UpdateOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
Guid configurationId,
|
||||
OrganizationIntegration integration,
|
||||
OrganizationIntegrationConfiguration existingConfiguration,
|
||||
OrganizationIntegrationConfiguration updatedConfiguration)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = organizationId;
|
||||
integration.Type = IntegrationType.Webhook;
|
||||
existingConfiguration.Id = configurationId;
|
||||
existingConfiguration.OrganizationIntegrationId = integrationId;
|
||||
existingConfiguration.EventType = EventType.User_LoggedIn;
|
||||
updatedConfiguration.Id = configurationId;
|
||||
updatedConfiguration.OrganizationIntegrationId = integrationId;
|
||||
existingConfiguration.EventType = EventType.User_LoggedIn;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetByIdAsync(configurationId)
|
||||
.Returns(existingConfiguration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationValidator>()
|
||||
.ValidateConfiguration(Arg.Any<IntegrationType>(), Arg.Any<OrganizationIntegrationConfiguration>())
|
||||
.Returns(true);
|
||||
|
||||
var result = await sutProvider.Sut.UpdateAsync(organizationId, integrationId, configurationId, updatedConfiguration);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().Received(1)
|
||||
.GetByIdAsync(integrationId);
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.GetByIdAsync(configurationId);
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.ReplaceAsync(updatedConfiguration);
|
||||
await sutProvider.GetDependency<IFusionCache>().Received(1)
|
||||
.RemoveAsync(EventIntegrationsCacheConstants.BuildCacheKeyForOrganizationIntegrationConfigurationDetails(
|
||||
organizationId,
|
||||
integration.Type,
|
||||
existingConfiguration.EventType.Value));
|
||||
// Also verify RemoveByTagAsync was NOT called
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
Assert.Equal(updatedConfiguration, result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_WildcardSuccess_UpdatesConfigurationAndInvalidatesCache(
|
||||
SutProvider<UpdateOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
Guid configurationId,
|
||||
OrganizationIntegration integration,
|
||||
OrganizationIntegrationConfiguration existingConfiguration,
|
||||
OrganizationIntegrationConfiguration updatedConfiguration)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = organizationId;
|
||||
integration.Type = IntegrationType.Webhook;
|
||||
existingConfiguration.Id = configurationId;
|
||||
existingConfiguration.OrganizationIntegrationId = integrationId;
|
||||
existingConfiguration.EventType = null;
|
||||
updatedConfiguration.Id = configurationId;
|
||||
updatedConfiguration.OrganizationIntegrationId = integrationId;
|
||||
updatedConfiguration.EventType = null;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetByIdAsync(configurationId)
|
||||
.Returns(existingConfiguration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationValidator>()
|
||||
.ValidateConfiguration(Arg.Any<IntegrationType>(), Arg.Any<OrganizationIntegrationConfiguration>())
|
||||
.Returns(true);
|
||||
|
||||
var result = await sutProvider.Sut.UpdateAsync(organizationId, integrationId, configurationId, updatedConfiguration);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().Received(1)
|
||||
.GetByIdAsync(integrationId);
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.GetByIdAsync(configurationId);
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.ReplaceAsync(updatedConfiguration);
|
||||
await sutProvider.GetDependency<IFusionCache>().Received(1)
|
||||
.RemoveByTagAsync(EventIntegrationsCacheConstants.BuildCacheTagForOrganizationIntegration(
|
||||
organizationId,
|
||||
integration.Type));
|
||||
// Also verify RemoveAsync was NOT called
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
Assert.Equal(updatedConfiguration, result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_ChangedEventType_UpdatesConfigurationAndInvalidatesCacheForBothTypes(
|
||||
SutProvider<UpdateOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
Guid configurationId,
|
||||
OrganizationIntegration integration,
|
||||
OrganizationIntegrationConfiguration existingConfiguration,
|
||||
OrganizationIntegrationConfiguration updatedConfiguration)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = organizationId;
|
||||
integration.Type = IntegrationType.Webhook;
|
||||
existingConfiguration.Id = configurationId;
|
||||
existingConfiguration.OrganizationIntegrationId = integrationId;
|
||||
existingConfiguration.EventType = EventType.User_LoggedIn;
|
||||
updatedConfiguration.Id = configurationId;
|
||||
updatedConfiguration.OrganizationIntegrationId = integrationId;
|
||||
updatedConfiguration.EventType = EventType.Cipher_Created;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetByIdAsync(configurationId)
|
||||
.Returns(existingConfiguration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationValidator>()
|
||||
.ValidateConfiguration(Arg.Any<IntegrationType>(), Arg.Any<OrganizationIntegrationConfiguration>())
|
||||
.Returns(true);
|
||||
|
||||
var result = await sutProvider.Sut.UpdateAsync(organizationId, integrationId, configurationId, updatedConfiguration);
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().Received(1)
|
||||
.GetByIdAsync(integrationId);
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.GetByIdAsync(configurationId);
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
||||
.ReplaceAsync(updatedConfiguration);
|
||||
await sutProvider.GetDependency<IFusionCache>().Received(1)
|
||||
.RemoveAsync(EventIntegrationsCacheConstants.BuildCacheKeyForOrganizationIntegrationConfigurationDetails(
|
||||
organizationId,
|
||||
integration.Type,
|
||||
existingConfiguration.EventType.Value));
|
||||
await sutProvider.GetDependency<IFusionCache>().Received(1)
|
||||
.RemoveAsync(EventIntegrationsCacheConstants.BuildCacheKeyForOrganizationIntegrationConfigurationDetails(
|
||||
organizationId,
|
||||
integration.Type,
|
||||
updatedConfiguration.EventType.Value));
|
||||
// Verify RemoveByTagAsync was NOT called since both are specific event types
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
Assert.Equal(updatedConfiguration, result);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_IntegrationDoesNotExist_ThrowsNotFound(
|
||||
SutProvider<UpdateOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
Guid configurationId,
|
||||
OrganizationIntegrationConfiguration updatedConfiguration)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns((OrganizationIntegration)null);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.UpdateAsync(organizationId, integrationId, configurationId, updatedConfiguration));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.GetByIdAsync(Arg.Any<Guid>());
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.ReplaceAsync(Arg.Any<OrganizationIntegrationConfiguration>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_IntegrationDoesNotBelongToOrganization_ThrowsNotFound(
|
||||
SutProvider<UpdateOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
Guid configurationId,
|
||||
OrganizationIntegration integration,
|
||||
OrganizationIntegrationConfiguration updatedConfiguration)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = Guid.NewGuid(); // Different organization
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.UpdateAsync(organizationId, integrationId, configurationId, updatedConfiguration));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.GetByIdAsync(Arg.Any<Guid>());
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.ReplaceAsync(Arg.Any<OrganizationIntegrationConfiguration>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_ConfigurationDoesNotExist_ThrowsNotFound(
|
||||
SutProvider<UpdateOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
Guid configurationId,
|
||||
OrganizationIntegration integration,
|
||||
OrganizationIntegrationConfiguration updatedConfiguration)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = organizationId;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetByIdAsync(configurationId)
|
||||
.Returns((OrganizationIntegrationConfiguration)null);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.UpdateAsync(organizationId, integrationId, configurationId, updatedConfiguration));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.ReplaceAsync(Arg.Any<OrganizationIntegrationConfiguration>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_ConfigurationDoesNotBelongToIntegration_ThrowsNotFound(
|
||||
SutProvider<UpdateOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
Guid configurationId,
|
||||
OrganizationIntegration integration,
|
||||
OrganizationIntegrationConfiguration existingConfiguration,
|
||||
OrganizationIntegrationConfiguration updatedConfiguration)
|
||||
{
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = organizationId;
|
||||
existingConfiguration.Id = configurationId;
|
||||
existingConfiguration.OrganizationIntegrationId = Guid.NewGuid(); // Different integration
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetByIdAsync(configurationId)
|
||||
.Returns(existingConfiguration);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.UpdateAsync(organizationId, integrationId, configurationId, updatedConfiguration));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.ReplaceAsync(Arg.Any<OrganizationIntegrationConfiguration>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_ValidationFails_ThrowsBadRequest(
|
||||
SutProvider<UpdateOrganizationIntegrationConfigurationCommand> sutProvider,
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
Guid configurationId,
|
||||
OrganizationIntegration integration,
|
||||
OrganizationIntegrationConfiguration existingConfiguration,
|
||||
OrganizationIntegrationConfiguration updatedConfiguration)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationValidator>()
|
||||
.ValidateConfiguration(Arg.Any<IntegrationType>(), Arg.Any<OrganizationIntegrationConfiguration>())
|
||||
.Returns(false);
|
||||
|
||||
integration.Id = integrationId;
|
||||
integration.OrganizationId = organizationId;
|
||||
existingConfiguration.Id = configurationId;
|
||||
existingConfiguration.OrganizationIntegrationId = integrationId;
|
||||
updatedConfiguration.Id = configurationId;
|
||||
updatedConfiguration.OrganizationIntegrationId = integrationId;
|
||||
updatedConfiguration.Template = "template";
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId)
|
||||
.Returns(integration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetByIdAsync(configurationId)
|
||||
.Returns(existingConfiguration);
|
||||
|
||||
await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.UpdateAsync(organizationId, integrationId, configurationId, updatedConfiguration));
|
||||
|
||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().DidNotReceive()
|
||||
.ReplaceAsync(Arg.Any<OrganizationIntegrationConfiguration>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveByTagAsync(Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_ChangedFromWildcardToSpecific_InvalidatesAllCaches(
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
OrganizationIntegration integration,
|
||||
OrganizationIntegrationConfiguration existingConfiguration,
|
||||
OrganizationIntegrationConfiguration updatedConfiguration,
|
||||
SutProvider<UpdateOrganizationIntegrationConfigurationCommand> sutProvider)
|
||||
{
|
||||
integration.OrganizationId = organizationId;
|
||||
existingConfiguration.OrganizationIntegrationId = integrationId;
|
||||
existingConfiguration.EventType = null; // Wildcard
|
||||
updatedConfiguration.EventType = EventType.User_LoggedIn; // Specific
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId).Returns(integration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetByIdAsync(existingConfiguration.Id).Returns(existingConfiguration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationValidator>()
|
||||
.ValidateConfiguration(Arg.Any<IntegrationType>(), Arg.Any<OrganizationIntegrationConfiguration>())
|
||||
.Returns(true);
|
||||
|
||||
await sutProvider.Sut.UpdateAsync(organizationId, integrationId, existingConfiguration.Id, updatedConfiguration);
|
||||
|
||||
await sutProvider.GetDependency<IFusionCache>().Received(1)
|
||||
.RemoveByTagAsync(EventIntegrationsCacheConstants.BuildCacheTagForOrganizationIntegration(
|
||||
organizationId,
|
||||
integration.Type));
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task UpdateAsync_ChangedFromSpecificToWildcard_InvalidatesAllCaches(
|
||||
Guid organizationId,
|
||||
Guid integrationId,
|
||||
OrganizationIntegration integration,
|
||||
OrganizationIntegrationConfiguration existingConfiguration,
|
||||
OrganizationIntegrationConfiguration updatedConfiguration,
|
||||
SutProvider<UpdateOrganizationIntegrationConfigurationCommand> sutProvider)
|
||||
{
|
||||
integration.OrganizationId = organizationId;
|
||||
existingConfiguration.OrganizationIntegrationId = integrationId;
|
||||
existingConfiguration.EventType = EventType.User_LoggedIn; // Specific
|
||||
updatedConfiguration.EventType = null; // Wildcard
|
||||
|
||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
||||
.GetByIdAsync(integrationId).Returns(integration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
||||
.GetByIdAsync(existingConfiguration.Id).Returns(existingConfiguration);
|
||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationValidator>()
|
||||
.ValidateConfiguration(Arg.Any<IntegrationType>(), Arg.Any<OrganizationIntegrationConfiguration>())
|
||||
.Returns(true);
|
||||
|
||||
await sutProvider.Sut.UpdateAsync(organizationId, integrationId, existingConfiguration.Id, updatedConfiguration);
|
||||
|
||||
await sutProvider.GetDependency<IFusionCache>().Received(1)
|
||||
.RemoveByTagAsync(EventIntegrationsCacheConstants.BuildCacheTagForOrganizationIntegration(
|
||||
organizationId,
|
||||
integration.Type));
|
||||
await sutProvider.GetDependency<IFusionCache>().DidNotReceive()
|
||||
.RemoveAsync(Arg.Any<string>());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
using System.Text.Json;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||
using Bit.Core.AdminConsole.Services;
|
||||
using Bit.Core.Enums;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.AdminConsole.Services;
|
||||
|
||||
public class OrganizationIntegrationConfigurationValidatorTests
|
||||
{
|
||||
private readonly OrganizationIntegrationConfigurationValidator _sut = new();
|
||||
|
||||
[Fact]
|
||||
public void ValidateConfiguration_CloudBillingSyncIntegration_ReturnsFalse()
|
||||
{
|
||||
var configuration = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = "{}",
|
||||
Template = "template"
|
||||
};
|
||||
|
||||
Assert.False(_sut.ValidateConfiguration(IntegrationType.CloudBillingSync, configuration));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData("")]
|
||||
[InlineData(" ")]
|
||||
public void ValidateConfiguration_EmptyTemplate_ReturnsFalse(string? template)
|
||||
{
|
||||
var config1 = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = JsonSerializer.Serialize(new SlackIntegrationConfiguration(ChannelId: "C12345")),
|
||||
Template = template
|
||||
};
|
||||
Assert.False(_sut.ValidateConfiguration(IntegrationType.Slack, config1));
|
||||
|
||||
var config2 = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = JsonSerializer.Serialize(new WebhookIntegrationConfiguration(Uri: new Uri("https://example.com"))),
|
||||
Template = template
|
||||
};
|
||||
Assert.False(_sut.ValidateConfiguration(IntegrationType.Webhook, config2));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData(" ")]
|
||||
public void ValidateConfiguration_EmptyNonNullConfiguration_ReturnsFalse(string? config)
|
||||
{
|
||||
var config1 = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = config,
|
||||
Template = "template"
|
||||
};
|
||||
Assert.False(_sut.ValidateConfiguration(IntegrationType.Hec, config1));
|
||||
|
||||
var config2 = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = config,
|
||||
Template = "template"
|
||||
};
|
||||
Assert.False(_sut.ValidateConfiguration(IntegrationType.Datadog, config2));
|
||||
|
||||
var config3 = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = config,
|
||||
Template = "template"
|
||||
};
|
||||
Assert.False(_sut.ValidateConfiguration(IntegrationType.Teams, config3));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateConfiguration_NullConfiguration_ReturnsTrue()
|
||||
{
|
||||
var config1 = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = null,
|
||||
Template = "template"
|
||||
};
|
||||
Assert.True(_sut.ValidateConfiguration(IntegrationType.Hec, config1));
|
||||
|
||||
var config2 = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = null,
|
||||
Template = "template"
|
||||
};
|
||||
Assert.True(_sut.ValidateConfiguration(IntegrationType.Datadog, config2));
|
||||
|
||||
var config3 = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = null,
|
||||
Template = "template"
|
||||
};
|
||||
Assert.True(_sut.ValidateConfiguration(IntegrationType.Teams, config3));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateConfiguration_InvalidJsonConfiguration_ReturnsFalse()
|
||||
{
|
||||
var config = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = "{not valid json}",
|
||||
Template = "template"
|
||||
};
|
||||
|
||||
Assert.False(_sut.ValidateConfiguration(IntegrationType.Slack, config));
|
||||
Assert.False(_sut.ValidateConfiguration(IntegrationType.Webhook, config));
|
||||
Assert.False(_sut.ValidateConfiguration(IntegrationType.Hec, config));
|
||||
Assert.False(_sut.ValidateConfiguration(IntegrationType.Datadog, config));
|
||||
Assert.False(_sut.ValidateConfiguration(IntegrationType.Teams, config));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateConfiguration_InvalidJsonFilters_ReturnsFalse()
|
||||
{
|
||||
var configuration = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = JsonSerializer.Serialize(new WebhookIntegrationConfiguration(Uri: new Uri("https://example.com"))),
|
||||
Template = "template",
|
||||
Filters = "{Not valid json}"
|
||||
};
|
||||
|
||||
Assert.False(_sut.ValidateConfiguration(IntegrationType.Webhook, configuration));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateConfiguration_ScimIntegration_ReturnsFalse()
|
||||
{
|
||||
var configuration = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = "{}",
|
||||
Template = "template"
|
||||
};
|
||||
|
||||
Assert.False(_sut.ValidateConfiguration(IntegrationType.Scim, configuration));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateConfiguration_ValidSlackConfiguration_ReturnsTrue()
|
||||
{
|
||||
var configuration = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = JsonSerializer.Serialize(new SlackIntegrationConfiguration(ChannelId: "C12345")),
|
||||
Template = "template"
|
||||
};
|
||||
|
||||
Assert.True(_sut.ValidateConfiguration(IntegrationType.Slack, configuration));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateConfiguration_ValidSlackConfigurationWithFilters_ReturnsTrue()
|
||||
{
|
||||
var configuration = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = JsonSerializer.Serialize(new SlackIntegrationConfiguration("C12345")),
|
||||
Template = "template",
|
||||
Filters = JsonSerializer.Serialize(new IntegrationFilterGroup()
|
||||
{
|
||||
AndOperator = true,
|
||||
Rules = [
|
||||
new IntegrationFilterRule()
|
||||
{
|
||||
Operation = IntegrationFilterOperation.Equals,
|
||||
Property = "CollectionId",
|
||||
Value = Guid.NewGuid()
|
||||
}
|
||||
],
|
||||
Groups = []
|
||||
})
|
||||
};
|
||||
|
||||
Assert.True(_sut.ValidateConfiguration(IntegrationType.Slack, configuration));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateConfiguration_ValidNoAuthWebhookConfiguration_ReturnsTrue()
|
||||
{
|
||||
var configuration = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = JsonSerializer.Serialize(new WebhookIntegrationConfiguration(Uri: new Uri("https://localhost"))),
|
||||
Template = "template"
|
||||
};
|
||||
|
||||
Assert.True(_sut.ValidateConfiguration(IntegrationType.Webhook, configuration));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateConfiguration_ValidWebhookConfiguration_ReturnsTrue()
|
||||
{
|
||||
var configuration = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = JsonSerializer.Serialize(new WebhookIntegrationConfiguration(
|
||||
Uri: new Uri("https://localhost"),
|
||||
Scheme: "Bearer",
|
||||
Token: "AUTH-TOKEN")),
|
||||
Template = "template"
|
||||
};
|
||||
|
||||
Assert.True(_sut.ValidateConfiguration(IntegrationType.Webhook, configuration));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateConfiguration_ValidWebhookConfigurationWithFilters_ReturnsTrue()
|
||||
{
|
||||
var configuration = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = JsonSerializer.Serialize(new WebhookIntegrationConfiguration(
|
||||
Uri: new Uri("https://example.com"),
|
||||
Scheme: "Bearer",
|
||||
Token: "AUTH-TOKEN")),
|
||||
Template = "template",
|
||||
Filters = JsonSerializer.Serialize(new IntegrationFilterGroup()
|
||||
{
|
||||
AndOperator = true,
|
||||
Rules = [
|
||||
new IntegrationFilterRule()
|
||||
{
|
||||
Operation = IntegrationFilterOperation.Equals,
|
||||
Property = "CollectionId",
|
||||
Value = Guid.NewGuid()
|
||||
}
|
||||
],
|
||||
Groups = []
|
||||
})
|
||||
};
|
||||
|
||||
Assert.True(_sut.ValidateConfiguration(IntegrationType.Webhook, configuration));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateConfiguration_UnknownIntegrationType_ReturnsFalse()
|
||||
{
|
||||
var unknownType = (IntegrationType)999;
|
||||
var configuration = new OrganizationIntegrationConfiguration
|
||||
{
|
||||
Configuration = "{}",
|
||||
Template = "template"
|
||||
};
|
||||
|
||||
Assert.False(_sut.ValidateConfiguration(unknownType, configuration));
|
||||
}
|
||||
}
|
||||
@@ -55,20 +55,6 @@ public class EventIntegrationsCacheConstantsTests
|
||||
Assert.NotEqual(keyWithEvent, keyWithDifferentIntegration);
|
||||
Assert.NotEqual(keyWithEvent, keyWithDifferentOrganization);
|
||||
Assert.Equal(keyWithEvent, keyWithSameDetails);
|
||||
|
||||
var expectedWithNullEvent = $"OrganizationIntegrationConfigurationDetails:{orgId:N}:Hec:";
|
||||
var keyWithNullEvent = EventIntegrationsCacheConstants.BuildCacheKeyForOrganizationIntegrationConfigurationDetails(
|
||||
orgId, integrationType, null);
|
||||
var keyWithNullEventDifferentIntegration = EventIntegrationsCacheConstants.BuildCacheKeyForOrganizationIntegrationConfigurationDetails(
|
||||
orgId, IntegrationType.Webhook, null);
|
||||
var keyWithNullEventDifferentOrganization = EventIntegrationsCacheConstants.BuildCacheKeyForOrganizationIntegrationConfigurationDetails(
|
||||
Guid.NewGuid(), integrationType, null);
|
||||
|
||||
Assert.Equal(expectedWithNullEvent, keyWithNullEvent);
|
||||
Assert.NotEqual(keyWithEvent, keyWithNullEvent);
|
||||
Assert.NotEqual(keyWithNullEvent, keyWithDifferentEvent);
|
||||
Assert.NotEqual(keyWithNullEvent, keyWithNullEventDifferentIntegration);
|
||||
Assert.NotEqual(keyWithNullEvent, keyWithNullEventDifferentOrganization);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
|
||||
Reference in New Issue
Block a user