1
0
mirror of https://github.com/bitwarden/server synced 2026-01-13 22:13:43 +00:00

Move all event integration code to Dirt (#6757)

* Move all event integration code to Dirt

* Format to fix lint
This commit is contained in:
Brant DeBow
2025-12-30 10:59:19 -05:00
committed by GitHub
parent 9a340c0fdd
commit 86a68ab637
158 changed files with 487 additions and 472 deletions

View File

@@ -1,211 +0,0 @@
using Bit.Api.AdminConsole.Controllers;
using Bit.Api.AdminConsole.Models.Request.Organizations;
using Bit.Api.AdminConsole.Models.Response.Organizations;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrations.Interfaces;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using Microsoft.AspNetCore.Mvc;
using NSubstitute;
using Xunit;
namespace Bit.Api.Test.AdminConsole.Controllers;
[ControllerCustomize(typeof(OrganizationIntegrationController))]
[SutProviderCustomize]
public class OrganizationIntegrationControllerTests
{
private readonly OrganizationIntegrationRequestModel _webhookRequestModel = new()
{
Configuration = null,
Type = IntegrationType.Webhook
};
[Theory, BitAutoData]
public async Task GetAsync_UserIsNotOrganizationAdmin_ThrowsNotFound(
SutProvider<OrganizationIntegrationController> sutProvider,
Guid organizationId)
{
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.GetDependency<ICurrentContext>()
.OrganizationOwner(organizationId)
.Returns(false);
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.GetAsync(organizationId));
}
[Theory, BitAutoData]
public async Task GetAsync_IntegrationsExist_ReturnsIntegrations(
SutProvider<OrganizationIntegrationController> sutProvider,
Guid organizationId,
List<OrganizationIntegration> integrations)
{
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.GetDependency<ICurrentContext>()
.OrganizationOwner(organizationId)
.Returns(true);
sutProvider.GetDependency<IGetOrganizationIntegrationsQuery>()
.GetManyByOrganizationAsync(organizationId)
.Returns(integrations);
var result = await sutProvider.Sut.GetAsync(organizationId);
await sutProvider.GetDependency<IGetOrganizationIntegrationsQuery>().Received(1)
.GetManyByOrganizationAsync(organizationId);
Assert.Equal(integrations.Count, result.Count);
Assert.All(result, r => Assert.IsType<OrganizationIntegrationResponseModel>(r));
}
[Theory, BitAutoData]
public async Task GetAsync_NoIntegrations_ReturnsEmptyList(
SutProvider<OrganizationIntegrationController> sutProvider,
Guid organizationId)
{
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.GetDependency<ICurrentContext>()
.OrganizationOwner(organizationId)
.Returns(true);
sutProvider.GetDependency<IGetOrganizationIntegrationsQuery>()
.GetManyByOrganizationAsync(organizationId)
.Returns([]);
var result = await sutProvider.Sut.GetAsync(organizationId);
Assert.Empty(result);
}
[Theory, BitAutoData]
public async Task CreateAsync_AllParamsProvided_Succeeds(
SutProvider<OrganizationIntegrationController> sutProvider,
Guid organizationId,
OrganizationIntegration integration)
{
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.GetDependency<ICurrentContext>()
.OrganizationOwner(organizationId)
.Returns(true);
sutProvider.GetDependency<ICreateOrganizationIntegrationCommand>()
.CreateAsync(Arg.Any<OrganizationIntegration>())
.Returns(integration);
var response = await sutProvider.Sut.CreateAsync(organizationId, _webhookRequestModel);
await sutProvider.GetDependency<ICreateOrganizationIntegrationCommand>().Received(1)
.CreateAsync(Arg.Is<OrganizationIntegration>(i =>
i.OrganizationId == organizationId &&
i.Type == IntegrationType.Webhook));
Assert.IsType<OrganizationIntegrationResponseModel>(response);
}
[Theory, BitAutoData]
public async Task CreateAsync_UserIsNotOrganizationAdmin_ThrowsNotFound(
SutProvider<OrganizationIntegrationController> sutProvider,
Guid organizationId)
{
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.GetDependency<ICurrentContext>()
.OrganizationOwner(organizationId)
.Returns(false);
await Assert.ThrowsAsync<NotFoundException>(async () =>
await sutProvider.Sut.CreateAsync(organizationId, _webhookRequestModel));
}
[Theory, BitAutoData]
public async Task DeleteAsync_AllParamsProvided_Succeeds(
SutProvider<OrganizationIntegrationController> sutProvider,
Guid organizationId,
Guid integrationId)
{
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.GetDependency<ICurrentContext>()
.OrganizationOwner(organizationId)
.Returns(true);
await sutProvider.Sut.DeleteAsync(organizationId, integrationId);
await sutProvider.GetDependency<IDeleteOrganizationIntegrationCommand>().Received(1)
.DeleteAsync(organizationId, integrationId);
}
[Theory, BitAutoData]
[Obsolete("Obsolete")]
public async Task PostDeleteAsync_AllParamsProvided_Succeeds(
SutProvider<OrganizationIntegrationController> sutProvider,
Guid organizationId,
Guid integrationId)
{
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.GetDependency<ICurrentContext>()
.OrganizationOwner(organizationId)
.Returns(true);
await sutProvider.Sut.PostDeleteAsync(organizationId, integrationId);
await sutProvider.GetDependency<IDeleteOrganizationIntegrationCommand>().Received(1)
.DeleteAsync(organizationId, integrationId);
}
[Theory, BitAutoData]
public async Task DeleteAsync_UserIsNotOrganizationAdmin_ThrowsNotFound(
SutProvider<OrganizationIntegrationController> sutProvider,
Guid organizationId,
Guid integrationId)
{
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.GetDependency<ICurrentContext>()
.OrganizationOwner(organizationId)
.Returns(false);
await Assert.ThrowsAsync<NotFoundException>(async () =>
await sutProvider.Sut.DeleteAsync(organizationId, integrationId));
}
[Theory, BitAutoData]
public async Task UpdateAsync_AllParamsProvided_Succeeds(
SutProvider<OrganizationIntegrationController> sutProvider,
Guid organizationId,
Guid integrationId,
OrganizationIntegration integration)
{
integration.OrganizationId = organizationId;
integration.Id = integrationId;
integration.Type = IntegrationType.Webhook;
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.GetDependency<ICurrentContext>()
.OrganizationOwner(organizationId)
.Returns(true);
sutProvider.GetDependency<IUpdateOrganizationIntegrationCommand>()
.UpdateAsync(organizationId, integrationId, Arg.Any<OrganizationIntegration>())
.Returns(integration);
var response = await sutProvider.Sut.UpdateAsync(organizationId, integrationId, _webhookRequestModel);
await sutProvider.GetDependency<IUpdateOrganizationIntegrationCommand>().Received(1)
.UpdateAsync(organizationId, integrationId, Arg.Is<OrganizationIntegration>(i =>
i.OrganizationId == organizationId &&
i.Type == IntegrationType.Webhook));
Assert.IsType<OrganizationIntegrationResponseModel>(response);
Assert.Equal(IntegrationType.Webhook, response.Type);
}
[Theory, BitAutoData]
public async Task UpdateAsync_UserIsNotOrganizationAdmin_ThrowsNotFound(
SutProvider<OrganizationIntegrationController> sutProvider,
Guid organizationId,
Guid integrationId)
{
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.GetDependency<ICurrentContext>()
.OrganizationOwner(organizationId)
.Returns(false);
await Assert.ThrowsAsync<NotFoundException>(async () =>
await sutProvider.Sut.UpdateAsync(organizationId, integrationId, _webhookRequestModel));
}
}

