mirror of
https://github.com/bitwarden/server
synced 2025-12-20 10:13:39 +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.Request.Organizations;
|
||||||
using Bit.Api.AdminConsole.Models.Response.Organizations;
|
using Bit.Api.AdminConsole.Models.Response.Organizations;
|
||||||
|
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
|
||||||
using Bit.Core.Context;
|
using Bit.Core.Context;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Repositories;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
@@ -12,8 +12,10 @@ namespace Bit.Api.AdminConsole.Controllers;
|
|||||||
[Authorize("Application")]
|
[Authorize("Application")]
|
||||||
public class OrganizationIntegrationConfigurationController(
|
public class OrganizationIntegrationConfigurationController(
|
||||||
ICurrentContext currentContext,
|
ICurrentContext currentContext,
|
||||||
IOrganizationIntegrationRepository integrationRepository,
|
ICreateOrganizationIntegrationConfigurationCommand createCommand,
|
||||||
IOrganizationIntegrationConfigurationRepository integrationConfigurationRepository) : Controller
|
IUpdateOrganizationIntegrationConfigurationCommand updateCommand,
|
||||||
|
IDeleteOrganizationIntegrationConfigurationCommand deleteCommand,
|
||||||
|
IGetOrganizationIntegrationConfigurationsQuery getQuery) : Controller
|
||||||
{
|
{
|
||||||
[HttpGet("")]
|
[HttpGet("")]
|
||||||
public async Task<List<OrganizationIntegrationConfigurationResponseModel>> GetAsync(
|
public async Task<List<OrganizationIntegrationConfigurationResponseModel>> GetAsync(
|
||||||
@@ -24,13 +26,8 @@ public class OrganizationIntegrationConfigurationController(
|
|||||||
{
|
{
|
||||||
throw new NotFoundException();
|
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
|
return configurations
|
||||||
.Select(configuration => new OrganizationIntegrationConfigurationResponseModel(configuration))
|
.Select(configuration => new OrganizationIntegrationConfigurationResponseModel(configuration))
|
||||||
.ToList();
|
.ToList();
|
||||||
@@ -46,19 +43,11 @@ public class OrganizationIntegrationConfigurationController(
|
|||||||
{
|
{
|
||||||
throw new NotFoundException();
|
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 = model.ToOrganizationIntegrationConfiguration(integrationId);
|
||||||
var configuration = await integrationConfigurationRepository.CreateAsync(organizationIntegrationConfiguration);
|
var created = await createCommand.CreateAsync(organizationId, integrationId, configuration);
|
||||||
return new OrganizationIntegrationConfigurationResponseModel(configuration);
|
|
||||||
|
return new OrganizationIntegrationConfigurationResponseModel(created);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPut("{configurationId:guid}")]
|
[HttpPut("{configurationId:guid}")]
|
||||||
@@ -72,26 +61,11 @@ public class OrganizationIntegrationConfigurationController(
|
|||||||
{
|
{
|
||||||
throw new NotFoundException();
|
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);
|
var configuration = model.ToOrganizationIntegrationConfiguration(integrationId);
|
||||||
if (configuration is null || configuration.OrganizationIntegrationId != integrationId)
|
var updated = await updateCommand.UpdateAsync(organizationId, integrationId, configurationId, configuration);
|
||||||
{
|
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
|
|
||||||
var newConfiguration = model.ToOrganizationIntegrationConfiguration(configuration);
|
return new OrganizationIntegrationConfigurationResponseModel(updated);
|
||||||
await integrationConfigurationRepository.ReplaceAsync(newConfiguration);
|
|
||||||
|
|
||||||
return new OrganizationIntegrationConfigurationResponseModel(newConfiguration);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpDelete("{configurationId:guid}")]
|
[HttpDelete("{configurationId:guid}")]
|
||||||
@@ -101,19 +75,8 @@ public class OrganizationIntegrationConfigurationController(
|
|||||||
{
|
{
|
||||||
throw new NotFoundException();
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
var integration = await integrationRepository.GetByIdAsync(integrationId);
|
|
||||||
if (integration == null || integration.OrganizationId != organizationId)
|
|
||||||
{
|
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
|
|
||||||
var configuration = await integrationConfigurationRepository.GetByIdAsync(configurationId);
|
await deleteCommand.DeleteAsync(organizationId, integrationId, configurationId);
|
||||||
if (configuration is null || configuration.OrganizationIntegrationId != integrationId)
|
|
||||||
{
|
|
||||||
throw new NotFoundException();
|
|
||||||
}
|
|
||||||
|
|
||||||
await integrationConfigurationRepository.DeleteAsync(configuration);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost("{configurationId:guid}/delete")]
|
[HttpPost("{configurationId:guid}/delete")]
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
using System.Text.Json;
|
using Bit.Core.AdminConsole.Entities;
|
||||||
using Bit.Core.AdminConsole.Entities;
|
|
||||||
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
|
|
||||||
|
|
||||||
@@ -16,38 +14,6 @@ public class OrganizationIntegrationConfigurationRequestModel
|
|||||||
|
|
||||||
public string? Template { get; set; }
|
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)
|
public OrganizationIntegrationConfiguration ToOrganizationIntegrationConfiguration(Guid organizationIntegrationId)
|
||||||
{
|
{
|
||||||
return new OrganizationIntegrationConfiguration()
|
return new OrganizationIntegrationConfiguration()
|
||||||
@@ -59,50 +25,4 @@ public class OrganizationIntegrationConfigurationRequestModel
|
|||||||
Template = Template
|
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.EventIntegrations.OrganizationIntegrations.Interfaces;
|
||||||
|
using Bit.Core.AdminConsole.Services;
|
||||||
using Bit.Core.Settings;
|
using Bit.Core.Settings;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
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.
|
// This is idempotent for the same named cache, so it's safe to call.
|
||||||
services.AddExtendedCache(EventIntegrationsCacheConstants.CacheName, globalSettings);
|
services.AddExtendedCache(EventIntegrationsCacheConstants.CacheName, globalSettings);
|
||||||
|
|
||||||
|
// Add Validator
|
||||||
|
services.TryAddSingleton<IOrganizationIntegrationConfigurationValidator, OrganizationIntegrationConfigurationValidator>();
|
||||||
|
|
||||||
// Add all commands/queries
|
// Add all commands/queries
|
||||||
services.AddOrganizationIntegrationCommandsQueries();
|
services.AddOrganizationIntegrationCommandsQueries();
|
||||||
|
services.AddOrganizationIntegrationConfigurationCommandsQueries();
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
@@ -35,4 +42,14 @@ public static class EventIntegrationsServiceCollectionExtensions
|
|||||||
|
|
||||||
return services;
|
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
|
/// Builds a deterministic cache key for an organization's integration configuration details
|
||||||
/// <see cref="OrganizationIntegrationConfigurationDetails"/>.
|
/// <see cref="OrganizationIntegrationConfigurationDetails"/>.
|
||||||
/// </summary>
|
/// </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="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>
|
/// <returns>
|
||||||
/// A cache key for the configuration details.
|
/// A cache key for the configuration details.
|
||||||
/// </returns>
|
/// </returns>
|
||||||
public static string BuildCacheKeyForOrganizationIntegrationConfigurationDetails(
|
public static string BuildCacheKeyForOrganizationIntegrationConfigurationDetails(
|
||||||
Guid organizationId,
|
Guid organizationId,
|
||||||
IntegrationType integrationType,
|
IntegrationType integrationType,
|
||||||
EventType? eventType
|
EventType eventType
|
||||||
) => $"OrganizationIntegrationConfigurationDetails:{organizationId:N}:{integrationType}:{eventType}";
|
) => $"OrganizationIntegrationConfigurationDetails:{organizationId:N}:{integrationType}:{eventType}";
|
||||||
|
|
||||||
/// <summary>
|
/// <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.Request.Organizations;
|
||||||
using Bit.Api.AdminConsole.Models.Response.Organizations;
|
using Bit.Api.AdminConsole.Models.Response.Organizations;
|
||||||
using Bit.Core.AdminConsole.Entities;
|
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.Context;
|
||||||
using Bit.Core.Enums;
|
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
using Bit.Core.Repositories;
|
|
||||||
using Bit.Test.Common.AutoFixture;
|
using Bit.Test.Common.AutoFixture;
|
||||||
using Bit.Test.Common.AutoFixture.Attributes;
|
using Bit.Test.Common.AutoFixture.Attributes;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using NSubstitute;
|
using NSubstitute;
|
||||||
using NSubstitute.ReturnsExtensions;
|
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Bit.Api.Test.AdminConsole.Controllers;
|
namespace Bit.Api.Test.AdminConsole.Controllers;
|
||||||
@@ -25,823 +21,191 @@ public class OrganizationIntegrationsConfigurationControllerTests
|
|||||||
public async Task DeleteAsync_AllParamsProvided_Succeeds(
|
public async Task DeleteAsync_AllParamsProvided_Succeeds(
|
||||||
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
||||||
Guid organizationId,
|
Guid organizationId,
|
||||||
OrganizationIntegration organizationIntegration,
|
Guid integrationId,
|
||||||
OrganizationIntegrationConfiguration organizationIntegrationConfiguration)
|
Guid configurationId)
|
||||||
{
|
{
|
||||||
organizationIntegration.OrganizationId = organizationId;
|
|
||||||
organizationIntegrationConfiguration.OrganizationIntegrationId = organizationIntegration.Id;
|
|
||||||
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
|
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
|
||||||
sutProvider.GetDependency<ICurrentContext>()
|
sutProvider.GetDependency<ICurrentContext>()
|
||||||
.OrganizationOwner(organizationId)
|
.OrganizationOwner(organizationId)
|
||||||
.Returns(true);
|
.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)
|
await sutProvider.GetDependency<IDeleteOrganizationIntegrationConfigurationCommand>().Received(1)
|
||||||
.GetByIdAsync(organizationIntegration.Id);
|
.DeleteAsync(organizationId, integrationId, configurationId);
|
||||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
|
||||||
.GetByIdAsync(organizationIntegrationConfiguration.Id);
|
|
||||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
|
||||||
.DeleteAsync(organizationIntegrationConfiguration);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
|
[Obsolete("Obsolete")]
|
||||||
public async Task PostDeleteAsync_AllParamsProvided_Succeeds(
|
public async Task PostDeleteAsync_AllParamsProvided_Succeeds(
|
||||||
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
||||||
Guid organizationId,
|
Guid organizationId,
|
||||||
OrganizationIntegration organizationIntegration,
|
Guid integrationId,
|
||||||
OrganizationIntegrationConfiguration organizationIntegrationConfiguration)
|
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.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)
|
|
||||||
{
|
{
|
||||||
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
|
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
|
||||||
sutProvider.GetDependency<ICurrentContext>()
|
sutProvider.GetDependency<ICurrentContext>()
|
||||||
.OrganizationOwner(organizationId)
|
.OrganizationOwner(organizationId)
|
||||||
.Returns(true);
|
.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]
|
await sutProvider.GetDependency<IDeleteOrganizationIntegrationConfigurationCommand>().Received(1)
|
||||||
public async Task DeleteAsync_IntegrationDoesNotBelongToOrganization_ThrowsNotFound(
|
.DeleteAsync(organizationId, integrationId, configurationId);
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task DeleteAsync_UserIsNotOrganizationAdmin_ThrowsNotFound(
|
public async Task DeleteAsync_UserIsNotOrganizationAdmin_ThrowsNotFound(
|
||||||
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
||||||
Guid organizationId)
|
Guid organizationId,
|
||||||
|
Guid integrationId,
|
||||||
|
Guid configurationId)
|
||||||
{
|
{
|
||||||
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
|
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
|
||||||
sutProvider.GetDependency<ICurrentContext>()
|
sutProvider.GetDependency<ICurrentContext>()
|
||||||
.OrganizationOwner(organizationId)
|
.OrganizationOwner(organizationId)
|
||||||
.Returns(false);
|
.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]
|
[Theory, BitAutoData]
|
||||||
public async Task GetAsync_ConfigurationsExist_Succeeds(
|
public async Task GetAsync_ConfigurationsExist_Succeeds(
|
||||||
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
||||||
Guid organizationId,
|
Guid organizationId,
|
||||||
OrganizationIntegration organizationIntegration,
|
Guid integrationId,
|
||||||
List<OrganizationIntegrationConfiguration> organizationIntegrationConfigurations)
|
List<OrganizationIntegrationConfiguration> configurations)
|
||||||
{
|
{
|
||||||
organizationIntegration.OrganizationId = organizationId;
|
|
||||||
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
|
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
|
||||||
sutProvider.GetDependency<ICurrentContext>()
|
sutProvider.GetDependency<ICurrentContext>()
|
||||||
.OrganizationOwner(organizationId)
|
.OrganizationOwner(organizationId)
|
||||||
.Returns(true);
|
.Returns(true);
|
||||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
sutProvider.GetDependency<IGetOrganizationIntegrationConfigurationsQuery>()
|
||||||
.GetByIdAsync(Arg.Any<Guid>())
|
.GetManyByIntegrationAsync(organizationId, integrationId)
|
||||||
.Returns(organizationIntegration);
|
.Returns(configurations);
|
||||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
|
||||||
.GetManyByIntegrationAsync(Arg.Any<Guid>())
|
var result = await sutProvider.Sut.GetAsync(organizationId, integrationId);
|
||||||
.Returns(organizationIntegrationConfigurations);
|
|
||||||
|
|
||||||
var result = await sutProvider.Sut.GetAsync(organizationId, organizationIntegration.Id);
|
|
||||||
Assert.NotNull(result);
|
Assert.NotNull(result);
|
||||||
Assert.Equal(organizationIntegrationConfigurations.Count, result.Count);
|
Assert.Equal(configurations.Count, result.Count);
|
||||||
Assert.All(result, r => Assert.IsType<OrganizationIntegrationConfigurationResponseModel>(r));
|
Assert.All(result, r => Assert.IsType<OrganizationIntegrationConfigurationResponseModel>(r));
|
||||||
|
await sutProvider.GetDependency<IGetOrganizationIntegrationConfigurationsQuery>().Received(1)
|
||||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().Received(1)
|
.GetManyByIntegrationAsync(organizationId, integrationId);
|
||||||
.GetByIdAsync(organizationIntegration.Id);
|
|
||||||
await sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>().Received(1)
|
|
||||||
.GetManyByIntegrationAsync(organizationIntegration.Id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task GetAsync_NoConfigurationsExist_ReturnsEmptyList(
|
public async Task GetAsync_NoConfigurationsExist_ReturnsEmptyList(
|
||||||
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
||||||
Guid organizationId,
|
Guid organizationId,
|
||||||
OrganizationIntegration organizationIntegration)
|
Guid integrationId)
|
||||||
{
|
{
|
||||||
organizationIntegration.OrganizationId = organizationId;
|
|
||||||
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
|
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
|
||||||
sutProvider.GetDependency<ICurrentContext>()
|
sutProvider.GetDependency<ICurrentContext>()
|
||||||
.OrganizationOwner(organizationId)
|
.OrganizationOwner(organizationId)
|
||||||
.Returns(true);
|
.Returns(true);
|
||||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
sutProvider.GetDependency<IGetOrganizationIntegrationConfigurationsQuery>()
|
||||||
.GetByIdAsync(Arg.Any<Guid>())
|
.GetManyByIntegrationAsync(organizationId, integrationId)
|
||||||
.Returns(organizationIntegration);
|
|
||||||
sutProvider.GetDependency<IOrganizationIntegrationConfigurationRepository>()
|
|
||||||
.GetManyByIntegrationAsync(Arg.Any<Guid>())
|
|
||||||
.Returns([]);
|
.Returns([]);
|
||||||
|
|
||||||
var result = await sutProvider.Sut.GetAsync(organizationId, organizationIntegration.Id);
|
var result = await sutProvider.Sut.GetAsync(organizationId, integrationId);
|
||||||
|
|
||||||
Assert.NotNull(result);
|
Assert.NotNull(result);
|
||||||
Assert.Empty(result);
|
Assert.Empty(result);
|
||||||
|
await sutProvider.GetDependency<IGetOrganizationIntegrationConfigurationsQuery>().Received(1)
|
||||||
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().Received(1)
|
.GetManyByIntegrationAsync(organizationId, integrationId);
|
||||||
.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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task GetAsync_UserIsNotOrganizationAdmin_ThrowsNotFound(
|
public async Task GetAsync_UserIsNotOrganizationAdmin_ThrowsNotFound(
|
||||||
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
||||||
Guid organizationId)
|
Guid organizationId,
|
||||||
|
Guid integrationId)
|
||||||
{
|
{
|
||||||
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
|
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
|
||||||
sutProvider.GetDependency<ICurrentContext>()
|
sutProvider.GetDependency<ICurrentContext>()
|
||||||
.OrganizationOwner(organizationId)
|
.OrganizationOwner(organizationId)
|
||||||
.Returns(false);
|
.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]
|
[Theory, BitAutoData]
|
||||||
public async Task PostAsync_AllParamsProvided_Slack_Succeeds(
|
public async Task PostAsync_AllParamsProvided_Succeeds(
|
||||||
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
||||||
Guid organizationId,
|
Guid organizationId,
|
||||||
OrganizationIntegration organizationIntegration,
|
Guid integrationId,
|
||||||
OrganizationIntegrationConfiguration organizationIntegrationConfiguration,
|
OrganizationIntegrationConfiguration configuration,
|
||||||
OrganizationIntegrationConfigurationRequestModel model)
|
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.Sut.Url = Substitute.For<IUrlHelper>();
|
||||||
sutProvider.GetDependency<ICurrentContext>()
|
sutProvider.GetDependency<ICurrentContext>()
|
||||||
.OrganizationOwner(organizationId)
|
.OrganizationOwner(organizationId)
|
||||||
.Returns(true);
|
.Returns(true);
|
||||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
sutProvider.GetDependency<ICreateOrganizationIntegrationConfigurationCommand>()
|
||||||
.GetByIdAsync(Arg.Any<Guid>())
|
.CreateAsync(organizationId, integrationId, Arg.Any<OrganizationIntegrationConfiguration>())
|
||||||
.Returns(organizationIntegration);
|
.Returns(configuration);
|
||||||
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)
|
var createResponse = await sutProvider.Sut.CreateAsync(organizationId, integrationId, model);
|
||||||
.CreateAsync(Arg.Any<OrganizationIntegrationConfiguration>());
|
|
||||||
|
await sutProvider.GetDependency<ICreateOrganizationIntegrationConfigurationCommand>().Received(1)
|
||||||
|
.CreateAsync(organizationId, integrationId, Arg.Any<OrganizationIntegrationConfiguration>());
|
||||||
Assert.IsType<OrganizationIntegrationConfigurationResponseModel>(createResponse);
|
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]
|
[Theory, BitAutoData]
|
||||||
public async Task PostAsync_AllParamsProvided_Webhook_Succeeds(
|
public async Task PostAsync_UserIsNotOrganizationAdmin_ThrowsNotFound(
|
||||||
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
||||||
Guid organizationId,
|
Guid organizationId,
|
||||||
OrganizationIntegration organizationIntegration,
|
Guid integrationId)
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
|
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
|
||||||
sutProvider.GetDependency<ICurrentContext>()
|
sutProvider.GetDependency<ICurrentContext>()
|
||||||
.OrganizationOwner(organizationId)
|
.OrganizationOwner(organizationId)
|
||||||
.Returns(false);
|
.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]
|
[Theory, BitAutoData]
|
||||||
public async Task UpdateAsync_AllParamsProvided_Slack_Succeeds(
|
public async Task UpdateAsync_AllParamsProvided_Succeeds(
|
||||||
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
||||||
Guid organizationId,
|
Guid organizationId,
|
||||||
OrganizationIntegration organizationIntegration,
|
Guid integrationId,
|
||||||
OrganizationIntegrationConfiguration organizationIntegrationConfiguration,
|
Guid configurationId,
|
||||||
|
OrganizationIntegrationConfiguration configuration,
|
||||||
OrganizationIntegrationConfigurationRequestModel model)
|
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.Sut.Url = Substitute.For<IUrlHelper>();
|
||||||
sutProvider.GetDependency<ICurrentContext>()
|
sutProvider.GetDependency<ICurrentContext>()
|
||||||
.OrganizationOwner(organizationId)
|
.OrganizationOwner(organizationId)
|
||||||
.Returns(true);
|
.Returns(true);
|
||||||
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
|
sutProvider.GetDependency<IUpdateOrganizationIntegrationConfigurationCommand>()
|
||||||
.GetByIdAsync(Arg.Any<Guid>())
|
.UpdateAsync(organizationId, integrationId, configurationId, Arg.Any<OrganizationIntegrationConfiguration>())
|
||||||
.Returns(organizationIntegration);
|
.Returns(configuration);
|
||||||
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)
|
var updateResponse = await sutProvider.Sut.UpdateAsync(organizationId, integrationId, configurationId, model);
|
||||||
.ReplaceAsync(Arg.Any<OrganizationIntegrationConfiguration>());
|
|
||||||
|
await sutProvider.GetDependency<IUpdateOrganizationIntegrationConfigurationCommand>().Received(1)
|
||||||
|
.UpdateAsync(organizationId, integrationId, configurationId, Arg.Any<OrganizationIntegrationConfiguration>());
|
||||||
Assert.IsType<OrganizationIntegrationConfigurationResponseModel>(updateResponse);
|
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]
|
[Theory, BitAutoData]
|
||||||
public async Task UpdateAsync_AllParamsProvided_Webhook_Succeeds(
|
public async Task UpdateAsync_UserIsNotOrganizationAdmin_ThrowsNotFound(
|
||||||
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
|
||||||
Guid organizationId,
|
Guid organizationId,
|
||||||
OrganizationIntegration organizationIntegration,
|
Guid integrationId,
|
||||||
OrganizationIntegrationConfiguration organizationIntegrationConfiguration,
|
Guid configurationId)
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
|
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
|
||||||
sutProvider.GetDependency<ICurrentContext>()
|
sutProvider.GetDependency<ICurrentContext>()
|
||||||
.OrganizationOwner(organizationId)
|
.OrganizationOwner(organizationId)
|
||||||
.Returns(false);
|
.Returns(false);
|
||||||
|
|
||||||
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.UpdateAsync(
|
await Assert.ThrowsAsync<NotFoundException>(async () =>
|
||||||
organizationId,
|
await sutProvider.Sut.UpdateAsync(organizationId, integrationId, configurationId, new OrganizationIntegrationConfigurationRequestModel()));
|
||||||
Guid.Empty,
|
|
||||||
Guid.Empty,
|
|
||||||
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.Repositories;
|
||||||
using Bit.Core.Settings;
|
using Bit.Core.Settings;
|
||||||
using Bit.Core.Utilities;
|
using Bit.Core.Utilities;
|
||||||
@@ -32,6 +34,7 @@ public class EventIntegrationServiceCollectionExtensionsTests
|
|||||||
|
|
||||||
// Mock required repository dependencies for commands
|
// Mock required repository dependencies for commands
|
||||||
_services.TryAddScoped(_ => Substitute.For<IOrganizationIntegrationRepository>());
|
_services.TryAddScoped(_ => Substitute.For<IOrganizationIntegrationRepository>());
|
||||||
|
_services.TryAddScoped(_ => Substitute.For<IOrganizationIntegrationConfigurationRepository>());
|
||||||
_services.TryAddScoped(_ => Substitute.For<IOrganizationRepository>());
|
_services.TryAddScoped(_ => Substitute.For<IOrganizationRepository>());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,6 +48,9 @@ public class EventIntegrationServiceCollectionExtensionsTests
|
|||||||
var cache = provider.GetRequiredKeyedService<IFusionCache>(EventIntegrationsCacheConstants.CacheName);
|
var cache = provider.GetRequiredKeyedService<IFusionCache>(EventIntegrationsCacheConstants.CacheName);
|
||||||
Assert.NotNull(cache);
|
Assert.NotNull(cache);
|
||||||
|
|
||||||
|
var validator = provider.GetRequiredService<IOrganizationIntegrationConfigurationValidator>();
|
||||||
|
Assert.NotNull(validator);
|
||||||
|
|
||||||
using var scope = provider.CreateScope();
|
using var scope = provider.CreateScope();
|
||||||
var sp = scope.ServiceProvider;
|
var sp = scope.ServiceProvider;
|
||||||
|
|
||||||
@@ -52,6 +58,11 @@ public class EventIntegrationServiceCollectionExtensionsTests
|
|||||||
Assert.NotNull(sp.GetService<IUpdateOrganizationIntegrationCommand>());
|
Assert.NotNull(sp.GetService<IUpdateOrganizationIntegrationCommand>());
|
||||||
Assert.NotNull(sp.GetService<IDeleteOrganizationIntegrationCommand>());
|
Assert.NotNull(sp.GetService<IDeleteOrganizationIntegrationCommand>());
|
||||||
Assert.NotNull(sp.GetService<IGetOrganizationIntegrationsQuery>());
|
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]
|
[Fact]
|
||||||
@@ -61,8 +72,11 @@ public class EventIntegrationServiceCollectionExtensionsTests
|
|||||||
|
|
||||||
var createIntegrationDescriptor = _services.First(s =>
|
var createIntegrationDescriptor = _services.First(s =>
|
||||||
s.ServiceType == typeof(ICreateOrganizationIntegrationCommand));
|
s.ServiceType == typeof(ICreateOrganizationIntegrationCommand));
|
||||||
|
var createConfigDescriptor = _services.First(s =>
|
||||||
|
s.ServiceType == typeof(ICreateOrganizationIntegrationConfigurationCommand));
|
||||||
|
|
||||||
Assert.Equal(ServiceLifetime.Scoped, createIntegrationDescriptor.Lifetime);
|
Assert.Equal(ServiceLifetime.Scoped, createIntegrationDescriptor.Lifetime);
|
||||||
|
Assert.Equal(ServiceLifetime.Scoped, createConfigDescriptor.Lifetime);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -117,7 +131,7 @@ public class EventIntegrationServiceCollectionExtensionsTests
|
|||||||
_services.AddEventIntegrationsCommandsQueries(_globalSettings);
|
_services.AddEventIntegrationsCommandsQueries(_globalSettings);
|
||||||
|
|
||||||
var createConfigCmdDescriptors = _services.Where(s =>
|
var createConfigCmdDescriptors = _services.Where(s =>
|
||||||
s.ServiceType == typeof(ICreateOrganizationIntegrationCommand)).ToList();
|
s.ServiceType == typeof(ICreateOrganizationIntegrationConfigurationCommand)).ToList();
|
||||||
Assert.Single(createConfigCmdDescriptors);
|
Assert.Single(createConfigCmdDescriptors);
|
||||||
|
|
||||||
var updateIntegrationCmdDescriptors = _services.Where(s =>
|
var updateIntegrationCmdDescriptors = _services.Where(s =>
|
||||||
@@ -148,6 +162,29 @@ public class EventIntegrationServiceCollectionExtensionsTests
|
|||||||
Assert.Single(createCmdDescriptors);
|
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)
|
private static GlobalSettings CreateGlobalSettings(Dictionary<string, string?> data)
|
||||||
{
|
{
|
||||||
var config = new ConfigurationBuilder()
|
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, keyWithDifferentIntegration);
|
||||||
Assert.NotEqual(keyWithEvent, keyWithDifferentOrganization);
|
Assert.NotEqual(keyWithEvent, keyWithDifferentOrganization);
|
||||||
Assert.Equal(keyWithEvent, keyWithSameDetails);
|
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]
|
[Theory, BitAutoData]
|
||||||
|
|||||||
Reference in New Issue
Block a user