View File

@@ -1,211 +0,0 @@
using Bit.Api.AdminConsole.Controllers;
using Bit.Api.AdminConsole.Models.Request.Organizations;
using Bit.Api.AdminConsole.Models.Response.Organizations;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.EventIntegrations.OrganizationIntegrationConfigurations.Interfaces;
using Bit.Core.Context;
using Bit.Core.Exceptions;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using Microsoft.AspNetCore.Mvc;
using NSubstitute;
using Xunit;
namespace Bit.Api.Test.AdminConsole.Controllers;
[ControllerCustomize(typeof(OrganizationIntegrationConfigurationController))]
[SutProviderCustomize]
public class OrganizationIntegrationsConfigurationControllerTests
{
[Theory, BitAutoData]
public async Task DeleteAsync_AllParamsProvided_Succeeds(
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
Guid organizationId,
Guid integrationId,
Guid configurationId)
{
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.GetDependency<ICurrentContext>()
.OrganizationOwner(organizationId)
.Returns(true);
await sutProvider.Sut.DeleteAsync(organizationId, integrationId, configurationId);
await sutProvider.GetDependency<IDeleteOrganizationIntegrationConfigurationCommand>().Received(1)
.DeleteAsync(organizationId, integrationId, configurationId);
}
[Theory, BitAutoData]
[Obsolete("Obsolete")]
public async Task PostDeleteAsync_AllParamsProvided_Succeeds(
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
Guid organizationId,
Guid integrationId,
Guid configurationId)
{
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.GetDependency<ICurrentContext>()
.OrganizationOwner(organizationId)
.Returns(true);
await sutProvider.Sut.PostDeleteAsync(organizationId, integrationId, configurationId);
await sutProvider.GetDependency<IDeleteOrganizationIntegrationConfigurationCommand>().Received(1)
.DeleteAsync(organizationId, integrationId, configurationId);
}
[Theory, BitAutoData]
public async Task DeleteAsync_UserIsNotOrganizationAdmin_ThrowsNotFound(
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
Guid organizationId,
Guid integrationId,
Guid configurationId)
{
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.GetDependency<ICurrentContext>()
.OrganizationOwner(organizationId)
.Returns(false);
await Assert.ThrowsAsync<NotFoundException>(async () =>
await sutProvider.Sut.DeleteAsync(organizationId, integrationId, configurationId));
}
[Theory, BitAutoData]
public async Task GetAsync_ConfigurationsExist_Succeeds(
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
Guid organizationId,
Guid integrationId,
List<OrganizationIntegrationConfiguration> configurations)
{
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.GetDependency<ICurrentContext>()
.OrganizationOwner(organizationId)
.Returns(true);
sutProvider.GetDependency<IGetOrganizationIntegrationConfigurationsQuery>()
.GetManyByIntegrationAsync(organizationId, integrationId)
.Returns(configurations);
var result = await sutProvider.Sut.GetAsync(organizationId, integrationId);
Assert.NotNull(result);
Assert.Equal(configurations.Count, result.Count);
Assert.All(result, r => Assert.IsType<OrganizationIntegrationConfigurationResponseModel>(r));
await sutProvider.GetDependency<IGetOrganizationIntegrationConfigurationsQuery>().Received(1)
.GetManyByIntegrationAsync(organizationId, integrationId);
}
[Theory, BitAutoData]
public async Task GetAsync_NoConfigurationsExist_ReturnsEmptyList(
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
Guid organizationId,
Guid integrationId)
{
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.GetDependency<ICurrentContext>()
.OrganizationOwner(organizationId)
.Returns(true);
sutProvider.GetDependency<IGetOrganizationIntegrationConfigurationsQuery>()
.GetManyByIntegrationAsync(organizationId, integrationId)
.Returns([]);
var result = await sutProvider.Sut.GetAsync(organizationId, integrationId);
Assert.NotNull(result);
Assert.Empty(result);
await sutProvider.GetDependency<IGetOrganizationIntegrationConfigurationsQuery>().Received(1)
.GetManyByIntegrationAsync(organizationId, integrationId);
}
[Theory, BitAutoData]
public async Task GetAsync_UserIsNotOrganizationAdmin_ThrowsNotFound(
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
Guid organizationId,
Guid integrationId)
{
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.GetDependency<ICurrentContext>()
.OrganizationOwner(organizationId)
.Returns(false);
await Assert.ThrowsAsync<NotFoundException>(async () =>
await sutProvider.Sut.GetAsync(organizationId, integrationId));
}
[Theory, BitAutoData]
public async Task PostAsync_AllParamsProvided_Succeeds(
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
Guid organizationId,
Guid integrationId,
OrganizationIntegrationConfiguration configuration,
OrganizationIntegrationConfigurationRequestModel model)
{
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.GetDependency<ICurrentContext>()
.OrganizationOwner(organizationId)
.Returns(true);
sutProvider.GetDependency<ICreateOrganizationIntegrationConfigurationCommand>()
.CreateAsync(organizationId, integrationId, Arg.Any<OrganizationIntegrationConfiguration>())
.Returns(configuration);
var createResponse = await sutProvider.Sut.CreateAsync(organizationId, integrationId, model);
await sutProvider.GetDependency<ICreateOrganizationIntegrationConfigurationCommand>().Received(1)
.CreateAsync(organizationId, integrationId, Arg.Any<OrganizationIntegrationConfiguration>());
Assert.IsType<OrganizationIntegrationConfigurationResponseModel>(createResponse);
}
[Theory, BitAutoData]
public async Task PostAsync_UserIsNotOrganizationAdmin_ThrowsNotFound(
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
Guid organizationId,
Guid integrationId)
{
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.GetDependency<ICurrentContext>()
.OrganizationOwner(organizationId)
.Returns(false);
await Assert.ThrowsAsync<NotFoundException>(async () =>
await sutProvider.Sut.CreateAsync(organizationId, integrationId, new OrganizationIntegrationConfigurationRequestModel()));
}
[Theory, BitAutoData]
public async Task UpdateAsync_AllParamsProvided_Succeeds(
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
Guid organizationId,
Guid integrationId,
Guid configurationId,
OrganizationIntegrationConfiguration configuration,
OrganizationIntegrationConfigurationRequestModel model)
{
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.GetDependency<ICurrentContext>()
.OrganizationOwner(organizationId)
.Returns(true);
sutProvider.GetDependency<IUpdateOrganizationIntegrationConfigurationCommand>()
.UpdateAsync(organizationId, integrationId, configurationId, Arg.Any<OrganizationIntegrationConfiguration>())
.Returns(configuration);
var updateResponse = await sutProvider.Sut.UpdateAsync(organizationId, integrationId, configurationId, model);
await sutProvider.GetDependency<IUpdateOrganizationIntegrationConfigurationCommand>().Received(1)
.UpdateAsync(organizationId, integrationId, configurationId, Arg.Any<OrganizationIntegrationConfiguration>());
Assert.IsType<OrganizationIntegrationConfigurationResponseModel>(updateResponse);
}
[Theory, BitAutoData]
public async Task UpdateAsync_UserIsNotOrganizationAdmin_ThrowsNotFound(
SutProvider<OrganizationIntegrationConfigurationController> sutProvider,
Guid organizationId,
Guid integrationId,
Guid configurationId)
{
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.GetDependency<ICurrentContext>()
.OrganizationOwner(organizationId)
.Returns(false);
await Assert.ThrowsAsync<NotFoundException>(async () =>
await sutProvider.Sut.UpdateAsync(organizationId, integrationId, configurationId, new OrganizationIntegrationConfigurationRequestModel()));
}
}

View File

@@ -1,383 +0,0 @@
#nullable enable
using Bit.Api.AdminConsole.Controllers;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.Extensions.Time.Testing;
using NSubstitute;
using Xunit;
namespace Bit.Api.Test.AdminConsole.Controllers;
[ControllerCustomize(typeof(SlackIntegrationController))]
[SutProviderCustomize]
public class SlackIntegrationControllerTests
{
private const string _slackToken = "xoxb-test-token";
private const string _validSlackCode = "A_test_code";
[Theory, BitAutoData]
public async Task CreateAsync_AllParamsProvided_Succeeds(
SutProvider<SlackIntegrationController> sutProvider,
OrganizationIntegration integration)
{
integration.Type = IntegrationType.Slack;
integration.Configuration = null;
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "SlackIntegration_Create"))
.Returns("https://localhost");
sutProvider.GetDependency<ISlackService>()
.ObtainTokenViaOAuth(_validSlackCode, Arg.Any<string>())
.Returns(_slackToken);
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
.GetByIdAsync(integration.Id)
.Returns(integration);
var state = IntegrationOAuthState.FromIntegration(integration, sutProvider.GetDependency<TimeProvider>());
var requestAction = await sutProvider.Sut.CreateAsync(_validSlackCode, state.ToString());
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().Received(1)
.UpsertAsync(Arg.Any<OrganizationIntegration>());
Assert.IsType<CreatedResult>(requestAction);
}
[Theory, BitAutoData]
public async Task CreateAsync_CodeIsEmpty_ThrowsBadRequest(
SutProvider<SlackIntegrationController> sutProvider,
OrganizationIntegration integration)
{
integration.Type = IntegrationType.Slack;
integration.Configuration = null;
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "SlackIntegration_Create"))
.Returns("https://localhost");
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
.GetByIdAsync(integration.Id)
.Returns(integration);
var state = IntegrationOAuthState.FromIntegration(integration, sutProvider.GetDependency<TimeProvider>());
await Assert.ThrowsAsync<BadRequestException>(async () =>
await sutProvider.Sut.CreateAsync(string.Empty, state.ToString()));
}
[Theory, BitAutoData]
public async Task CreateAsync_CallbackUrlIsEmpty_ThrowsBadRequest(
SutProvider<SlackIntegrationController> sutProvider,
OrganizationIntegration integration)
{
integration.Type = IntegrationType.Slack;
integration.Configuration = null;
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "SlackIntegration_Create"))
.Returns((string?)null);
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
.GetByIdAsync(integration.Id)
.Returns(integration);
var state = IntegrationOAuthState.FromIntegration(integration, sutProvider.GetDependency<TimeProvider>());
await Assert.ThrowsAsync<BadRequestException>(async () =>
await sutProvider.Sut.CreateAsync(_validSlackCode, state.ToString()));
}
[Theory, BitAutoData]
public async Task CreateAsync_SlackServiceReturnsEmpty_ThrowsBadRequest(
SutProvider<SlackIntegrationController> sutProvider,
OrganizationIntegration integration)
{
integration.Type = IntegrationType.Slack;
integration.Configuration = null;
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "SlackIntegration_Create"))
.Returns("https://localhost");
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
.GetByIdAsync(integration.Id)
.Returns(integration);
sutProvider.GetDependency<ISlackService>()
.ObtainTokenViaOAuth(_validSlackCode, Arg.Any<string>())
.Returns(string.Empty);
var state = IntegrationOAuthState.FromIntegration(integration, sutProvider.GetDependency<TimeProvider>());
await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.CreateAsync(_validSlackCode, state.ToString()));
}
[Theory, BitAutoData]
public async Task CreateAsync_StateEmpty_ThrowsNotFound(
SutProvider<SlackIntegrationController> sutProvider)
{
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "SlackIntegration_Create"))
.Returns("https://localhost");
sutProvider.GetDependency<ISlackService>()
.ObtainTokenViaOAuth(_validSlackCode, Arg.Any<string>())
.Returns(_slackToken);
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.CreateAsync(_validSlackCode, string.Empty));
}
[Theory, BitAutoData]
public async Task CreateAsync_StateExpired_ThrowsNotFound(
SutProvider<SlackIntegrationController> sutProvider,
OrganizationIntegration integration)
{
var timeProvider = new FakeTimeProvider(new DateTime(2024, 4, 3, 2, 1, 0, DateTimeKind.Utc));
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "SlackIntegration_Create"))
.Returns("https://localhost");
sutProvider.GetDependency<ISlackService>()
.ObtainTokenViaOAuth(_validSlackCode, Arg.Any<string>())
.Returns(_slackToken);
var state = IntegrationOAuthState.FromIntegration(integration, timeProvider);
timeProvider.Advance(TimeSpan.FromMinutes(30));
sutProvider.SetDependency<TimeProvider>(timeProvider);
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.CreateAsync(_validSlackCode, state.ToString()));
}
[Theory, BitAutoData]
public async Task CreateAsync_StateHasNonexistentIntegration_ThrowsNotFound(
SutProvider<SlackIntegrationController> sutProvider,
OrganizationIntegration integration)
{
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "SlackIntegration_Create"))
.Returns("https://localhost");
sutProvider.GetDependency<ISlackService>()
.ObtainTokenViaOAuth(_validSlackCode, Arg.Any<string>())
.Returns(_slackToken);
var state = IntegrationOAuthState.FromIntegration(integration, sutProvider.GetDependency<TimeProvider>());
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.CreateAsync(_validSlackCode, state.ToString()));
}
[Theory, BitAutoData]
public async Task CreateAsync_StateHasWrongOrganizationHash_ThrowsNotFound(
SutProvider<SlackIntegrationController> sutProvider,
OrganizationIntegration integration,
OrganizationIntegration wrongOrgIntegration)
{
wrongOrgIntegration.Id = integration.Id;
wrongOrgIntegration.Type = IntegrationType.Slack;
wrongOrgIntegration.Configuration = null;
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "SlackIntegration_Create"))
.Returns("https://localhost");
sutProvider.GetDependency<ISlackService>()
.ObtainTokenViaOAuth(_validSlackCode, Arg.Any<string>())
.Returns(_slackToken);
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
.GetByIdAsync(integration.Id)
.Returns(wrongOrgIntegration);
var state = IntegrationOAuthState.FromIntegration(integration, sutProvider.GetDependency<TimeProvider>());
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.CreateAsync(_validSlackCode, state.ToString()));
}
[Theory, BitAutoData]
public async Task CreateAsync_StateHasNonEmptyIntegration_ThrowsNotFound(
SutProvider<SlackIntegrationController> sutProvider,
OrganizationIntegration integration)
{
integration.Type = IntegrationType.Slack;
integration.Configuration = "{}";
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "SlackIntegration_Create"))
.Returns("https://localhost");
sutProvider.GetDependency<ISlackService>()
.ObtainTokenViaOAuth(_validSlackCode, Arg.Any<string>())
.Returns(_slackToken);
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
.GetByIdAsync(integration.Id)
.Returns(integration);
var state = IntegrationOAuthState.FromIntegration(integration, sutProvider.GetDependency<TimeProvider>());
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.CreateAsync(_validSlackCode, state.ToString()));
}
[Theory, BitAutoData]
public async Task CreateAsync_StateHasNonSlackIntegration_ThrowsNotFound(
SutProvider<SlackIntegrationController> sutProvider,
OrganizationIntegration integration)
{
integration.Type = IntegrationType.Hec;
integration.Configuration = null;
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "SlackIntegration_Create"))
.Returns("https://localhost");
sutProvider.GetDependency<ISlackService>()
.ObtainTokenViaOAuth(_validSlackCode, Arg.Any<string>())
.Returns(_slackToken);
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
.GetByIdAsync(integration.Id)
.Returns(integration);
var state = IntegrationOAuthState.FromIntegration(integration, sutProvider.GetDependency<TimeProvider>());
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.CreateAsync(_validSlackCode, state.ToString()));
}
[Theory, BitAutoData]
public async Task RedirectAsync_Success(
SutProvider<SlackIntegrationController> sutProvider,
OrganizationIntegration integration)
{
integration.Configuration = null;
var expectedUrl = "https://localhost/";
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "SlackIntegration_Create"))
.Returns(expectedUrl);
sutProvider.GetDependency<ICurrentContext>()
.OrganizationOwner(integration.OrganizationId)
.Returns(true);
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
.GetManyByOrganizationAsync(integration.OrganizationId)
.Returns([]);
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
.CreateAsync(Arg.Any<OrganizationIntegration>())
.Returns(integration);
sutProvider.GetDependency<ISlackService>().GetRedirectUrl(Arg.Any<string>(), Arg.Any<string>()).Returns(expectedUrl);
var expectedState = IntegrationOAuthState.FromIntegration(integration, sutProvider.GetDependency<TimeProvider>());
var requestAction = await sutProvider.Sut.RedirectAsync(integration.OrganizationId);
Assert.IsType<RedirectResult>(requestAction);
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().Received(1)
.CreateAsync(Arg.Any<OrganizationIntegration>());
sutProvider.GetDependency<ISlackService>().Received(1).GetRedirectUrl(Arg.Any<string>(), expectedState.ToString());
}
[Theory, BitAutoData]
public async Task RedirectAsync_IntegrationAlreadyExistsWithNullConfig_Success(
SutProvider<SlackIntegrationController> sutProvider,
Guid organizationId,
OrganizationIntegration integration)
{
integration.OrganizationId = organizationId;
integration.Configuration = null;
integration.Type = IntegrationType.Slack;
var expectedUrl = "https://localhost/";
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "SlackIntegration_Create"))
.Returns(expectedUrl);
sutProvider.GetDependency<ICurrentContext>()
.OrganizationOwner(organizationId)
.Returns(true);
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
.GetManyByOrganizationAsync(organizationId)
.Returns([integration]);
sutProvider.GetDependency<ISlackService>().GetRedirectUrl(Arg.Any<string>(), Arg.Any<string>()).Returns(expectedUrl);
var requestAction = await sutProvider.Sut.RedirectAsync(organizationId);
var expectedState = IntegrationOAuthState.FromIntegration(integration, sutProvider.GetDependency<TimeProvider>());
Assert.IsType<RedirectResult>(requestAction);
sutProvider.GetDependency<ISlackService>().Received(1).GetRedirectUrl(Arg.Any<string>(), expectedState.ToString());
}
[Theory, BitAutoData]
public async Task RedirectAsync_IntegrationAlreadyExistsWithConfig_ThrowsBadRequest(
SutProvider<SlackIntegrationController> sutProvider,
Guid organizationId,
OrganizationIntegration integration)
{
integration.OrganizationId = organizationId;
integration.Configuration = "{}";
integration.Type = IntegrationType.Slack;
var expectedUrl = "https://localhost/";
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "SlackIntegration_Create"))
.Returns(expectedUrl);
sutProvider.GetDependency<ICurrentContext>()
.OrganizationOwner(organizationId)
.Returns(true);
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
.GetManyByOrganizationAsync(organizationId)
.Returns([integration]);
sutProvider.GetDependency<ISlackService>().GetRedirectUrl(Arg.Any<string>(), Arg.Any<string>()).Returns(expectedUrl);
await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.RedirectAsync(organizationId));
}
[Theory, BitAutoData]
public async Task RedirectAsync_CallbackUrlReturnsEmpty_ThrowsBadRequest(
SutProvider<SlackIntegrationController> sutProvider,
Guid organizationId)
{
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "SlackIntegration_Create"))
.Returns((string?)null);
sutProvider.GetDependency<ICurrentContext>()
.OrganizationOwner(organizationId)
.Returns(true);
await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.RedirectAsync(organizationId));
}
[Theory, BitAutoData]
public async Task RedirectAsync_SlackServiceReturnsEmpty_ThrowsNotFound(
SutProvider<SlackIntegrationController> sutProvider,
Guid organizationId,
OrganizationIntegration integration)
{
integration.OrganizationId = organizationId;
integration.Configuration = null;
var expectedUrl = "https://localhost/";
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "SlackIntegration_Create"))
.Returns(expectedUrl);
sutProvider.GetDependency<ICurrentContext>()
.OrganizationOwner(organizationId)
.Returns(true);
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
.GetManyByOrganizationAsync(organizationId)
.Returns([]);
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
.CreateAsync(Arg.Any<OrganizationIntegration>())
.Returns(integration);
sutProvider.GetDependency<ISlackService>().GetRedirectUrl(Arg.Any<string>(), Arg.Any<string>()).Returns(string.Empty);
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.RedirectAsync(organizationId));
}
[Theory, BitAutoData]
public async Task RedirectAsync_UserIsNotOrganizationAdmin_ThrowsNotFound(SutProvider<SlackIntegrationController> sutProvider,
Guid organizationId)
{
sutProvider.GetDependency<ICurrentContext>()
.OrganizationOwner(organizationId)
.Returns(false);
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.RedirectAsync(organizationId));
}
}

View File

@@ -1,436 +0,0 @@
#nullable enable
using Bit.Api.AdminConsole.Controllers;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
using Bit.Core.Context;
using Bit.Core.Enums;
using Bit.Core.Exceptions;
using Bit.Core.Models.Teams;
using Bit.Core.Repositories;
using Bit.Core.Services;
using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Integration.AspNet.Core;
using Microsoft.Extensions.Time.Testing;
using NSubstitute;
using Xunit;
namespace Bit.Api.Test.AdminConsole.Controllers;
[ControllerCustomize(typeof(TeamsIntegrationController))]
[SutProviderCustomize]
public class TeamsIntegrationControllerTests
{
private const string _teamsToken = "test-token";
private const string _validTeamsCode = "A_test_code";
[Theory, BitAutoData]
public async Task CreateAsync_AllParamsProvided_Succeeds(
SutProvider<TeamsIntegrationController> sutProvider,
OrganizationIntegration integration)
{
integration.Type = IntegrationType.Teams;
integration.Configuration = null;
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "TeamsIntegration_Create"))
.Returns("https://localhost");
sutProvider.GetDependency<ITeamsService>()
.ObtainTokenViaOAuth(_validTeamsCode, Arg.Any<string>())
.Returns(_teamsToken);
sutProvider.GetDependency<ITeamsService>()
.GetJoinedTeamsAsync(_teamsToken)
.Returns([
new TeamInfo() { DisplayName = "Test Team", Id = Guid.NewGuid().ToString(), TenantId = Guid.NewGuid().ToString() }
]);
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
.GetByIdAsync(integration.Id)
.Returns(integration);
var state = IntegrationOAuthState.FromIntegration(integration, sutProvider.GetDependency<TimeProvider>());
var requestAction = await sutProvider.Sut.CreateAsync(_validTeamsCode, state.ToString());
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().Received(1)
.UpsertAsync(Arg.Any<OrganizationIntegration>());
Assert.IsType<CreatedResult>(requestAction);
}
[Theory, BitAutoData]
public async Task CreateAsync_CallbackUrlIsEmpty_ThrowsBadRequest(
SutProvider<TeamsIntegrationController> sutProvider,
OrganizationIntegration integration)
{
integration.Type = IntegrationType.Teams;
integration.Configuration = null;
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "TeamsIntegration_Create"))
.Returns((string?)null);
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
.GetByIdAsync(integration.Id)
.Returns(integration);
var state = IntegrationOAuthState.FromIntegration(integration, sutProvider.GetDependency<TimeProvider>());
await Assert.ThrowsAsync<BadRequestException>(async () =>
await sutProvider.Sut.CreateAsync(_validTeamsCode, state.ToString()));
}
[Theory, BitAutoData]
public async Task CreateAsync_CodeIsEmpty_ThrowsBadRequest(
SutProvider<TeamsIntegrationController> sutProvider,
OrganizationIntegration integration)
{
integration.Type = IntegrationType.Teams;
integration.Configuration = null;
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "TeamsIntegration_Create"))
.Returns("https://localhost");
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
.GetByIdAsync(integration.Id)
.Returns(integration);
var state = IntegrationOAuthState.FromIntegration(integration, sutProvider.GetDependency<TimeProvider>());
await Assert.ThrowsAsync<BadRequestException>(async () =>
await sutProvider.Sut.CreateAsync(string.Empty, state.ToString()));
}
[Theory, BitAutoData]
public async Task CreateAsync_NoTeamsFound_ThrowsBadRequest(
SutProvider<TeamsIntegrationController> sutProvider,
OrganizationIntegration integration)
{
integration.Type = IntegrationType.Teams;
integration.Configuration = null;
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "TeamsIntegration_Create"))
.Returns("https://localhost");
sutProvider.GetDependency<ITeamsService>()
.ObtainTokenViaOAuth(_validTeamsCode, Arg.Any<string>())
.Returns(_teamsToken);
sutProvider.GetDependency<ITeamsService>()
.GetJoinedTeamsAsync(_teamsToken)
.Returns([]);
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
.GetByIdAsync(integration.Id)
.Returns(integration);
var state = IntegrationOAuthState.FromIntegration(integration, sutProvider.GetDependency<TimeProvider>());
await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.CreateAsync(_validTeamsCode, state.ToString()));
}
[Theory, BitAutoData]
public async Task CreateAsync_TeamsServiceReturnsEmptyToken_ThrowsBadRequest(
SutProvider<TeamsIntegrationController> sutProvider,
OrganizationIntegration integration)
{
integration.Type = IntegrationType.Teams;
integration.Configuration = null;
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "TeamsIntegration_Create"))
.Returns("https://localhost");
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
.GetByIdAsync(integration.Id)
.Returns(integration);
sutProvider.GetDependency<ITeamsService>()
.ObtainTokenViaOAuth(_validTeamsCode, Arg.Any<string>())
.Returns(string.Empty);
var state = IntegrationOAuthState.FromIntegration(integration, sutProvider.GetDependency<TimeProvider>());
await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.CreateAsync(_validTeamsCode, state.ToString()));
}
[Theory, BitAutoData]
public async Task CreateAsync_StateEmpty_ThrowsNotFound(
SutProvider<TeamsIntegrationController> sutProvider)
{
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "TeamsIntegration_Create"))
.Returns("https://localhost");
sutProvider.GetDependency<ITeamsService>()
.ObtainTokenViaOAuth(_validTeamsCode, Arg.Any<string>())
.Returns(_teamsToken);
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.CreateAsync(_validTeamsCode, string.Empty));
}
[Theory, BitAutoData]
public async Task CreateAsync_StateExpired_ThrowsNotFound(
SutProvider<TeamsIntegrationController> sutProvider,
OrganizationIntegration integration)
{
var timeProvider = new FakeTimeProvider(new DateTime(2024, 4, 3, 2, 1, 0, DateTimeKind.Utc));
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "TeamsIntegration_Create"))
.Returns("https://localhost");
sutProvider.GetDependency<ITeamsService>()
.ObtainTokenViaOAuth(_validTeamsCode, Arg.Any<string>())
.Returns(_teamsToken);
var state = IntegrationOAuthState.FromIntegration(integration, timeProvider);
timeProvider.Advance(TimeSpan.FromMinutes(30));
sutProvider.SetDependency<TimeProvider>(timeProvider);
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.CreateAsync(_validTeamsCode, state.ToString()));
}
[Theory, BitAutoData]
public async Task CreateAsync_StateHasNonexistentIntegration_ThrowsNotFound(
SutProvider<TeamsIntegrationController> sutProvider,
OrganizationIntegration integration)
{
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "TeamsIntegration_Create"))
.Returns("https://localhost");
sutProvider.GetDependency<ITeamsService>()
.ObtainTokenViaOAuth(_validTeamsCode, Arg.Any<string>())
.Returns(_teamsToken);
var state = IntegrationOAuthState.FromIntegration(integration, sutProvider.GetDependency<TimeProvider>());
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.CreateAsync(_validTeamsCode, state.ToString()));
}
[Theory, BitAutoData]
public async Task CreateAsync_StateHasWrongOrganizationHash_ThrowsNotFound(
SutProvider<TeamsIntegrationController> sutProvider,
OrganizationIntegration integration,
OrganizationIntegration wrongOrgIntegration)
{
wrongOrgIntegration.Id = integration.Id;
wrongOrgIntegration.Type = IntegrationType.Teams;
wrongOrgIntegration.Configuration = null;
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "TeamsIntegration_Create"))
.Returns("https://localhost");
sutProvider.GetDependency<ITeamsService>()
.ObtainTokenViaOAuth(_validTeamsCode, Arg.Any<string>())
.Returns(_teamsToken);
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
.GetByIdAsync(integration.Id)
.Returns(wrongOrgIntegration);
var state = IntegrationOAuthState.FromIntegration(integration, sutProvider.GetDependency<TimeProvider>());
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.CreateAsync(_validTeamsCode, state.ToString()));
}
[Theory, BitAutoData]
public async Task CreateAsync_StateHasNonEmptyIntegration_ThrowsNotFound(
SutProvider<TeamsIntegrationController> sutProvider,
OrganizationIntegration integration)
{
integration.Type = IntegrationType.Teams;
integration.Configuration = "{}";
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "TeamsIntegration_Create"))
.Returns("https://localhost");
sutProvider.GetDependency<ITeamsService>()
.ObtainTokenViaOAuth(_validTeamsCode, Arg.Any<string>())
.Returns(_teamsToken);
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
.GetByIdAsync(integration.Id)
.Returns(integration);
var state = IntegrationOAuthState.FromIntegration(integration, sutProvider.GetDependency<TimeProvider>());
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.CreateAsync(_validTeamsCode, state.ToString()));
}
[Theory, BitAutoData]
public async Task CreateAsync_StateHasNonTeamsIntegration_ThrowsNotFound(
SutProvider<TeamsIntegrationController> sutProvider,
OrganizationIntegration integration)
{
integration.Type = IntegrationType.Hec;
integration.Configuration = null;
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "TeamsIntegration_Create"))
.Returns("https://localhost");
sutProvider.GetDependency<ITeamsService>()
.ObtainTokenViaOAuth(_validTeamsCode, Arg.Any<string>())
.Returns(_teamsToken);
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
.GetByIdAsync(integration.Id)
.Returns(integration);
var state = IntegrationOAuthState.FromIntegration(integration, sutProvider.GetDependency<TimeProvider>());
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.CreateAsync(_validTeamsCode, state.ToString()));
}
[Theory, BitAutoData]
public async Task RedirectAsync_Success(
SutProvider<TeamsIntegrationController> sutProvider,
OrganizationIntegration integration)
{
integration.Configuration = null;
var expectedUrl = "https://localhost/";
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "TeamsIntegration_Create"))
.Returns(expectedUrl);
sutProvider.GetDependency<ICurrentContext>()
.OrganizationOwner(integration.OrganizationId)
.Returns(true);
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
.GetManyByOrganizationAsync(integration.OrganizationId)
.Returns([]);
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
.CreateAsync(Arg.Any<OrganizationIntegration>())
.Returns(integration);
sutProvider.GetDependency<ITeamsService>().GetRedirectUrl(Arg.Any<string>(), Arg.Any<string>()).Returns(expectedUrl);
var expectedState = IntegrationOAuthState.FromIntegration(integration, sutProvider.GetDependency<TimeProvider>());
var requestAction = await sutProvider.Sut.RedirectAsync(integration.OrganizationId);
Assert.IsType<RedirectResult>(requestAction);
await sutProvider.GetDependency<IOrganizationIntegrationRepository>().Received(1)
.CreateAsync(Arg.Any<OrganizationIntegration>());
sutProvider.GetDependency<ITeamsService>().Received(1).GetRedirectUrl(Arg.Any<string>(), expectedState.ToString());
}
[Theory, BitAutoData]
public async Task RedirectAsync_IntegrationAlreadyExistsWithNullConfig_Success(
SutProvider<TeamsIntegrationController> sutProvider,
Guid organizationId,
OrganizationIntegration integration)
{
integration.OrganizationId = organizationId;
integration.Configuration = null;
integration.Type = IntegrationType.Teams;
var expectedUrl = "https://localhost/";
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "TeamsIntegration_Create"))
.Returns(expectedUrl);
sutProvider.GetDependency<ICurrentContext>()
.OrganizationOwner(organizationId)
.Returns(true);
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
.GetManyByOrganizationAsync(organizationId)
.Returns([integration]);
sutProvider.GetDependency<ITeamsService>().GetRedirectUrl(Arg.Any<string>(), Arg.Any<string>()).Returns(expectedUrl);
var requestAction = await sutProvider.Sut.RedirectAsync(organizationId);
var expectedState = IntegrationOAuthState.FromIntegration(integration, sutProvider.GetDependency<TimeProvider>());
Assert.IsType<RedirectResult>(requestAction);
sutProvider.GetDependency<ITeamsService>().Received(1).GetRedirectUrl(Arg.Any<string>(), expectedState.ToString());
}
[Theory, BitAutoData]
public async Task RedirectAsync_CallbackUrlIsEmpty_ThrowsBadRequest(
SutProvider<TeamsIntegrationController> sutProvider,
Guid organizationId,
OrganizationIntegration integration)
{
integration.OrganizationId = organizationId;
integration.Configuration = null;
integration.Type = IntegrationType.Teams;
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "TeamsIntegration_Create"))
.Returns((string?)null);
sutProvider.GetDependency<ICurrentContext>()
.OrganizationOwner(organizationId)
.Returns(true);
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
.GetManyByOrganizationAsync(organizationId)
.Returns([integration]);
await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.RedirectAsync(organizationId));
}
[Theory, BitAutoData]
public async Task RedirectAsync_IntegrationAlreadyExistsWithConfig_ThrowsBadRequest(
SutProvider<TeamsIntegrationController> sutProvider,
Guid organizationId,
OrganizationIntegration integration)
{
integration.OrganizationId = organizationId;
integration.Configuration = "{}";
integration.Type = IntegrationType.Teams;
var expectedUrl = "https://localhost/";
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "TeamsIntegration_Create"))
.Returns(expectedUrl);
sutProvider.GetDependency<ICurrentContext>()
.OrganizationOwner(organizationId)
.Returns(true);
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
.GetManyByOrganizationAsync(organizationId)
.Returns([integration]);
sutProvider.GetDependency<ITeamsService>().GetRedirectUrl(Arg.Any<string>(), Arg.Any<string>()).Returns(expectedUrl);
await Assert.ThrowsAsync<BadRequestException>(async () => await sutProvider.Sut.RedirectAsync(organizationId));
}
[Theory, BitAutoData]
public async Task RedirectAsync_TeamsServiceReturnsEmpty_ThrowsNotFound(
SutProvider<TeamsIntegrationController> sutProvider,
Guid organizationId,
OrganizationIntegration integration)
{
integration.OrganizationId = organizationId;
integration.Configuration = null;
var expectedUrl = "https://localhost/";
sutProvider.Sut.Url = Substitute.For<IUrlHelper>();
sutProvider.Sut.Url
.RouteUrl(Arg.Is<UrlRouteContext>(c => c.RouteName == "TeamsIntegration_Create"))
.Returns(expectedUrl);
sutProvider.GetDependency<ICurrentContext>()
.OrganizationOwner(organizationId)
.Returns(true);
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
.GetManyByOrganizationAsync(organizationId)
.Returns([]);
sutProvider.GetDependency<IOrganizationIntegrationRepository>()
.CreateAsync(Arg.Any<OrganizationIntegration>())
.Returns(integration);
sutProvider.GetDependency<ITeamsService>().GetRedirectUrl(Arg.Any<string>(), Arg.Any<string>()).Returns(string.Empty);
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.RedirectAsync(organizationId));
}
[Theory, BitAutoData]
public async Task RedirectAsync_UserIsNotOrganizationAdmin_ThrowsNotFound(SutProvider<TeamsIntegrationController> sutProvider,
Guid organizationId)
{
sutProvider.GetDependency<ICurrentContext>()
.OrganizationOwner(organizationId)
.Returns(false);
await Assert.ThrowsAsync<NotFoundException>(async () => await sutProvider.Sut.RedirectAsync(organizationId));
}
[Theory, BitAutoData]
public async Task IncomingPostAsync_ForwardsToBot(SutProvider<TeamsIntegrationController> sutProvider)
{
var adapter = sutProvider.GetDependency<IBotFrameworkHttpAdapter>();
var bot = sutProvider.GetDependency<IBot>();
await sutProvider.Sut.IncomingPostAsync();
await adapter.Received(1).ProcessAsync(Arg.Any<HttpRequest>(), Arg.Any<HttpResponse>(), bot);
}
}

View File

@@ -1,262 +0,0 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json;
using Bit.Api.AdminConsole.Models.Request.Organizations;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
using Bit.Core.Enums;
using Bit.Test.Common.AutoFixture.Attributes;
using Xunit;
namespace Bit.Api.Test.AdminConsole.Models.Request.Organizations;
public class OrganizationIntegrationRequestModelTests
{
[Fact]
public void ToOrganizationIntegration_CreatesNewOrganizationIntegration()
{
var model = new OrganizationIntegrationRequestModel
{
Type = IntegrationType.Hec,
Configuration = JsonSerializer.Serialize(new HecIntegration(Uri: new Uri("http://localhost"), Scheme: "Bearer", Token: "Token"))
};
var organizationId = Guid.NewGuid();
var organizationIntegration = model.ToOrganizationIntegration(organizationId);
Assert.Equal(organizationIntegration.Type, model.Type);
Assert.Equal(organizationIntegration.Configuration, model.Configuration);
Assert.Equal(organizationIntegration.OrganizationId, organizationId);
}
[Theory, BitAutoData]
public void ToOrganizationIntegration_UpdatesExistingOrganizationIntegration(OrganizationIntegration integration)
{
var model = new OrganizationIntegrationRequestModel
{
Type = IntegrationType.Hec,
Configuration = JsonSerializer.Serialize(new HecIntegration(Uri: new Uri("http://localhost"), Scheme: "Bearer", Token: "Token"))
};
var organizationIntegration = model.ToOrganizationIntegration(integration);
Assert.Equal(organizationIntegration.Configuration, model.Configuration);
}
[Fact]
public void Validate_CloudBillingSync_ReturnsNotYetSupportedError()
{
var model = new OrganizationIntegrationRequestModel
{
Type = IntegrationType.CloudBillingSync,
Configuration = null
};
var results = model.Validate(new ValidationContext(model)).ToList();
Assert.Single(results);
Assert.Contains(nameof(model.Type), results[0].MemberNames);
Assert.Contains("not yet supported", results[0].ErrorMessage);
}
[Fact]
public void Validate_Scim_ReturnsNotYetSupportedError()
{
var model = new OrganizationIntegrationRequestModel
{
Type = IntegrationType.Scim,
Configuration = null
};
var results = model.Validate(new ValidationContext(model)).ToList();
Assert.Single(results);
Assert.Contains(nameof(model.Type), results[0].MemberNames);
Assert.Contains("not yet supported", results[0].ErrorMessage);
}
[Fact]
public void Validate_Slack_ReturnsCannotBeCreatedDirectlyError()
{
var model = new OrganizationIntegrationRequestModel
{
Type = IntegrationType.Slack,
Configuration = null
};
var results = model.Validate(new ValidationContext(model)).ToList();
Assert.Single(results);
Assert.Contains(nameof(model.Type), results[0].MemberNames);
Assert.Contains("cannot be created directly", results[0].ErrorMessage);
}
[Fact]
public void Validate_Teams_ReturnsCannotBeCreatedDirectlyError()
{
var model = new OrganizationIntegrationRequestModel
{
Type = IntegrationType.Teams,
Configuration = null
};
var results = model.Validate(new ValidationContext(model)).ToList();
Assert.Single(results);
Assert.Contains(nameof(model.Type), results[0].MemberNames);
Assert.Contains("cannot be created directly", results[0].ErrorMessage);
}
[Fact]
public void Validate_Webhook_WithNullConfiguration_ReturnsNoErrors()
{
var model = new OrganizationIntegrationRequestModel
{
Type = IntegrationType.Webhook,
Configuration = null
};
var results = model.Validate(new ValidationContext(model)).ToList();
Assert.Empty(results);
}
[Fact]
public void Validate_Webhook_WithInvalidConfiguration_ReturnsConfigurationError()
{
var model = new OrganizationIntegrationRequestModel
{
Type = IntegrationType.Webhook,
Configuration = "something"
};
var results = model.Validate(new ValidationContext(model)).ToList();
Assert.Single(results);
Assert.Contains(nameof(model.Configuration), results[0].MemberNames);
Assert.Contains("Must include valid", results[0].ErrorMessage);
}
[Fact]
public void Validate_Webhook_WithValidConfiguration_ReturnsNoErrors()
{
var model = new OrganizationIntegrationRequestModel
{
Type = IntegrationType.Webhook,
Configuration = JsonSerializer.Serialize(new WebhookIntegration(new Uri("https://example.com")))
};
var results = model.Validate(new ValidationContext(model)).ToList();
Assert.Empty(results);
}
[Fact]
public void Validate_Hec_WithNullConfiguration_ReturnsError()
{
var model = new OrganizationIntegrationRequestModel
{
Type = IntegrationType.Hec,
Configuration = null
};
var results = model.Validate(new ValidationContext(model)).ToList();
Assert.Single(results);
Assert.Contains(nameof(model.Configuration), results[0].MemberNames);
Assert.Contains("Must include valid", results[0].ErrorMessage);
}
[Fact]
public void Validate_Hec_WithInvalidConfiguration_ReturnsError()
{
var model = new OrganizationIntegrationRequestModel
{
Type = IntegrationType.Hec,
Configuration = "Not valid"
};
var results = model.Validate(new ValidationContext(model)).ToList();
Assert.Single(results);
Assert.Contains(nameof(model.Configuration), results[0].MemberNames);
Assert.Contains("Must include valid", results[0].ErrorMessage);
}
[Fact]
public void Validate_Hec_WithValidConfiguration_ReturnsNoErrors()
{
var model = new OrganizationIntegrationRequestModel
{
Type = IntegrationType.Hec,
Configuration = JsonSerializer.Serialize(new HecIntegration(Uri: new Uri("http://localhost"), Scheme: "Bearer", Token: "Token"))
};
var results = model.Validate(new ValidationContext(model)).ToList();
Assert.Empty(results);
}
[Fact]
public void Validate_Datadog_WithNullConfiguration_ReturnsError()
{
var model = new OrganizationIntegrationRequestModel
{
Type = IntegrationType.Datadog,
Configuration = null
};
var results = model.Validate(new ValidationContext(model)).ToList();
Assert.Single(results);
Assert.Contains(nameof(model.Configuration), results[0].MemberNames);
Assert.Contains("Must include valid", results[0].ErrorMessage);
}
[Fact]
public void Validate_Datadog_WithInvalidConfiguration_ReturnsError()
{
var model = new OrganizationIntegrationRequestModel
{
Type = IntegrationType.Datadog,
Configuration = "Not valid"
};
var results = model.Validate(new ValidationContext(model)).ToList();
Assert.Single(results);
Assert.Contains(nameof(model.Configuration), results[0].MemberNames);
Assert.Contains("Must include valid", results[0].ErrorMessage);
}
[Fact]
public void Validate_Datadog_WithValidConfiguration_ReturnsNoErrors()
{
var model = new OrganizationIntegrationRequestModel
{
Type = IntegrationType.Datadog,
Configuration = JsonSerializer.Serialize(
new DatadogIntegration(ApiKey: "API1234", Uri: new Uri("http://localhost"))
)
};
var results = model.Validate(new ValidationContext(model)).ToList();
Assert.Empty(results);
}
[Fact]
public void Validate_UnknownIntegrationType_ReturnsUnrecognizedError()
{
var model = new OrganizationIntegrationRequestModel
{
Type = (IntegrationType)999,
Configuration = null
};
var results = model.Validate(new ValidationContext(model)).ToList();
Assert.Single(results);
Assert.Contains(nameof(model.Type), results[0].MemberNames);
Assert.Contains("not recognized", results[0].ErrorMessage);
}
}

View File

@@ -1,160 +0,0 @@
#nullable enable
using System.Text.Json;
using Bit.Api.AdminConsole.Models.Response.Organizations;
using Bit.Core.AdminConsole.Entities;
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
using Bit.Core.Enums;
using Bit.Core.Models.Teams;
using Bit.Test.Common.AutoFixture.Attributes;
using Xunit;
namespace Bit.Api.Test.AdminConsole.Models.Response.Organizations;
public class OrganizationIntegrationResponseModelTests
{
[Theory, BitAutoData]
public void Status_CloudBillingSync_AlwaysNotApplicable(OrganizationIntegration oi)
{
oi.Type = IntegrationType.CloudBillingSync;
oi.Configuration = null;
var model = new OrganizationIntegrationResponseModel(oi);
Assert.Equal(OrganizationIntegrationStatus.NotApplicable, model.Status);
model.Configuration = "{}";
Assert.Equal(OrganizationIntegrationStatus.NotApplicable, model.Status);
}
[Theory, BitAutoData]
public void Status_Scim_AlwaysNotApplicable(OrganizationIntegration oi)
{
oi.Type = IntegrationType.Scim;
oi.Configuration = null;
var model = new OrganizationIntegrationResponseModel(oi);
Assert.Equal(OrganizationIntegrationStatus.NotApplicable, model.Status);
model.Configuration = "{}";
Assert.Equal(OrganizationIntegrationStatus.NotApplicable, model.Status);
}
[Theory, BitAutoData]
public void Status_Slack_NullConfig_ReturnsInitiated(OrganizationIntegration oi)
{
oi.Type = IntegrationType.Slack;
oi.Configuration = null;
var model = new OrganizationIntegrationResponseModel(oi);
Assert.Equal(OrganizationIntegrationStatus.Initiated, model.Status);
}
[Theory, BitAutoData]
public void Status_Slack_WithConfig_ReturnsCompleted(OrganizationIntegration oi)
{
oi.Type = IntegrationType.Slack;
oi.Configuration = "{}";
var model = new OrganizationIntegrationResponseModel(oi);
Assert.Equal(OrganizationIntegrationStatus.Completed, model.Status);
}
[Theory, BitAutoData]
public void Status_Teams_NullConfig_ReturnsInitiated(OrganizationIntegration oi)
{
oi.Type = IntegrationType.Teams;
oi.Configuration = null;
var model = new OrganizationIntegrationResponseModel(oi);
Assert.Equal(OrganizationIntegrationStatus.Initiated, model.Status);
}
[Theory, BitAutoData]
public void Status_Teams_WithTenantAndTeamsConfig_ReturnsInProgress(OrganizationIntegration oi)
{
oi.Type = IntegrationType.Teams;
oi.Configuration = JsonSerializer.Serialize(new TeamsIntegration(
TenantId: "tenant", Teams: [new TeamInfo() { DisplayName = "Team", Id = "TeamId", TenantId = "tenant" }]
));
var model = new OrganizationIntegrationResponseModel(oi);
Assert.Equal(OrganizationIntegrationStatus.InProgress, model.Status);
}
[Theory, BitAutoData]
public void Status_Teams_WithCompletedConfig_ReturnsCompleted(OrganizationIntegration oi)
{
oi.Type = IntegrationType.Teams;
oi.Configuration = JsonSerializer.Serialize(new TeamsIntegration(
TenantId: "tenant",
Teams: [new TeamInfo() { DisplayName = "Team", Id = "TeamId", TenantId = "tenant" }],
ServiceUrl: new Uri("https://example.com"),
ChannelId: "channellId"
));
var model = new OrganizationIntegrationResponseModel(oi);
Assert.Equal(OrganizationIntegrationStatus.Completed, model.Status);
}
[Theory, BitAutoData]
public void Status_Webhook_AlwaysCompleted(OrganizationIntegration oi)
{
oi.Type = IntegrationType.Webhook;
oi.Configuration = null;
var model = new OrganizationIntegrationResponseModel(oi);
Assert.Equal(OrganizationIntegrationStatus.Completed, model.Status);
model.Configuration = "{}";
Assert.Equal(OrganizationIntegrationStatus.Completed, model.Status);
}
[Theory, BitAutoData]
public void Status_Hec_NullConfig_ReturnsInvalid(OrganizationIntegration oi)
{
oi.Type = IntegrationType.Hec;
oi.Configuration = null;
var model = new OrganizationIntegrationResponseModel(oi);
Assert.Equal(OrganizationIntegrationStatus.Invalid, model.Status);
}
[Theory, BitAutoData]
public void Status_Hec_WithConfig_ReturnsCompleted(OrganizationIntegration oi)
{
oi.Type = IntegrationType.Hec;
oi.Configuration = "{}";
var model = new OrganizationIntegrationResponseModel(oi);
Assert.Equal(OrganizationIntegrationStatus.Completed, model.Status);
}
[Theory, BitAutoData]
public void Status_Datadog_NullConfig_ReturnsInvalid(OrganizationIntegration oi)
{
oi.Type = IntegrationType.Datadog;
oi.Configuration = null;
var model = new OrganizationIntegrationResponseModel(oi);
Assert.Equal(OrganizationIntegrationStatus.Invalid, model.Status);
}
[Theory, BitAutoData]
public void Status_Datadog_WithConfig_ReturnsCompleted(OrganizationIntegration oi)
{
oi.Type = IntegrationType.Datadog;
oi.Configuration = "{}";
var model = new OrganizationIntegrationResponseModel(oi);
Assert.Equal(OrganizationIntegrationStatus.Completed, model.Status);
}
}