mirror of
https://github.com/bitwarden/server.git
synced 2025-06-30 07:36:14 -05:00
Merge branch 'main' into auth/pm-20348/extension-auth-approvals-add-auth-request-endpoint
This commit is contained in:
@ -28,6 +28,8 @@ public class ApiApplicationFactory : WebApplicationFactoryBase<Startup>
|
||||
_identityApplicationFactory.ManagesDatabase = false;
|
||||
}
|
||||
|
||||
public IdentityApplicationFactory Identity => _identityApplicationFactory;
|
||||
|
||||
protected override void ConfigureWebHost(IWebHostBuilder builder)
|
||||
{
|
||||
base.ConfigureWebHost(builder);
|
||||
|
@ -0,0 +1,449 @@
|
||||
using System.Net;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using Azure.Storage.Queues;
|
||||
using Bit.Api.IntegrationTest.Factories;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models;
|
||||
using Bit.Core.Models.Api;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.NotificationHub;
|
||||
using Bit.Core.Platform.Installations;
|
||||
using Bit.Core.Repositories;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
using static Bit.Core.Settings.GlobalSettings;
|
||||
|
||||
namespace Bit.Api.IntegrationTest.Platform.Controllers;
|
||||
|
||||
public class PushControllerTests
|
||||
{
|
||||
private static readonly Guid _userId = Guid.NewGuid();
|
||||
private static readonly Guid _organizationId = Guid.NewGuid();
|
||||
private static readonly Guid _deviceId = Guid.NewGuid();
|
||||
|
||||
public static IEnumerable<object[]> SendData()
|
||||
{
|
||||
static object[] Typed<T>(PushSendRequestModel<T> pushSendRequestModel, string expectedHubTagExpression, bool expectHubCall = true)
|
||||
{
|
||||
return [pushSendRequestModel, expectedHubTagExpression, expectHubCall];
|
||||
}
|
||||
|
||||
static object[] UserTyped(PushType pushType)
|
||||
{
|
||||
return Typed(new PushSendRequestModel<UserPushNotification>
|
||||
{
|
||||
Type = pushType,
|
||||
UserId = _userId,
|
||||
DeviceId = _deviceId,
|
||||
Payload = new UserPushNotification
|
||||
{
|
||||
Date = DateTime.UtcNow,
|
||||
UserId = _userId,
|
||||
},
|
||||
}, $"(template:payload_userId:%installation%_{_userId})");
|
||||
}
|
||||
|
||||
// User cipher
|
||||
yield return Typed(new PushSendRequestModel<SyncCipherPushNotification>
|
||||
{
|
||||
Type = PushType.SyncCipherUpdate,
|
||||
UserId = _userId,
|
||||
DeviceId = _deviceId,
|
||||
Payload = new SyncCipherPushNotification
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
UserId = _userId,
|
||||
},
|
||||
}, $"(template:payload_userId:%installation%_{_userId})");
|
||||
|
||||
// Organization cipher, an org cipher would not naturally be synced from our
|
||||
// code but it is technically possible to be submitted to the endpoint.
|
||||
yield return Typed(new PushSendRequestModel<SyncCipherPushNotification>
|
||||
{
|
||||
Type = PushType.SyncCipherUpdate,
|
||||
OrganizationId = _organizationId,
|
||||
DeviceId = _deviceId,
|
||||
Payload = new SyncCipherPushNotification
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
OrganizationId = _organizationId,
|
||||
},
|
||||
}, $"(template:payload && organizationId:%installation%_{_organizationId})");
|
||||
|
||||
yield return Typed(new PushSendRequestModel<SyncCipherPushNotification>
|
||||
{
|
||||
Type = PushType.SyncCipherCreate,
|
||||
UserId = _userId,
|
||||
DeviceId = _deviceId,
|
||||
Payload = new SyncCipherPushNotification
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
UserId = _userId,
|
||||
},
|
||||
}, $"(template:payload_userId:%installation%_{_userId})");
|
||||
|
||||
// Organization cipher, an org cipher would not naturally be synced from our
|
||||
// code but it is technically possible to be submitted to the endpoint.
|
||||
yield return Typed(new PushSendRequestModel<SyncCipherPushNotification>
|
||||
{
|
||||
Type = PushType.SyncCipherCreate,
|
||||
OrganizationId = _organizationId,
|
||||
DeviceId = _deviceId,
|
||||
Payload = new SyncCipherPushNotification
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
OrganizationId = _organizationId,
|
||||
},
|
||||
}, $"(template:payload && organizationId:%installation%_{_organizationId})");
|
||||
|
||||
yield return Typed(new PushSendRequestModel<SyncCipherPushNotification>
|
||||
{
|
||||
Type = PushType.SyncCipherDelete,
|
||||
UserId = _userId,
|
||||
DeviceId = _deviceId,
|
||||
Payload = new SyncCipherPushNotification
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
UserId = _userId,
|
||||
},
|
||||
}, $"(template:payload_userId:%installation%_{_userId})");
|
||||
|
||||
// Organization cipher, an org cipher would not naturally be synced from our
|
||||
// code but it is technically possible to be submitted to the endpoint.
|
||||
yield return Typed(new PushSendRequestModel<SyncCipherPushNotification>
|
||||
{
|
||||
Type = PushType.SyncCipherDelete,
|
||||
OrganizationId = _organizationId,
|
||||
DeviceId = _deviceId,
|
||||
Payload = new SyncCipherPushNotification
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
OrganizationId = _organizationId,
|
||||
},
|
||||
}, $"(template:payload && organizationId:%installation%_{_organizationId})");
|
||||
|
||||
yield return Typed(new PushSendRequestModel<SyncFolderPushNotification>
|
||||
{
|
||||
Type = PushType.SyncFolderDelete,
|
||||
UserId = _userId,
|
||||
DeviceId = _deviceId,
|
||||
Payload = new SyncFolderPushNotification
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
UserId = _userId,
|
||||
},
|
||||
}, $"(template:payload_userId:%installation%_{_userId})");
|
||||
|
||||
yield return Typed(new PushSendRequestModel<SyncFolderPushNotification>
|
||||
{
|
||||
Type = PushType.SyncFolderCreate,
|
||||
UserId = _userId,
|
||||
DeviceId = _deviceId,
|
||||
Payload = new SyncFolderPushNotification
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
UserId = _userId,
|
||||
},
|
||||
}, $"(template:payload_userId:%installation%_{_userId})");
|
||||
|
||||
yield return Typed(new PushSendRequestModel<SyncFolderPushNotification>
|
||||
{
|
||||
Type = PushType.SyncFolderCreate,
|
||||
UserId = _userId,
|
||||
DeviceId = _deviceId,
|
||||
Payload = new SyncFolderPushNotification
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
UserId = _userId,
|
||||
},
|
||||
}, $"(template:payload_userId:%installation%_{_userId})");
|
||||
|
||||
yield return UserTyped(PushType.SyncCiphers);
|
||||
yield return UserTyped(PushType.SyncVault);
|
||||
yield return UserTyped(PushType.SyncOrganizations);
|
||||
yield return UserTyped(PushType.SyncOrgKeys);
|
||||
yield return UserTyped(PushType.SyncSettings);
|
||||
yield return UserTyped(PushType.LogOut);
|
||||
yield return UserTyped(PushType.PendingSecurityTasks);
|
||||
|
||||
yield return Typed(new PushSendRequestModel<AuthRequestPushNotification>
|
||||
{
|
||||
Type = PushType.AuthRequest,
|
||||
UserId = _userId,
|
||||
DeviceId = _deviceId,
|
||||
Payload = new AuthRequestPushNotification
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
UserId = _userId,
|
||||
},
|
||||
}, $"(template:payload_userId:%installation%_{_userId})");
|
||||
|
||||
yield return Typed(new PushSendRequestModel<AuthRequestPushNotification>
|
||||
{
|
||||
Type = PushType.AuthRequestResponse,
|
||||
UserId = _userId,
|
||||
DeviceId = _deviceId,
|
||||
Payload = new AuthRequestPushNotification
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
UserId = _userId,
|
||||
},
|
||||
}, $"(template:payload_userId:%installation%_{_userId})");
|
||||
|
||||
yield return Typed(new PushSendRequestModel<NotificationPushNotification>
|
||||
{
|
||||
Type = PushType.Notification,
|
||||
UserId = _userId,
|
||||
DeviceId = _deviceId,
|
||||
Payload = new NotificationPushNotification
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
UserId = _userId,
|
||||
},
|
||||
}, $"(template:payload_userId:%installation%_{_userId})");
|
||||
|
||||
yield return Typed(new PushSendRequestModel<NotificationPushNotification>
|
||||
{
|
||||
Type = PushType.Notification,
|
||||
UserId = _userId,
|
||||
DeviceId = _deviceId,
|
||||
ClientType = ClientType.All,
|
||||
Payload = new NotificationPushNotification
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Global = true,
|
||||
},
|
||||
}, $"(template:payload_userId:%installation%_{_userId})");
|
||||
|
||||
yield return Typed(new PushSendRequestModel<NotificationPushNotification>
|
||||
{
|
||||
Type = PushType.NotificationStatus,
|
||||
OrganizationId = _organizationId,
|
||||
DeviceId = _deviceId,
|
||||
Payload = new NotificationPushNotification
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
UserId = _userId,
|
||||
},
|
||||
}, $"(template:payload && organizationId:%installation%_{_organizationId})");
|
||||
|
||||
yield return Typed(new PushSendRequestModel<NotificationPushNotification>
|
||||
{
|
||||
Type = PushType.NotificationStatus,
|
||||
OrganizationId = _organizationId,
|
||||
DeviceId = _deviceId,
|
||||
Payload = new NotificationPushNotification
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
UserId = _userId,
|
||||
},
|
||||
}, $"(template:payload && organizationId:%installation%_{_organizationId})");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(SendData))]
|
||||
public async Task Send_Works<T>(PushSendRequestModel<T> pushSendRequestModel, string expectedHubTagExpression, bool expectHubCall)
|
||||
{
|
||||
var (apiFactory, httpClient, installation, queueClient, notificationHubProxy) = await SetupTest();
|
||||
|
||||
// Act
|
||||
var pushSendResponse = await httpClient.PostAsJsonAsync("push/send", pushSendRequestModel);
|
||||
|
||||
// Assert
|
||||
pushSendResponse.EnsureSuccessStatusCode();
|
||||
|
||||
// Relayed notifications, the ones coming to this endpoint should
|
||||
// not make their way into our Azure Queue and instead should only be sent to Azure Notifications
|
||||
// hub.
|
||||
await queueClient
|
||||
.Received(0)
|
||||
.SendMessageAsync(Arg.Any<string>());
|
||||
|
||||
// Check that this notification was sent through hubs the expected number of times
|
||||
await notificationHubProxy
|
||||
.Received(expectHubCall ? 1 : 0)
|
||||
.SendTemplateNotificationAsync(
|
||||
Arg.Any<Dictionary<string, string>>(),
|
||||
Arg.Is(expectedHubTagExpression.Replace("%installation%", installation.Id.ToString()))
|
||||
);
|
||||
|
||||
// TODO: Expect on the dictionary more?
|
||||
|
||||
// Notifications being relayed from SH should have the device id
|
||||
// tracked so that we can later send the notification to that device.
|
||||
await apiFactory.GetService<IInstallationDeviceRepository>()
|
||||
.Received(1)
|
||||
.UpsertAsync(Arg.Is<InstallationDeviceEntity>(
|
||||
ide => ide.PartitionKey == installation.Id.ToString() && ide.RowKey == pushSendRequestModel.DeviceId.ToString()
|
||||
));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Send_InstallationNotification_NotAuthenticatedInstallation_Fails()
|
||||
{
|
||||
var (_, httpClient, _, _, _) = await SetupTest();
|
||||
|
||||
var response = await httpClient.PostAsJsonAsync("push/send", new PushSendRequestModel<object>
|
||||
{
|
||||
Type = PushType.NotificationStatus,
|
||||
InstallationId = Guid.NewGuid(),
|
||||
Payload = new { }
|
||||
});
|
||||
|
||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
var body = await response.Content.ReadFromJsonAsync<JsonNode>();
|
||||
Assert.Equal(JsonValueKind.Object, body.GetValueKind());
|
||||
Assert.True(body.AsObject().TryGetPropertyValue("message", out var message));
|
||||
Assert.Equal(JsonValueKind.String, message.GetValueKind());
|
||||
Assert.Equal("InstallationId does not match current context.", message.GetValue<string>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Send_InstallationNotification_Works()
|
||||
{
|
||||
var (apiFactory, httpClient, installation, _, notificationHubProxy) = await SetupTest();
|
||||
|
||||
var deviceId = Guid.NewGuid();
|
||||
|
||||
var response = await httpClient.PostAsJsonAsync("push/send", new PushSendRequestModel<object>
|
||||
{
|
||||
Type = PushType.NotificationStatus,
|
||||
InstallationId = installation.Id,
|
||||
Payload = new { },
|
||||
DeviceId = deviceId,
|
||||
ClientType = ClientType.Web,
|
||||
});
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
await notificationHubProxy
|
||||
.Received(1)
|
||||
.SendTemplateNotificationAsync(
|
||||
Arg.Any<Dictionary<string, string>>(),
|
||||
Arg.Is($"(template:payload && installationId:{installation.Id} && clientType:Web)")
|
||||
);
|
||||
|
||||
await apiFactory.GetService<IInstallationDeviceRepository>()
|
||||
.Received(1)
|
||||
.UpsertAsync(Arg.Is<InstallationDeviceEntity>(
|
||||
ide => ide.PartitionKey == installation.Id.ToString() && ide.RowKey == deviceId.ToString()
|
||||
));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Send_NoOrganizationNoInstallationNoUser_FailsModelValidation()
|
||||
{
|
||||
var (_, client, _, _, _) = await SetupTest();
|
||||
|
||||
var response = await client.PostAsJsonAsync("push/send", new PushSendRequestModel<object>
|
||||
{
|
||||
Type = PushType.AuthRequest,
|
||||
Payload = new { },
|
||||
});
|
||||
|
||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
var body = await response.Content.ReadFromJsonAsync<JsonNode>();
|
||||
Assert.Equal(JsonValueKind.Object, body.GetValueKind());
|
||||
Assert.True(body.AsObject().TryGetPropertyValue("message", out var message));
|
||||
Assert.Equal(JsonValueKind.String, message.GetValueKind());
|
||||
Assert.Equal("The model state is invalid.", message.GetValue<string>());
|
||||
}
|
||||
|
||||
private static async Task<(ApiApplicationFactory Factory, HttpClient AuthedClient, Installation Installation, QueueClient MockedQueue, INotificationHubProxy MockedHub)> SetupTest()
|
||||
{
|
||||
// Arrange
|
||||
var apiFactory = new ApiApplicationFactory();
|
||||
|
||||
var queueClient = Substitute.For<QueueClient>();
|
||||
|
||||
// Substitute the underlying queue messages will go to.
|
||||
apiFactory.ConfigureServices(services =>
|
||||
{
|
||||
var queueClientService = services.FirstOrDefault(
|
||||
sd => sd.ServiceKey == (object)"notifications"
|
||||
&& sd.ServiceType == typeof(QueueClient)
|
||||
) ?? throw new InvalidOperationException("Expected service was not found.");
|
||||
|
||||
services.Remove(queueClientService);
|
||||
|
||||
services.AddKeyedSingleton("notifications", queueClient);
|
||||
});
|
||||
|
||||
var notificationHubProxy = Substitute.For<INotificationHubProxy>();
|
||||
|
||||
apiFactory.SubstituteService<INotificationHubPool>(s =>
|
||||
{
|
||||
s.AllClients
|
||||
.Returns(notificationHubProxy);
|
||||
});
|
||||
|
||||
apiFactory.SubstituteService<IInstallationDeviceRepository>(s => { });
|
||||
|
||||
// Setup as cloud with NotificationHub setup and Azure Queue
|
||||
apiFactory.UpdateConfiguration("GlobalSettings:Notifications:ConnectionString", "any_value");
|
||||
|
||||
// Configure hubs
|
||||
var index = 0;
|
||||
void AddHub(NotificationHubSettings notificationHubSettings)
|
||||
{
|
||||
apiFactory.UpdateConfiguration(
|
||||
$"GlobalSettings:NotificationHubPool:NotificationHubs:{index}:ConnectionString",
|
||||
notificationHubSettings.ConnectionString
|
||||
);
|
||||
apiFactory.UpdateConfiguration(
|
||||
$"GlobalSettings:NotificationHubPool:NotificationHubs:{index}:HubName",
|
||||
notificationHubSettings.HubName
|
||||
);
|
||||
apiFactory.UpdateConfiguration(
|
||||
$"GlobalSettings:NotificationHubPool:NotificationHubs:{index}:RegistrationStartDate",
|
||||
notificationHubSettings.RegistrationStartDate?.ToString()
|
||||
);
|
||||
apiFactory.UpdateConfiguration(
|
||||
$"GlobalSettings:NotificationHubPool:NotificationHubs:{index}:RegistrationEndDate",
|
||||
notificationHubSettings.RegistrationEndDate?.ToString()
|
||||
);
|
||||
index++;
|
||||
}
|
||||
|
||||
AddHub(new NotificationHubSettings
|
||||
{
|
||||
ConnectionString = "some_value",
|
||||
RegistrationStartDate = DateTime.UtcNow.AddDays(-2),
|
||||
});
|
||||
|
||||
var httpClient = apiFactory.CreateClient();
|
||||
|
||||
// Add installation into database
|
||||
var installationRepository = apiFactory.GetService<IInstallationRepository>();
|
||||
var installation = await installationRepository.CreateAsync(new Installation
|
||||
{
|
||||
Key = "my_test_key",
|
||||
Email = "test@example.com",
|
||||
Enabled = true,
|
||||
});
|
||||
|
||||
var identityClient = apiFactory.Identity.CreateDefaultClient();
|
||||
|
||||
var connectTokenResponse = await identityClient.PostAsync("connect/token", new FormUrlEncodedContent(new Dictionary<string, string>
|
||||
{
|
||||
{ "grant_type", "client_credentials" },
|
||||
{ "scope", "api.push" },
|
||||
{ "client_id", $"installation.{installation.Id}" },
|
||||
{ "client_secret", installation.Key },
|
||||
}));
|
||||
|
||||
connectTokenResponse.EnsureSuccessStatusCode();
|
||||
|
||||
var connectTokenResponseModel = await connectTokenResponse.Content.ReadFromJsonAsync<JsonNode>();
|
||||
|
||||
// Setup authentication
|
||||
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
|
||||
connectTokenResponseModel["token_type"].GetValue<string>(),
|
||||
connectTokenResponseModel["access_token"].GetValue<string>()
|
||||
);
|
||||
|
||||
return (apiFactory, httpClient, installation, queueClient, notificationHubProxy);
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@ using Bit.Api.AdminConsole.Controllers;
|
||||
using Bit.Api.AdminConsole.Models.Request.Organizations;
|
||||
using Bit.Api.AdminConsole.Models.Response.Organizations;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Models.Data.Integrations;
|
||||
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
|
@ -30,6 +30,7 @@ public class OrganizationUserControllerPutTests
|
||||
OrganizationUser organizationUser, OrganizationAbility organizationAbility,
|
||||
SutProvider<OrganizationUsersController> sutProvider, Guid savingUserId)
|
||||
{
|
||||
// Arrange
|
||||
Put_Setup(sutProvider, organizationAbility, organizationUser, savingUserId, currentCollectionAccess: []);
|
||||
|
||||
// Authorize all changes for basic happy path test
|
||||
@ -41,15 +42,18 @@ public class OrganizationUserControllerPutTests
|
||||
// Save these for later - organizationUser object will be mutated
|
||||
var orgUserId = organizationUser.Id;
|
||||
var orgUserEmail = organizationUser.Email;
|
||||
var existingUserType = organizationUser.Type;
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.Put(organizationAbility.Id, organizationUser.Id, model);
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<IUpdateOrganizationUserCommand>().Received(1).UpdateUserAsync(Arg.Is<OrganizationUser>(ou =>
|
||||
ou.Type == model.Type &&
|
||||
ou.Permissions == CoreHelpers.ClassToJsonData(model.Permissions) &&
|
||||
ou.AccessSecretsManager == model.AccessSecretsManager &&
|
||||
ou.Id == orgUserId &&
|
||||
ou.Email == orgUserEmail),
|
||||
ou.Email == orgUserEmail), existingUserType,
|
||||
savingUserId,
|
||||
Arg.Is<List<CollectionAccessSelection>>(cas =>
|
||||
cas.All(c => model.Collections.Any(m => m.Id == c.Id))),
|
||||
@ -77,6 +81,7 @@ public class OrganizationUserControllerPutTests
|
||||
OrganizationUser organizationUser, OrganizationAbility organizationAbility,
|
||||
SutProvider<OrganizationUsersController> sutProvider, Guid savingUserId)
|
||||
{
|
||||
// Arrange
|
||||
// Updating self
|
||||
organizationUser.UserId = savingUserId;
|
||||
organizationAbility.AllowAdminAccessToAllCollectionItems = false;
|
||||
@ -88,15 +93,18 @@ public class OrganizationUserControllerPutTests
|
||||
|
||||
var orgUserId = organizationUser.Id;
|
||||
var orgUserEmail = organizationUser.Email;
|
||||
var existingUserType = organizationUser.Type;
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.Put(organizationAbility.Id, organizationUser.Id, model);
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<IUpdateOrganizationUserCommand>().Received(1).UpdateUserAsync(Arg.Is<OrganizationUser>(ou =>
|
||||
ou.Type == model.Type &&
|
||||
ou.Permissions == CoreHelpers.ClassToJsonData(model.Permissions) &&
|
||||
ou.AccessSecretsManager == model.AccessSecretsManager &&
|
||||
ou.Id == orgUserId &&
|
||||
ou.Email == orgUserEmail),
|
||||
ou.Type == model.Type &&
|
||||
ou.Permissions == CoreHelpers.ClassToJsonData(model.Permissions) &&
|
||||
ou.AccessSecretsManager == model.AccessSecretsManager &&
|
||||
ou.Id == orgUserId &&
|
||||
ou.Email == orgUserEmail), existingUserType,
|
||||
savingUserId,
|
||||
Arg.Is<List<CollectionAccessSelection>>(cas =>
|
||||
cas.All(c => model.Collections.Any(m => m.Id == c.Id))),
|
||||
@ -110,6 +118,7 @@ public class OrganizationUserControllerPutTests
|
||||
OrganizationUser organizationUser, OrganizationAbility organizationAbility,
|
||||
SutProvider<OrganizationUsersController> sutProvider, Guid savingUserId)
|
||||
{
|
||||
// Arrange
|
||||
// Updating self
|
||||
organizationUser.UserId = savingUserId;
|
||||
organizationAbility.AllowAdminAccessToAllCollectionItems = true;
|
||||
@ -121,15 +130,18 @@ public class OrganizationUserControllerPutTests
|
||||
|
||||
var orgUserId = organizationUser.Id;
|
||||
var orgUserEmail = organizationUser.Email;
|
||||
var existingUserType = organizationUser.Type;
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.Put(organizationAbility.Id, organizationUser.Id, model);
|
||||
|
||||
// Assert
|
||||
await sutProvider.GetDependency<IUpdateOrganizationUserCommand>().Received(1).UpdateUserAsync(Arg.Is<OrganizationUser>(ou =>
|
||||
ou.Type == model.Type &&
|
||||
ou.Permissions == CoreHelpers.ClassToJsonData(model.Permissions) &&
|
||||
ou.AccessSecretsManager == model.AccessSecretsManager &&
|
||||
ou.Id == orgUserId &&
|
||||
ou.Email == orgUserEmail),
|
||||
ou.Type == model.Type &&
|
||||
ou.Permissions == CoreHelpers.ClassToJsonData(model.Permissions) &&
|
||||
ou.AccessSecretsManager == model.AccessSecretsManager &&
|
||||
ou.Id == orgUserId &&
|
||||
ou.Email == orgUserEmail), existingUserType,
|
||||
savingUserId,
|
||||
Arg.Is<List<CollectionAccessSelection>>(cas =>
|
||||
cas.All(c => model.Collections.Any(m => m.Id == c.Id))),
|
||||
@ -142,6 +154,7 @@ public class OrganizationUserControllerPutTests
|
||||
OrganizationUser organizationUser, OrganizationAbility organizationAbility,
|
||||
SutProvider<OrganizationUsersController> sutProvider, Guid savingUserId)
|
||||
{
|
||||
// Arrange
|
||||
var editedCollectionId = CoreHelpers.GenerateComb();
|
||||
var readonlyCollectionId1 = CoreHelpers.GenerateComb();
|
||||
var readonlyCollectionId2 = CoreHelpers.GenerateComb();
|
||||
@ -194,16 +207,19 @@ public class OrganizationUserControllerPutTests
|
||||
.AuthorizeAsync(Arg.Any<ClaimsPrincipal>(), Arg.Is<Collection>(c => c.Id == readonlyCollectionId1 || c.Id == readonlyCollectionId2),
|
||||
Arg.Is<IEnumerable<IAuthorizationRequirement>>(reqs => reqs.Contains(BulkCollectionOperations.ModifyUserAccess)))
|
||||
.Returns(AuthorizationResult.Failed());
|
||||
var existingUserType = organizationUser.Type;
|
||||
|
||||
// Act
|
||||
await sutProvider.Sut.Put(organizationAbility.Id, organizationUser.Id, model);
|
||||
|
||||
// Assert
|
||||
// Expect all collection access (modified and unmodified) to be saved
|
||||
await sutProvider.GetDependency<IUpdateOrganizationUserCommand>().Received(1).UpdateUserAsync(Arg.Is<OrganizationUser>(ou =>
|
||||
ou.Type == model.Type &&
|
||||
ou.Permissions == CoreHelpers.ClassToJsonData(model.Permissions) &&
|
||||
ou.AccessSecretsManager == model.AccessSecretsManager &&
|
||||
ou.Id == orgUserId &&
|
||||
ou.Email == orgUserEmail),
|
||||
ou.Type == model.Type &&
|
||||
ou.Permissions == CoreHelpers.ClassToJsonData(model.Permissions) &&
|
||||
ou.AccessSecretsManager == model.AccessSecretsManager &&
|
||||
ou.Id == orgUserId &&
|
||||
ou.Email == orgUserEmail), existingUserType,
|
||||
savingUserId,
|
||||
Arg.Is<List<CollectionAccessSelection>>(cas =>
|
||||
cas.Select(c => c.Id).SequenceEqual(currentCollectionAccess.Select(c => c.Id)) &&
|
||||
|
@ -1,6 +1,6 @@
|
||||
using System.Text.Json;
|
||||
using Bit.Api.AdminConsole.Models.Request.Organizations;
|
||||
using Bit.Core.AdminConsole.Models.Data.Integrations;
|
||||
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||
using Bit.Core.Enums;
|
||||
using Xunit;
|
||||
|
||||
|
@ -18,210 +18,6 @@ namespace Bit.Api.Test.Platform.Push.Controllers;
|
||||
[SutProviderCustomize]
|
||||
public class PushControllerTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData(false, true)]
|
||||
[BitAutoData(false, false)]
|
||||
[BitAutoData(true, true)]
|
||||
public async Task SendAsync_InstallationIdNotSetOrSelfHosted_BadRequest(bool haveInstallationId, bool selfHosted,
|
||||
SutProvider<PushController> sutProvider, Guid installationId, Guid userId, Guid organizationId)
|
||||
{
|
||||
sutProvider.GetDependency<IGlobalSettings>().SelfHosted = selfHosted;
|
||||
if (haveInstallationId)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().InstallationId.Returns(installationId);
|
||||
}
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.SendAsync(new PushSendRequestModel
|
||||
{
|
||||
Type = PushType.Notification,
|
||||
UserId = userId.ToString(),
|
||||
OrganizationId = organizationId.ToString(),
|
||||
InstallationId = installationId.ToString(),
|
||||
Payload = "test-payload"
|
||||
}));
|
||||
|
||||
Assert.Equal("Not correctly configured for push relays.", exception.Message);
|
||||
|
||||
await sutProvider.GetDependency<IPushNotificationService>().Received(0)
|
||||
.SendPayloadToUserAsync(Arg.Any<string>(), Arg.Any<PushType>(), Arg.Any<object>(), Arg.Any<string>(),
|
||||
Arg.Any<string?>(), Arg.Any<ClientType?>());
|
||||
await sutProvider.GetDependency<IPushNotificationService>().Received(0)
|
||||
.SendPayloadToOrganizationAsync(Arg.Any<string>(), Arg.Any<PushType>(), Arg.Any<object>(),
|
||||
Arg.Any<string>(), Arg.Any<string?>(), Arg.Any<ClientType?>());
|
||||
await sutProvider.GetDependency<IPushNotificationService>().Received(0)
|
||||
.SendPayloadToInstallationAsync(Arg.Any<string>(), Arg.Any<PushType>(), Arg.Any<object>(),
|
||||
Arg.Any<string>(), Arg.Any<string?>(), Arg.Any<ClientType?>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SendAsync_UserIdAndOrganizationIdAndInstallationIdEmpty_NoPushNotificationSent(
|
||||
SutProvider<PushController> sutProvider, Guid installationId)
|
||||
{
|
||||
sutProvider.GetDependency<IGlobalSettings>().SelfHosted = false;
|
||||
sutProvider.GetDependency<ICurrentContext>().InstallationId.Returns(installationId);
|
||||
|
||||
await sutProvider.Sut.SendAsync(new PushSendRequestModel
|
||||
{
|
||||
Type = PushType.Notification,
|
||||
UserId = null,
|
||||
OrganizationId = null,
|
||||
InstallationId = null,
|
||||
Payload = "test-payload"
|
||||
});
|
||||
|
||||
await sutProvider.GetDependency<IPushNotificationService>().Received(0)
|
||||
.SendPayloadToUserAsync(Arg.Any<string>(), Arg.Any<PushType>(), Arg.Any<object>(), Arg.Any<string>(),
|
||||
Arg.Any<string?>(), Arg.Any<ClientType?>());
|
||||
await sutProvider.GetDependency<IPushNotificationService>().Received(0)
|
||||
.SendPayloadToOrganizationAsync(Arg.Any<string>(), Arg.Any<PushType>(), Arg.Any<object>(),
|
||||
Arg.Any<string>(), Arg.Any<string?>(), Arg.Any<ClientType?>());
|
||||
await sutProvider.GetDependency<IPushNotificationService>().Received(0)
|
||||
.SendPayloadToInstallationAsync(Arg.Any<string>(), Arg.Any<PushType>(), Arg.Any<object>(),
|
||||
Arg.Any<string>(), Arg.Any<string?>(), Arg.Any<ClientType?>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[RepeatingPatternBitAutoData([false, true], [false, true], [false, true])]
|
||||
public async Task SendAsync_UserIdSet_SendPayloadToUserAsync(bool haveIdentifier, bool haveDeviceId,
|
||||
bool haveOrganizationId, SutProvider<PushController> sutProvider, Guid installationId, Guid userId,
|
||||
Guid identifier, Guid deviceId)
|
||||
{
|
||||
sutProvider.GetDependency<IGlobalSettings>().SelfHosted = false;
|
||||
sutProvider.GetDependency<ICurrentContext>().InstallationId.Returns(installationId);
|
||||
|
||||
var expectedUserId = $"{installationId}_{userId}";
|
||||
var expectedIdentifier = haveIdentifier ? $"{installationId}_{identifier}" : null;
|
||||
var expectedDeviceId = haveDeviceId ? $"{installationId}_{deviceId}" : null;
|
||||
|
||||
await sutProvider.Sut.SendAsync(new PushSendRequestModel
|
||||
{
|
||||
Type = PushType.Notification,
|
||||
UserId = userId.ToString(),
|
||||
OrganizationId = haveOrganizationId ? Guid.NewGuid().ToString() : null,
|
||||
InstallationId = null,
|
||||
Payload = "test-payload",
|
||||
DeviceId = haveDeviceId ? deviceId.ToString() : null,
|
||||
Identifier = haveIdentifier ? identifier.ToString() : null,
|
||||
ClientType = ClientType.All,
|
||||
});
|
||||
|
||||
await sutProvider.GetDependency<IPushNotificationService>().Received(1)
|
||||
.SendPayloadToUserAsync(expectedUserId, PushType.Notification, "test-payload", expectedIdentifier,
|
||||
expectedDeviceId, ClientType.All);
|
||||
await sutProvider.GetDependency<IPushNotificationService>().Received(0)
|
||||
.SendPayloadToOrganizationAsync(Arg.Any<string>(), Arg.Any<PushType>(), Arg.Any<object>(),
|
||||
Arg.Any<string>(), Arg.Any<string?>(), Arg.Any<ClientType?>());
|
||||
await sutProvider.GetDependency<IPushNotificationService>().Received(0)
|
||||
.SendPayloadToInstallationAsync(Arg.Any<string>(), Arg.Any<PushType>(), Arg.Any<object>(),
|
||||
Arg.Any<string>(), Arg.Any<string?>(), Arg.Any<ClientType?>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[RepeatingPatternBitAutoData([false, true], [false, true])]
|
||||
public async Task SendAsync_OrganizationIdSet_SendPayloadToOrganizationAsync(bool haveIdentifier, bool haveDeviceId,
|
||||
SutProvider<PushController> sutProvider, Guid installationId, Guid organizationId, Guid identifier,
|
||||
Guid deviceId)
|
||||
{
|
||||
sutProvider.GetDependency<IGlobalSettings>().SelfHosted = false;
|
||||
sutProvider.GetDependency<ICurrentContext>().InstallationId.Returns(installationId);
|
||||
|
||||
var expectedOrganizationId = $"{installationId}_{organizationId}";
|
||||
var expectedIdentifier = haveIdentifier ? $"{installationId}_{identifier}" : null;
|
||||
var expectedDeviceId = haveDeviceId ? $"{installationId}_{deviceId}" : null;
|
||||
|
||||
await sutProvider.Sut.SendAsync(new PushSendRequestModel
|
||||
{
|
||||
Type = PushType.Notification,
|
||||
UserId = null,
|
||||
OrganizationId = organizationId.ToString(),
|
||||
InstallationId = null,
|
||||
Payload = "test-payload",
|
||||
DeviceId = haveDeviceId ? deviceId.ToString() : null,
|
||||
Identifier = haveIdentifier ? identifier.ToString() : null,
|
||||
ClientType = ClientType.All,
|
||||
});
|
||||
|
||||
await sutProvider.GetDependency<IPushNotificationService>().Received(1)
|
||||
.SendPayloadToOrganizationAsync(expectedOrganizationId, PushType.Notification, "test-payload",
|
||||
expectedIdentifier, expectedDeviceId, ClientType.All);
|
||||
await sutProvider.GetDependency<IPushNotificationService>().Received(0)
|
||||
.SendPayloadToUserAsync(Arg.Any<string>(), Arg.Any<PushType>(), Arg.Any<object>(), Arg.Any<string>(),
|
||||
Arg.Any<string?>(), Arg.Any<ClientType?>());
|
||||
await sutProvider.GetDependency<IPushNotificationService>().Received(0)
|
||||
.SendPayloadToInstallationAsync(Arg.Any<string>(), Arg.Any<PushType>(), Arg.Any<object>(),
|
||||
Arg.Any<string>(), Arg.Any<string?>(), Arg.Any<ClientType?>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[RepeatingPatternBitAutoData([false, true], [false, true])]
|
||||
public async Task SendAsync_InstallationIdSet_SendPayloadToInstallationAsync(bool haveIdentifier, bool haveDeviceId,
|
||||
SutProvider<PushController> sutProvider, Guid installationId, Guid identifier, Guid deviceId)
|
||||
{
|
||||
sutProvider.GetDependency<IGlobalSettings>().SelfHosted = false;
|
||||
sutProvider.GetDependency<ICurrentContext>().InstallationId.Returns(installationId);
|
||||
|
||||
var expectedIdentifier = haveIdentifier ? $"{installationId}_{identifier}" : null;
|
||||
var expectedDeviceId = haveDeviceId ? $"{installationId}_{deviceId}" : null;
|
||||
|
||||
await sutProvider.Sut.SendAsync(new PushSendRequestModel
|
||||
{
|
||||
Type = PushType.Notification,
|
||||
UserId = null,
|
||||
OrganizationId = null,
|
||||
InstallationId = installationId.ToString(),
|
||||
Payload = "test-payload",
|
||||
DeviceId = haveDeviceId ? deviceId.ToString() : null,
|
||||
Identifier = haveIdentifier ? identifier.ToString() : null,
|
||||
ClientType = ClientType.All,
|
||||
});
|
||||
|
||||
await sutProvider.GetDependency<IPushNotificationService>().Received(1)
|
||||
.SendPayloadToInstallationAsync(installationId.ToString(), PushType.Notification, "test-payload",
|
||||
expectedIdentifier, expectedDeviceId, ClientType.All);
|
||||
await sutProvider.GetDependency<IPushNotificationService>().Received(0)
|
||||
.SendPayloadToOrganizationAsync(Arg.Any<string>(), Arg.Any<PushType>(), Arg.Any<object>(),
|
||||
Arg.Any<string>(), Arg.Any<string?>(), Arg.Any<ClientType?>());
|
||||
await sutProvider.GetDependency<IPushNotificationService>().Received(0)
|
||||
.SendPayloadToUserAsync(Arg.Any<string>(), Arg.Any<PushType>(), Arg.Any<object>(), Arg.Any<string>(),
|
||||
Arg.Any<string?>(), Arg.Any<ClientType?>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task SendAsync_InstallationIdNotMatching_BadRequest(SutProvider<PushController> sutProvider,
|
||||
Guid installationId)
|
||||
{
|
||||
sutProvider.GetDependency<IGlobalSettings>().SelfHosted = false;
|
||||
sutProvider.GetDependency<ICurrentContext>().InstallationId.Returns(installationId);
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
sutProvider.Sut.SendAsync(new PushSendRequestModel
|
||||
{
|
||||
Type = PushType.Notification,
|
||||
UserId = null,
|
||||
OrganizationId = null,
|
||||
InstallationId = Guid.NewGuid().ToString(),
|
||||
Payload = "test-payload",
|
||||
DeviceId = null,
|
||||
Identifier = null,
|
||||
ClientType = ClientType.All,
|
||||
}));
|
||||
|
||||
Assert.Equal("InstallationId does not match current context.", exception.Message);
|
||||
|
||||
await sutProvider.GetDependency<IPushNotificationService>().Received(0)
|
||||
.SendPayloadToInstallationAsync(Arg.Any<string>(), Arg.Any<PushType>(), Arg.Any<object>(),
|
||||
Arg.Any<string>(), Arg.Any<string?>(), Arg.Any<ClientType?>());
|
||||
await sutProvider.GetDependency<IPushNotificationService>().Received(0)
|
||||
.SendPayloadToOrganizationAsync(Arg.Any<string>(), Arg.Any<PushType>(), Arg.Any<object>(),
|
||||
Arg.Any<string>(), Arg.Any<string?>(), Arg.Any<ClientType?>());
|
||||
await sutProvider.GetDependency<IPushNotificationService>().Received(0)
|
||||
.SendPayloadToUserAsync(Arg.Any<string>(), Arg.Any<PushType>(), Arg.Any<object>(), Arg.Any<string>(),
|
||||
Arg.Any<string?>(), Arg.Any<ClientType?>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(false, true)]
|
||||
[BitAutoData(false, false)]
|
||||
|
@ -1,9 +1,9 @@
|
||||
using System.Text.Json;
|
||||
using Bit.Core.AdminConsole.Models.Data.Integrations;
|
||||
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||
using Bit.Core.Enums;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Models.Data.Integrations;
|
||||
namespace Bit.Core.Test.Models.Data.EventIntegrations;
|
||||
|
||||
public class IntegrationMessageTests
|
||||
{
|
||||
@ -45,6 +45,7 @@ public class IntegrationMessageTests
|
||||
var json = message.ToJson();
|
||||
var result = IntegrationMessage<WebhookIntegrationConfigurationDetails>.FromJson(json);
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(message.Configuration, result.Configuration);
|
||||
Assert.Equal(message.MessageId, result.MessageId);
|
||||
Assert.Equal(message.RenderedTemplate, result.RenderedTemplate);
|
@ -10,6 +10,7 @@ using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Models.Data.Organizations.OrganizationUsers;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
@ -442,4 +443,98 @@ public class ConfirmOrganizationUserCommandTests
|
||||
await sutProvider.GetDependency<IMailService>().Received(1).SendOrganizationConfirmedEmailAsync(org.DisplayName(), user.Email, orgUser.AccessSecretsManager);
|
||||
await organizationUserRepository.Received(1).ReplaceManyAsync(Arg.Is<List<OrganizationUser>>(users => users.Contains(orgUser) && users.Count == 1));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ConfirmUserAsync_WithCreateDefaultLocationEnabled_WithPersonalOwnershipPolicyApplicable_WithValidCollectionName_CreatesDefaultCollection(
|
||||
Organization organization, OrganizationUser confirmingUser,
|
||||
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser, User user,
|
||||
string key, string collectionName, SutProvider<ConfirmOrganizationUserCommand> sutProvider)
|
||||
{
|
||||
organization.PlanType = PlanType.EnterpriseAnnually;
|
||||
orgUser.OrganizationId = confirmingUser.OrganizationId = organization.Id;
|
||||
orgUser.UserId = user.Id;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(organization.Id).Returns(organization);
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>().GetManyAsync(default).ReturnsForAnyArgs(new[] { orgUser });
|
||||
sutProvider.GetDependency<IUserRepository>().GetManyAsync(default).ReturnsForAnyArgs(new[] { user });
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.CreateDefaultLocation).Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IPolicyRequirementQuery>()
|
||||
.GetAsync<PersonalOwnershipPolicyRequirement>(user.Id)
|
||||
.Returns(new PersonalOwnershipPolicyRequirement(
|
||||
PersonalOwnershipState.Restricted,
|
||||
[organization.Id]));
|
||||
|
||||
await sutProvider.Sut.ConfirmUserAsync(orgUser.OrganizationId, orgUser.Id, key, confirmingUser.Id, collectionName);
|
||||
|
||||
await sutProvider.GetDependency<ICollectionRepository>()
|
||||
.Received(1)
|
||||
.CreateAsync(
|
||||
Arg.Is<Collection>(c => c.Name == collectionName &&
|
||||
c.OrganizationId == organization.Id &&
|
||||
c.Type == CollectionType.DefaultUserCollection),
|
||||
Arg.Is<IEnumerable<CollectionAccessSelection>>(groups => groups == null),
|
||||
Arg.Is<IEnumerable<CollectionAccessSelection>>(u =>
|
||||
u.Count() == 1 &&
|
||||
u.First().Id == orgUser.Id &&
|
||||
u.First().Manage == true));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ConfirmUserAsync_WithCreateDefaultLocationEnabled_WithPersonalOwnershipPolicyApplicable_WithInvalidCollectionName_DoesNotCreateDefaultCollection(
|
||||
Organization org, OrganizationUser confirmingUser,
|
||||
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser, User user,
|
||||
string key, SutProvider<ConfirmOrganizationUserCommand> sutProvider)
|
||||
{
|
||||
org.PlanType = PlanType.EnterpriseAnnually;
|
||||
orgUser.OrganizationId = confirmingUser.OrganizationId = org.Id;
|
||||
orgUser.UserId = user.Id;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>().GetManyAsync(default).ReturnsForAnyArgs(new[] { orgUser });
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(org.Id).Returns(org);
|
||||
sutProvider.GetDependency<IUserRepository>().GetManyAsync(default).ReturnsForAnyArgs(new[] { user });
|
||||
|
||||
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.CreateDefaultLocation).Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IPolicyRequirementQuery>()
|
||||
.GetAsync<PersonalOwnershipPolicyRequirement>(user.Id)
|
||||
.Returns(new PersonalOwnershipPolicyRequirement(
|
||||
PersonalOwnershipState.Restricted,
|
||||
[org.Id]));
|
||||
|
||||
await sutProvider.Sut.ConfirmUserAsync(orgUser.OrganizationId, orgUser.Id, key, confirmingUser.Id, "");
|
||||
|
||||
await sutProvider.GetDependency<ICollectionRepository>()
|
||||
.DidNotReceive()
|
||||
.CreateAsync(Arg.Any<Collection>(), Arg.Any<IEnumerable<CollectionAccessSelection>>(), Arg.Any<IEnumerable<CollectionAccessSelection>>());
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task ConfirmUserAsync_WithCreateDefaultLocationEnabled_WithPersonalOwnershipPolicyNotApplicable_DoesNotCreateDefaultCollection(
|
||||
Organization org, OrganizationUser confirmingUser,
|
||||
[OrganizationUser(OrganizationUserStatusType.Accepted)] OrganizationUser orgUser, User user,
|
||||
string key, string collectionName, SutProvider<ConfirmOrganizationUserCommand> sutProvider)
|
||||
{
|
||||
org.PlanType = PlanType.EnterpriseAnnually;
|
||||
orgUser.OrganizationId = confirmingUser.OrganizationId = org.Id;
|
||||
orgUser.UserId = user.Id;
|
||||
|
||||
sutProvider.GetDependency<IOrganizationRepository>().GetByIdAsync(org.Id).Returns(org);
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>().GetManyAsync(default).ReturnsForAnyArgs(new[] { orgUser });
|
||||
sutProvider.GetDependency<IUserRepository>().GetManyAsync(default).ReturnsForAnyArgs(new[] { user });
|
||||
sutProvider.GetDependency<IFeatureService>().IsEnabled(FeatureFlagKeys.CreateDefaultLocation).Returns(true);
|
||||
|
||||
sutProvider.GetDependency<IPolicyRequirementQuery>()
|
||||
.GetAsync<PersonalOwnershipPolicyRequirement>(user.Id)
|
||||
.Returns(new PersonalOwnershipPolicyRequirement(
|
||||
PersonalOwnershipState.Restricted,
|
||||
[Guid.NewGuid()]));
|
||||
|
||||
await sutProvider.Sut.ConfirmUserAsync(orgUser.OrganizationId, orgUser.Id, key, confirmingUser.Id, collectionName);
|
||||
|
||||
await sutProvider.GetDependency<ICollectionRepository>()
|
||||
.DidNotReceive()
|
||||
.CreateAsync(Arg.Any<Collection>(), Arg.Any<IEnumerable<CollectionAccessSelection>>(), Arg.Any<IEnumerable<CollectionAccessSelection>>());
|
||||
}
|
||||
}
|
||||
|
@ -27,8 +27,10 @@ public class UpdateOrganizationUserCommandTests
|
||||
List<CollectionAccessSelection> collections, List<Guid> groups, SutProvider<UpdateOrganizationUserCommand> sutProvider)
|
||||
{
|
||||
user.Id = default(Guid);
|
||||
var existingUserType = OrganizationUserType.User;
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.UpdateUserAsync(user, savingUserId, collections, groups));
|
||||
() => sutProvider.Sut.UpdateUserAsync(user, existingUserType, savingUserId, collections, groups));
|
||||
Assert.Contains("invite the user first", exception.Message.ToLowerInvariant());
|
||||
}
|
||||
|
||||
@ -37,9 +39,10 @@ public class UpdateOrganizationUserCommandTests
|
||||
Guid? savingUserId, SutProvider<UpdateOrganizationUserCommand> sutProvider)
|
||||
{
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>().GetByIdAsync(user.Id).Returns(originalUser);
|
||||
var existingUserType = OrganizationUserType.User;
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.UpdateUserAsync(user, savingUserId, null, null));
|
||||
() => sutProvider.Sut.UpdateUserAsync(user, existingUserType, savingUserId, null, null));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
@ -55,8 +58,10 @@ public class UpdateOrganizationUserCommandTests
|
||||
.Returns(callInfo => callInfo.Arg<IEnumerable<Guid>>()
|
||||
.Select(guid => new Collection { Id = guid, OrganizationId = CoreHelpers.GenerateComb() }).ToList());
|
||||
|
||||
var existingUserType = OrganizationUserType.User;
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.UpdateUserAsync(user, savingUserId, collectionAccess, null));
|
||||
() => sutProvider.Sut.UpdateUserAsync(user, existingUserType, savingUserId, collectionAccess, null));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
@ -76,9 +81,9 @@ public class UpdateOrganizationUserCommandTests
|
||||
result.RemoveAt(0);
|
||||
return result;
|
||||
});
|
||||
|
||||
var existingUserType = OrganizationUserType.User;
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.UpdateUserAsync(user, savingUserId, collectionAccess, null));
|
||||
() => sutProvider.Sut.UpdateUserAsync(user, existingUserType, savingUserId, collectionAccess, null));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
@ -94,8 +99,10 @@ public class UpdateOrganizationUserCommandTests
|
||||
.Returns(callInfo => callInfo.Arg<IEnumerable<Guid>>()
|
||||
.Select(guid => new Group { Id = guid, OrganizationId = CoreHelpers.GenerateComb() }).ToList());
|
||||
|
||||
var existingUserType = OrganizationUserType.User;
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.UpdateUserAsync(user, savingUserId, null, groupAccess));
|
||||
() => sutProvider.Sut.UpdateUserAsync(user, existingUserType, savingUserId, null, groupAccess));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
@ -115,9 +122,9 @@ public class UpdateOrganizationUserCommandTests
|
||||
result.RemoveAt(0);
|
||||
return result;
|
||||
});
|
||||
|
||||
var existingUserType = OrganizationUserType.User;
|
||||
await Assert.ThrowsAsync<NotFoundException>(
|
||||
() => sutProvider.Sut.UpdateUserAsync(user, savingUserId, null, groupAccess));
|
||||
() => sutProvider.Sut.UpdateUserAsync(user, existingUserType, savingUserId, null, groupAccess));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
@ -165,7 +172,9 @@ public class UpdateOrganizationUserCommandTests
|
||||
.GetCountByFreeOrganizationAdminUserAsync(newUserData.Id)
|
||||
.Returns(0);
|
||||
|
||||
await sutProvider.Sut.UpdateUserAsync(newUserData, savingUser.UserId, collections, groups);
|
||||
var existingUserType = OrganizationUserType.User;
|
||||
|
||||
await sutProvider.Sut.UpdateUserAsync(newUserData, existingUserType, savingUser.UserId, collections, groups);
|
||||
|
||||
var organizationService = sutProvider.GetDependency<IOrganizationService>();
|
||||
await organizationService.Received(1).ValidateOrganizationUserUpdatePermissions(
|
||||
@ -184,7 +193,7 @@ public class UpdateOrganizationUserCommandTests
|
||||
[Theory]
|
||||
[BitAutoData(OrganizationUserType.Admin)]
|
||||
[BitAutoData(OrganizationUserType.Owner)]
|
||||
public async Task UpdateUserAsync_WhenUpdatingUserToAdminOrOwner_WithUserAlreadyAdminOfAnotherFreeOrganization_Throws(
|
||||
public async Task UpdateUserAsync_WhenUpdatingUserToAdminOrOwner_AndExistingUserTypeIsNotAdminOrOwner_WithUserAlreadyAdminOfAnotherFreeOrganization_Throws(
|
||||
OrganizationUserType userType,
|
||||
OrganizationUser oldUserData,
|
||||
OrganizationUser newUserData,
|
||||
@ -199,10 +208,39 @@ public class UpdateOrganizationUserCommandTests
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetCountByFreeOrganizationAdminUserAsync(newUserData.UserId!.Value)
|
||||
.Returns(1);
|
||||
var existingUserType = OrganizationUserType.User;
|
||||
|
||||
// Assert
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.UpdateUserAsync(newUserData, null, null, null));
|
||||
() => sutProvider.Sut.UpdateUserAsync(newUserData, existingUserType, null, null, null));
|
||||
Assert.Contains("User can only be an admin of one free organization.", exception.Message);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(OrganizationUserType.Admin, OrganizationUserType.Admin)]
|
||||
[BitAutoData(OrganizationUserType.Admin, OrganizationUserType.Owner)]
|
||||
[BitAutoData(OrganizationUserType.Owner, OrganizationUserType.Admin)]
|
||||
[BitAutoData(OrganizationUserType.Owner, OrganizationUserType.Owner)]
|
||||
public async Task UpdateUserAsync_WhenUpdatingUserToAdminOrOwner_AndExistingUserTypeIsAdminOrOwner_WithUserAlreadyAdminOfAnotherFreeOrganization_Throws(
|
||||
OrganizationUserType newUserType,
|
||||
OrganizationUserType existingUserType,
|
||||
OrganizationUser oldUserData,
|
||||
OrganizationUser newUserData,
|
||||
Organization organization,
|
||||
SutProvider<UpdateOrganizationUserCommand> sutProvider)
|
||||
{
|
||||
organization.PlanType = PlanType.Free;
|
||||
newUserData.Type = newUserType;
|
||||
|
||||
Setup(sutProvider, organization, newUserData, oldUserData);
|
||||
|
||||
sutProvider.GetDependency<IOrganizationUserRepository>()
|
||||
.GetCountByFreeOrganizationAdminUserAsync(newUserData.UserId!.Value)
|
||||
.Returns(2);
|
||||
|
||||
// Assert
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.UpdateUserAsync(newUserData, existingUserType, null, null, null));
|
||||
Assert.Contains("User can only be an admin of one free organization.", exception.Message);
|
||||
}
|
||||
|
||||
|
@ -12,20 +12,42 @@ namespace Bit.Core.Test.AdminConsole.OrganizationFeatures.Policies.PolicyRequire
|
||||
public class PersonalOwnershipPolicyRequirementFactoryTests
|
||||
{
|
||||
[Theory, BitAutoData]
|
||||
public void DisablePersonalOwnership_WithNoPolicies_ReturnsFalse(SutProvider<PersonalOwnershipPolicyRequirementFactory> sutProvider)
|
||||
public void State_WithNoPolicies_ReturnsAllowed(SutProvider<PersonalOwnershipPolicyRequirementFactory> sutProvider)
|
||||
{
|
||||
var actual = sutProvider.Sut.Create([]);
|
||||
|
||||
Assert.False(actual.DisablePersonalOwnership);
|
||||
Assert.Equal(PersonalOwnershipState.Allowed, actual.State);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void DisablePersonalOwnership_WithPersonalOwnershipPolicies_ReturnsTrue(
|
||||
public void State_WithPersonalOwnershipPolicies_ReturnsRestricted(
|
||||
[PolicyDetails(PolicyType.PersonalOwnership)] PolicyDetails[] policies,
|
||||
SutProvider<PersonalOwnershipPolicyRequirementFactory> sutProvider)
|
||||
{
|
||||
var actual = sutProvider.Sut.Create(policies);
|
||||
|
||||
Assert.True(actual.DisablePersonalOwnership);
|
||||
Assert.Equal(PersonalOwnershipState.Restricted, actual.State);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void RequiresDefaultCollection_WithNoPolicies_ReturnsFalse(
|
||||
Guid organizationId,
|
||||
SutProvider<PersonalOwnershipPolicyRequirementFactory> sutProvider)
|
||||
{
|
||||
var actual = sutProvider.Sut.Create([]);
|
||||
|
||||
Assert.False(actual.RequiresDefaultCollection(organizationId));
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void RequiresDefaultCollection_WithPersonalOwnershipPolicies_ReturnsCorrectResult(
|
||||
[PolicyDetails(PolicyType.PersonalOwnership)] PolicyDetails[] policies,
|
||||
Guid nonPolicyOrganizationId,
|
||||
SutProvider<PersonalOwnershipPolicyRequirementFactory> sutProvider)
|
||||
{
|
||||
var actual = sutProvider.Sut.Create(policies);
|
||||
|
||||
Assert.True(actual.RequiresDefaultCollection(policies[0].OrganizationId));
|
||||
Assert.False(actual.RequiresDefaultCollection(nonPolicyOrganizationId));
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
#nullable enable
|
||||
|
||||
using Azure.Messaging.ServiceBus;
|
||||
using Bit.Core.AdminConsole.Models.Data.Integrations;
|
||||
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
@ -52,7 +52,6 @@ public class AzureServiceBusIntegrationListenerServiceTests
|
||||
public async Task HandleMessageAsync_FailureNotRetryable_PublishesToDeadLetterQueue(IntegrationMessage<WebhookIntegrationConfiguration> message)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
message.DelayUntilDate = DateTime.UtcNow.AddMinutes(-1);
|
||||
message.RetryCount = 0;
|
||||
|
||||
var result = new IntegrationHandlerResult(false, message);
|
||||
@ -71,7 +70,6 @@ public class AzureServiceBusIntegrationListenerServiceTests
|
||||
public async Task HandleMessageAsync_FailureRetryableButTooManyRetries_PublishesToDeadLetterQueue(IntegrationMessage<WebhookIntegrationConfiguration> message)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
message.DelayUntilDate = DateTime.UtcNow.AddMinutes(-1);
|
||||
message.RetryCount = _maxRetries;
|
||||
var result = new IntegrationHandlerResult(false, message);
|
||||
result.Retryable = true;
|
||||
@ -90,12 +88,10 @@ public class AzureServiceBusIntegrationListenerServiceTests
|
||||
public async Task HandleMessageAsync_FailureRetryable_PublishesToRetryQueue(IntegrationMessage<WebhookIntegrationConfiguration> message)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
message.DelayUntilDate = DateTime.UtcNow.AddMinutes(-1);
|
||||
message.RetryCount = 0;
|
||||
|
||||
var result = new IntegrationHandlerResult(false, message);
|
||||
result.Retryable = true;
|
||||
result.DelayUntilDate = DateTime.UtcNow.AddMinutes(1);
|
||||
_handler.HandleAsync(Arg.Any<string>()).Returns(result);
|
||||
|
||||
var expected = (IntegrationMessage<WebhookIntegrationConfiguration>)IntegrationMessage<WebhookIntegrationConfiguration>.FromJson(message.ToJson())!;
|
||||
@ -110,7 +106,6 @@ public class AzureServiceBusIntegrationListenerServiceTests
|
||||
public async Task HandleMessageAsync_SuccessfulResult_Succeeds(IntegrationMessage<WebhookIntegrationConfiguration> message)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
message.DelayUntilDate = DateTime.UtcNow.AddMinutes(-1);
|
||||
var result = new IntegrationHandlerResult(true, message);
|
||||
_handler.HandleAsync(Arg.Any<string>()).Returns(result);
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
using System.Text.Json;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Models.Data.Integrations;
|
||||
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Data;
|
||||
|
@ -1,4 +1,4 @@
|
||||
using Bit.Core.AdminConsole.Models.Data.Integrations;
|
||||
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Services;
|
||||
using Xunit;
|
||||
|
@ -1,9 +1,10 @@
|
||||
using System.Text;
|
||||
using Bit.Core.AdminConsole.Models.Data.Integrations;
|
||||
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Bit.Test.Common.Helpers;
|
||||
using Microsoft.Extensions.Time.Testing;
|
||||
using NSubstitute;
|
||||
using RabbitMQ.Client;
|
||||
using RabbitMQ.Client.Events;
|
||||
@ -18,19 +19,24 @@ public class RabbitMqIntegrationListenerServiceTests
|
||||
private const string _queueName = "test_queue";
|
||||
private const string _retryQueueName = "test_queue_retry";
|
||||
private const string _routingKey = "test_routing_key";
|
||||
private readonly DateTime _now = new DateTime(2014, 3, 2, 1, 0, 0, DateTimeKind.Utc);
|
||||
private readonly IIntegrationHandler _handler = Substitute.For<IIntegrationHandler>();
|
||||
private readonly IRabbitMqService _rabbitMqService = Substitute.For<IRabbitMqService>();
|
||||
|
||||
private SutProvider<RabbitMqIntegrationListenerService> GetSutProvider()
|
||||
{
|
||||
return new SutProvider<RabbitMqIntegrationListenerService>()
|
||||
var sutProvider = new SutProvider<RabbitMqIntegrationListenerService>()
|
||||
.SetDependency(_handler)
|
||||
.SetDependency(_rabbitMqService)
|
||||
.SetDependency(_queueName, "queueName")
|
||||
.SetDependency(_retryQueueName, "retryQueueName")
|
||||
.SetDependency(_routingKey, "routingKey")
|
||||
.SetDependency(_maxRetries, "maxRetries")
|
||||
.WithFakeTimeProvider()
|
||||
.Create();
|
||||
sutProvider.GetDependency<FakeTimeProvider>().SetUtcNow(_now);
|
||||
|
||||
return sutProvider;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -55,7 +61,7 @@ public class RabbitMqIntegrationListenerServiceTests
|
||||
var cancellationToken = CancellationToken.None;
|
||||
await sutProvider.Sut.StartAsync(cancellationToken);
|
||||
|
||||
message.DelayUntilDate = DateTime.UtcNow.AddMinutes(-1);
|
||||
message.DelayUntilDate = null;
|
||||
message.RetryCount = 0;
|
||||
var eventArgs = new BasicDeliverEventArgs(
|
||||
consumerTag: string.Empty,
|
||||
@ -94,7 +100,7 @@ public class RabbitMqIntegrationListenerServiceTests
|
||||
var cancellationToken = CancellationToken.None;
|
||||
await sutProvider.Sut.StartAsync(cancellationToken);
|
||||
|
||||
message.DelayUntilDate = DateTime.UtcNow.AddMinutes(-1);
|
||||
message.DelayUntilDate = null;
|
||||
message.RetryCount = _maxRetries;
|
||||
var eventArgs = new BasicDeliverEventArgs(
|
||||
consumerTag: string.Empty,
|
||||
@ -132,7 +138,7 @@ public class RabbitMqIntegrationListenerServiceTests
|
||||
var cancellationToken = CancellationToken.None;
|
||||
await sutProvider.Sut.StartAsync(cancellationToken);
|
||||
|
||||
message.DelayUntilDate = DateTime.UtcNow.AddMinutes(-1);
|
||||
message.DelayUntilDate = null;
|
||||
message.RetryCount = 0;
|
||||
var eventArgs = new BasicDeliverEventArgs(
|
||||
consumerTag: string.Empty,
|
||||
@ -145,7 +151,7 @@ public class RabbitMqIntegrationListenerServiceTests
|
||||
);
|
||||
var result = new IntegrationHandlerResult(false, message);
|
||||
result.Retryable = true;
|
||||
result.DelayUntilDate = DateTime.UtcNow.AddMinutes(1);
|
||||
result.DelayUntilDate = _now.AddMinutes(1);
|
||||
_handler.HandleAsync(Arg.Any<string>()).Returns(result);
|
||||
|
||||
var expected = IntegrationMessage<WebhookIntegrationConfiguration>.FromJson(message.ToJson());
|
||||
@ -173,7 +179,7 @@ public class RabbitMqIntegrationListenerServiceTests
|
||||
var cancellationToken = CancellationToken.None;
|
||||
await sutProvider.Sut.StartAsync(cancellationToken);
|
||||
|
||||
message.DelayUntilDate = DateTime.UtcNow.AddMinutes(-1);
|
||||
message.DelayUntilDate = null;
|
||||
var eventArgs = new BasicDeliverEventArgs(
|
||||
consumerTag: string.Empty,
|
||||
deliveryTag: 0,
|
||||
@ -205,7 +211,7 @@ public class RabbitMqIntegrationListenerServiceTests
|
||||
var cancellationToken = CancellationToken.None;
|
||||
await sutProvider.Sut.StartAsync(cancellationToken);
|
||||
|
||||
message.DelayUntilDate = DateTime.UtcNow.AddMinutes(1);
|
||||
message.DelayUntilDate = _now.AddMinutes(1);
|
||||
var eventArgs = new BasicDeliverEventArgs(
|
||||
consumerTag: string.Empty,
|
||||
deliveryTag: 0,
|
||||
|
@ -1,4 +1,4 @@
|
||||
using Bit.Core.AdminConsole.Models.Data.Integrations;
|
||||
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
|
@ -1,10 +1,11 @@
|
||||
using System.Net;
|
||||
using Bit.Core.AdminConsole.Models.Data.Integrations;
|
||||
using Bit.Core.AdminConsole.Models.Data.EventIntegrations;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Bit.Test.Common.Helpers;
|
||||
using Bit.Test.Common.MockedHttpClient;
|
||||
using Microsoft.Extensions.Time.Testing;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
@ -33,6 +34,7 @@ public class WebhookIntegrationHandlerTests
|
||||
|
||||
return new SutProvider<WebhookIntegrationHandler>()
|
||||
.SetDependency(clientFactory)
|
||||
.WithFakeTimeProvider()
|
||||
.Create();
|
||||
}
|
||||
|
||||
@ -62,9 +64,13 @@ public class WebhookIntegrationHandlerTests
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleAsync_TooManyRequests_ReturnsFailureSetsNotBeforUtc(IntegrationMessage<WebhookIntegrationConfigurationDetails> message)
|
||||
public async Task HandleAsync_TooManyRequests_ReturnsFailureSetsDelayUntilDate(IntegrationMessage<WebhookIntegrationConfigurationDetails> message)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
var now = new DateTime(2014, 3, 2, 1, 0, 0, DateTimeKind.Utc);
|
||||
var retryAfter = now.AddSeconds(60);
|
||||
|
||||
sutProvider.GetDependency<FakeTimeProvider>().SetUtcNow(now);
|
||||
message.Configuration = new WebhookIntegrationConfigurationDetails(_webhookUrl);
|
||||
|
||||
_handler.Fallback
|
||||
@ -78,19 +84,21 @@ public class WebhookIntegrationHandlerTests
|
||||
Assert.True(result.Retryable);
|
||||
Assert.Equal(result.Message, message);
|
||||
Assert.True(result.DelayUntilDate.HasValue);
|
||||
Assert.InRange(result.DelayUntilDate.Value, DateTime.UtcNow.AddSeconds(59), DateTime.UtcNow.AddSeconds(61));
|
||||
Assert.Equal(retryAfter, result.DelayUntilDate.Value);
|
||||
Assert.Equal("Too Many Requests", result.FailureReason);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task HandleAsync_TooManyRequestsWithDate_ReturnsFailureSetsNotBeforUtc(IntegrationMessage<WebhookIntegrationConfigurationDetails> message)
|
||||
public async Task HandleAsync_TooManyRequestsWithDate_ReturnsFailureSetsDelayUntilDate(IntegrationMessage<WebhookIntegrationConfigurationDetails> message)
|
||||
{
|
||||
var sutProvider = GetSutProvider();
|
||||
var now = new DateTime(2014, 3, 2, 1, 0, 0, DateTimeKind.Utc);
|
||||
var retryAfter = now.AddSeconds(60);
|
||||
message.Configuration = new WebhookIntegrationConfigurationDetails(_webhookUrl);
|
||||
|
||||
_handler.Fallback
|
||||
.WithStatusCode(HttpStatusCode.TooManyRequests)
|
||||
.WithHeader("Retry-After", DateTime.UtcNow.AddSeconds(60).ToString("r")) // "r" is the round-trip format: RFC1123
|
||||
.WithHeader("Retry-After", retryAfter.ToString("r"))
|
||||
.WithContent(new StringContent("<html><head><title>test</title></head><body>test</body></html>"));
|
||||
|
||||
var result = await sutProvider.Sut.HandleAsync(message);
|
||||
@ -99,7 +107,7 @@ public class WebhookIntegrationHandlerTests
|
||||
Assert.True(result.Retryable);
|
||||
Assert.Equal(result.Message, message);
|
||||
Assert.True(result.DelayUntilDate.HasValue);
|
||||
Assert.InRange(result.DelayUntilDate.Value, DateTime.UtcNow.AddSeconds(59), DateTime.UtcNow.AddSeconds(61));
|
||||
Assert.Equal(retryAfter, result.DelayUntilDate.Value);
|
||||
Assert.Equal("Too Many Requests", result.FailureReason);
|
||||
}
|
||||
|
||||
|
@ -11,16 +11,14 @@ namespace Bit.Core.Test.Models.Api.Request;
|
||||
|
||||
public class PushSendRequestModelTests
|
||||
{
|
||||
[Theory]
|
||||
[RepeatingPatternBitAutoData([null, "", " "], [null, "", " "], [null, "", " "])]
|
||||
public void Validate_UserIdOrganizationIdInstallationIdNullOrEmpty_Invalid(string? userId, string? organizationId,
|
||||
string? installationId)
|
||||
[Fact]
|
||||
public void Validate_UserIdOrganizationIdInstallationIdNull_Invalid()
|
||||
{
|
||||
var model = new PushSendRequestModel
|
||||
var model = new PushSendRequestModel<string>
|
||||
{
|
||||
UserId = userId,
|
||||
OrganizationId = organizationId,
|
||||
InstallationId = installationId,
|
||||
UserId = null,
|
||||
OrganizationId = null,
|
||||
InstallationId = null,
|
||||
Type = PushType.SyncCiphers,
|
||||
Payload = "test"
|
||||
};
|
||||
@ -32,16 +30,14 @@ public class PushSendRequestModelTests
|
||||
result => result.ErrorMessage == "UserId or OrganizationId or InstallationId is required.");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[RepeatingPatternBitAutoData([null, "", " "], [null, "", " "])]
|
||||
public void Validate_UserIdProvidedOrganizationIdInstallationIdNullOrEmpty_Valid(string? organizationId,
|
||||
string? installationId)
|
||||
[Fact]
|
||||
public void Validate_UserIdProvidedOrganizationIdInstallationIdNull_Valid()
|
||||
{
|
||||
var model = new PushSendRequestModel
|
||||
var model = new PushSendRequestModel<string>
|
||||
{
|
||||
UserId = Guid.NewGuid().ToString(),
|
||||
OrganizationId = organizationId,
|
||||
InstallationId = installationId,
|
||||
UserId = Guid.NewGuid(),
|
||||
OrganizationId = null,
|
||||
InstallationId = null,
|
||||
Type = PushType.SyncCiphers,
|
||||
Payload = "test"
|
||||
};
|
||||
@ -51,16 +47,14 @@ public class PushSendRequestModelTests
|
||||
Assert.Empty(results);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[RepeatingPatternBitAutoData([null, "", " "], [null, "", " "])]
|
||||
public void Validate_OrganizationIdProvidedUserIdInstallationIdNullOrEmpty_Valid(string? userId,
|
||||
string? installationId)
|
||||
[Fact]
|
||||
public void Validate_OrganizationIdProvidedUserIdInstallationIdNull_Valid()
|
||||
{
|
||||
var model = new PushSendRequestModel
|
||||
var model = new PushSendRequestModel<string>
|
||||
{
|
||||
UserId = userId,
|
||||
OrganizationId = Guid.NewGuid().ToString(),
|
||||
InstallationId = installationId,
|
||||
UserId = null,
|
||||
OrganizationId = Guid.NewGuid(),
|
||||
InstallationId = null,
|
||||
Type = PushType.SyncCiphers,
|
||||
Payload = "test"
|
||||
};
|
||||
@ -70,16 +64,14 @@ public class PushSendRequestModelTests
|
||||
Assert.Empty(results);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[RepeatingPatternBitAutoData([null, "", " "], [null, "", " "])]
|
||||
public void Validate_InstallationIdProvidedUserIdOrganizationIdNullOrEmpty_Valid(string? userId,
|
||||
string? organizationId)
|
||||
[Fact]
|
||||
public void Validate_InstallationIdProvidedUserIdOrganizationIdNull_Valid()
|
||||
{
|
||||
var model = new PushSendRequestModel
|
||||
var model = new PushSendRequestModel<string>
|
||||
{
|
||||
UserId = userId,
|
||||
OrganizationId = organizationId,
|
||||
InstallationId = Guid.NewGuid().ToString(),
|
||||
UserId = null,
|
||||
OrganizationId = null,
|
||||
InstallationId = Guid.NewGuid(),
|
||||
Type = PushType.SyncCiphers,
|
||||
Payload = "test"
|
||||
};
|
||||
@ -94,10 +86,10 @@ public class PushSendRequestModelTests
|
||||
[BitAutoData("Type")]
|
||||
public void Validate_RequiredFieldNotProvided_Invalid(string requiredField)
|
||||
{
|
||||
var model = new PushSendRequestModel
|
||||
var model = new PushSendRequestModel<string>
|
||||
{
|
||||
UserId = Guid.NewGuid().ToString(),
|
||||
OrganizationId = Guid.NewGuid().ToString(),
|
||||
UserId = Guid.NewGuid(),
|
||||
OrganizationId = Guid.NewGuid(),
|
||||
Type = PushType.SyncCiphers,
|
||||
Payload = "test"
|
||||
};
|
||||
@ -115,7 +107,7 @@ public class PushSendRequestModelTests
|
||||
|
||||
var serialized = JsonSerializer.Serialize(dictionary, JsonHelpers.IgnoreWritingNull);
|
||||
var jsonException =
|
||||
Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<PushSendRequestModel>(serialized));
|
||||
Assert.Throws<JsonException>(() => JsonSerializer.Deserialize<PushSendRequestModel<string>>(serialized));
|
||||
Assert.Contains($"missing required properties, including the following: {requiredField}",
|
||||
jsonException.Message);
|
||||
}
|
||||
@ -123,15 +115,15 @@ public class PushSendRequestModelTests
|
||||
[Fact]
|
||||
public void Validate_AllFieldsPresent_Valid()
|
||||
{
|
||||
var model = new PushSendRequestModel
|
||||
var model = new PushSendRequestModel<string>
|
||||
{
|
||||
UserId = Guid.NewGuid().ToString(),
|
||||
OrganizationId = Guid.NewGuid().ToString(),
|
||||
UserId = Guid.NewGuid(),
|
||||
OrganizationId = Guid.NewGuid(),
|
||||
Type = PushType.SyncCiphers,
|
||||
Payload = "test payload",
|
||||
Identifier = Guid.NewGuid().ToString(),
|
||||
ClientType = ClientType.All,
|
||||
DeviceId = Guid.NewGuid().ToString()
|
||||
DeviceId = Guid.NewGuid()
|
||||
};
|
||||
|
||||
var results = Validate(model);
|
||||
@ -139,7 +131,7 @@ public class PushSendRequestModelTests
|
||||
Assert.Empty(results);
|
||||
}
|
||||
|
||||
private static List<ValidationResult> Validate(PushSendRequestModel model)
|
||||
private static List<ValidationResult> Validate<T>(PushSendRequestModel<T> model)
|
||||
{
|
||||
var results = new List<ValidationResult>();
|
||||
Validator.TryValidateObject(model, new ValidationContext(model), results, true);
|
||||
|
@ -5,12 +5,11 @@ using Bit.Core.Auth.Entities;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.NotificationCenter.Entities;
|
||||
using Bit.Core.NotificationCenter.Enums;
|
||||
using Bit.Core.NotificationHub;
|
||||
using Bit.Core.Platform.Push;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Test.NotificationCenter.AutoFixture;
|
||||
using Bit.Core.Tools.Entities;
|
||||
using Bit.Core.Vault.Entities;
|
||||
@ -33,483 +32,6 @@ public class NotificationHubPushNotificationServiceTests
|
||||
private static readonly DateTime _now = DateTime.UtcNow;
|
||||
private static readonly Guid _installationId = Guid.Parse("da73177b-513f-4444-b582-595c890e1022");
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
[NotificationCustomize]
|
||||
public async Task PushNotificationAsync_GlobalInstallationIdDefault_NotSent(
|
||||
SutProvider<NotificationHubPushNotificationService> sutProvider, Notification notification)
|
||||
{
|
||||
sutProvider.GetDependency<IGlobalSettings>().Installation.Id = default;
|
||||
|
||||
await sutProvider.Sut.PushNotificationAsync(notification);
|
||||
|
||||
await sutProvider.GetDependency<INotificationHubPool>()
|
||||
.Received(0)
|
||||
.AllClients
|
||||
.Received(0)
|
||||
.SendTemplateNotificationAsync(Arg.Any<IDictionary<string, string>>(), Arg.Any<string>());
|
||||
await sutProvider.GetDependency<IInstallationDeviceRepository>()
|
||||
.Received(0)
|
||||
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
[NotificationCustomize]
|
||||
public async Task PushNotificationAsync_GlobalInstallationIdSetClientTypeAll_SentToInstallationId(
|
||||
SutProvider<NotificationHubPushNotificationService> sutProvider, Notification notification, Guid installationId)
|
||||
{
|
||||
sutProvider.GetDependency<IGlobalSettings>().Installation.Id = installationId;
|
||||
notification.ClientType = ClientType.All;
|
||||
var expectedNotification = ToNotificationPushNotification(notification, null, installationId);
|
||||
|
||||
await sutProvider.Sut.PushNotificationAsync(notification);
|
||||
|
||||
await AssertSendTemplateNotificationAsync(sutProvider, PushType.Notification,
|
||||
expectedNotification,
|
||||
$"(template:payload && installationId:{installationId})");
|
||||
await sutProvider.GetDependency<IInstallationDeviceRepository>()
|
||||
.Received(0)
|
||||
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(ClientType.Browser)]
|
||||
[BitAutoData(ClientType.Desktop)]
|
||||
[BitAutoData(ClientType.Web)]
|
||||
[BitAutoData(ClientType.Mobile)]
|
||||
[NotificationCustomize]
|
||||
public async Task PushNotificationAsync_GlobalInstallationIdSetClientTypeNotAll_SentToInstallationIdAndClientType(
|
||||
ClientType clientType, SutProvider<NotificationHubPushNotificationService> sutProvider,
|
||||
Notification notification, Guid installationId)
|
||||
{
|
||||
sutProvider.GetDependency<IGlobalSettings>().Installation.Id = installationId;
|
||||
notification.ClientType = clientType;
|
||||
var expectedNotification = ToNotificationPushNotification(notification, null, installationId);
|
||||
|
||||
await sutProvider.Sut.PushNotificationAsync(notification);
|
||||
|
||||
await AssertSendTemplateNotificationAsync(sutProvider, PushType.Notification,
|
||||
expectedNotification,
|
||||
$"(template:payload && installationId:{installationId} && clientType:{clientType})");
|
||||
await sutProvider.GetDependency<IInstallationDeviceRepository>()
|
||||
.Received(0)
|
||||
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(false)]
|
||||
[BitAutoData(true)]
|
||||
[NotificationCustomize(false)]
|
||||
public async Task PushNotificationAsync_UserIdProvidedClientTypeAll_SentToUser(
|
||||
bool organizationIdNull, SutProvider<NotificationHubPushNotificationService> sutProvider,
|
||||
Notification notification)
|
||||
{
|
||||
if (organizationIdNull)
|
||||
{
|
||||
notification.OrganizationId = null;
|
||||
}
|
||||
|
||||
notification.ClientType = ClientType.All;
|
||||
var expectedNotification = ToNotificationPushNotification(notification, null, null);
|
||||
|
||||
await sutProvider.Sut.PushNotificationAsync(notification);
|
||||
|
||||
await AssertSendTemplateNotificationAsync(sutProvider, PushType.Notification,
|
||||
expectedNotification,
|
||||
$"(template:payload_userId:{notification.UserId})");
|
||||
await sutProvider.GetDependency<IInstallationDeviceRepository>()
|
||||
.Received(0)
|
||||
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(ClientType.Browser)]
|
||||
[BitAutoData(ClientType.Desktop)]
|
||||
[BitAutoData(ClientType.Web)]
|
||||
[BitAutoData(ClientType.Mobile)]
|
||||
[NotificationCustomize(false)]
|
||||
public async Task PushNotificationAsync_UserIdProvidedOrganizationIdNullClientTypeNotAll_SentToUser(
|
||||
ClientType clientType, SutProvider<NotificationHubPushNotificationService> sutProvider,
|
||||
Notification notification)
|
||||
{
|
||||
notification.OrganizationId = null;
|
||||
notification.ClientType = clientType;
|
||||
var expectedNotification = ToNotificationPushNotification(notification, null, null);
|
||||
|
||||
await sutProvider.Sut.PushNotificationAsync(notification);
|
||||
|
||||
await AssertSendTemplateNotificationAsync(sutProvider, PushType.Notification,
|
||||
expectedNotification,
|
||||
$"(template:payload_userId:{notification.UserId} && clientType:{clientType})");
|
||||
await sutProvider.GetDependency<IInstallationDeviceRepository>()
|
||||
.Received(0)
|
||||
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(ClientType.Browser)]
|
||||
[BitAutoData(ClientType.Desktop)]
|
||||
[BitAutoData(ClientType.Web)]
|
||||
[BitAutoData(ClientType.Mobile)]
|
||||
[NotificationCustomize(false)]
|
||||
public async Task PushNotificationAsync_UserIdProvidedOrganizationIdProvidedClientTypeNotAll_SentToUser(
|
||||
ClientType clientType, SutProvider<NotificationHubPushNotificationService> sutProvider,
|
||||
Notification notification)
|
||||
{
|
||||
notification.ClientType = clientType;
|
||||
var expectedNotification = ToNotificationPushNotification(notification, null, null);
|
||||
|
||||
await sutProvider.Sut.PushNotificationAsync(notification);
|
||||
|
||||
await AssertSendTemplateNotificationAsync(sutProvider, PushType.Notification,
|
||||
expectedNotification,
|
||||
$"(template:payload_userId:{notification.UserId} && clientType:{clientType})");
|
||||
await sutProvider.GetDependency<IInstallationDeviceRepository>()
|
||||
.Received(0)
|
||||
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
[NotificationCustomize(false)]
|
||||
public async Task PushNotificationAsync_UserIdNullOrganizationIdProvidedClientTypeAll_SentToOrganization(
|
||||
SutProvider<NotificationHubPushNotificationService> sutProvider, Notification notification)
|
||||
{
|
||||
notification.UserId = null;
|
||||
notification.ClientType = ClientType.All;
|
||||
var expectedNotification = ToNotificationPushNotification(notification, null, null);
|
||||
|
||||
await sutProvider.Sut.PushNotificationAsync(notification);
|
||||
|
||||
await AssertSendTemplateNotificationAsync(sutProvider, PushType.Notification,
|
||||
expectedNotification,
|
||||
$"(template:payload && organizationId:{notification.OrganizationId})");
|
||||
await sutProvider.GetDependency<IInstallationDeviceRepository>()
|
||||
.Received(0)
|
||||
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(ClientType.Browser)]
|
||||
[BitAutoData(ClientType.Desktop)]
|
||||
[BitAutoData(ClientType.Web)]
|
||||
[BitAutoData(ClientType.Mobile)]
|
||||
[NotificationCustomize(false)]
|
||||
public async Task PushNotificationAsync_UserIdNullOrganizationIdProvidedClientTypeNotAll_SentToOrganization(
|
||||
ClientType clientType, SutProvider<NotificationHubPushNotificationService> sutProvider,
|
||||
Notification notification)
|
||||
{
|
||||
notification.UserId = null;
|
||||
notification.ClientType = clientType;
|
||||
var expectedNotification = ToNotificationPushNotification(notification, null, null);
|
||||
|
||||
await sutProvider.Sut.PushNotificationAsync(notification);
|
||||
|
||||
await AssertSendTemplateNotificationAsync(sutProvider, PushType.Notification,
|
||||
expectedNotification,
|
||||
$"(template:payload && organizationId:{notification.OrganizationId} && clientType:{clientType})");
|
||||
await sutProvider.GetDependency<IInstallationDeviceRepository>()
|
||||
.Received(0)
|
||||
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
[NotificationCustomize]
|
||||
public async Task PushNotificationStatusAsync_GlobalInstallationIdDefault_NotSent(
|
||||
SutProvider<NotificationHubPushNotificationService> sutProvider, Notification notification,
|
||||
NotificationStatus notificationStatus)
|
||||
{
|
||||
sutProvider.GetDependency<IGlobalSettings>().Installation.Id = default;
|
||||
|
||||
await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus);
|
||||
|
||||
await sutProvider.GetDependency<INotificationHubPool>()
|
||||
.Received(0)
|
||||
.AllClients
|
||||
.Received(0)
|
||||
.SendTemplateNotificationAsync(Arg.Any<IDictionary<string, string>>(), Arg.Any<string>());
|
||||
await sutProvider.GetDependency<IInstallationDeviceRepository>()
|
||||
.Received(0)
|
||||
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
[NotificationCustomize]
|
||||
public async Task PushNotificationStatusAsync_GlobalInstallationIdSetClientTypeAll_SentToInstallationId(
|
||||
SutProvider<NotificationHubPushNotificationService> sutProvider,
|
||||
Notification notification, NotificationStatus notificationStatus, Guid installationId)
|
||||
{
|
||||
sutProvider.GetDependency<IGlobalSettings>().Installation.Id = installationId;
|
||||
notification.ClientType = ClientType.All;
|
||||
|
||||
var expectedNotification = ToNotificationPushNotification(notification, notificationStatus, installationId);
|
||||
|
||||
await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus);
|
||||
|
||||
await AssertSendTemplateNotificationAsync(sutProvider, PushType.NotificationStatus,
|
||||
expectedNotification,
|
||||
$"(template:payload && installationId:{installationId})");
|
||||
await sutProvider.GetDependency<IInstallationDeviceRepository>()
|
||||
.Received(0)
|
||||
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(ClientType.Browser)]
|
||||
[BitAutoData(ClientType.Desktop)]
|
||||
[BitAutoData(ClientType.Web)]
|
||||
[BitAutoData(ClientType.Mobile)]
|
||||
[NotificationCustomize]
|
||||
public async Task
|
||||
PushNotificationStatusAsync_GlobalInstallationIdSetClientTypeNotAll_SentToInstallationIdAndClientType(
|
||||
ClientType clientType, SutProvider<NotificationHubPushNotificationService> sutProvider,
|
||||
Notification notification, NotificationStatus notificationStatus, Guid installationId)
|
||||
{
|
||||
sutProvider.GetDependency<IGlobalSettings>().Installation.Id = installationId;
|
||||
notification.ClientType = clientType;
|
||||
|
||||
var expectedNotification = ToNotificationPushNotification(notification, notificationStatus, installationId);
|
||||
|
||||
await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus);
|
||||
|
||||
await AssertSendTemplateNotificationAsync(sutProvider, PushType.NotificationStatus,
|
||||
expectedNotification,
|
||||
$"(template:payload && installationId:{installationId} && clientType:{clientType})");
|
||||
await sutProvider.GetDependency<IInstallationDeviceRepository>()
|
||||
.Received(0)
|
||||
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(false)]
|
||||
[BitAutoData(true)]
|
||||
[NotificationCustomize(false)]
|
||||
public async Task PushNotificationStatusAsync_UserIdProvidedClientTypeAll_SentToUser(
|
||||
bool organizationIdNull, SutProvider<NotificationHubPushNotificationService> sutProvider,
|
||||
Notification notification, NotificationStatus notificationStatus)
|
||||
{
|
||||
if (organizationIdNull)
|
||||
{
|
||||
notification.OrganizationId = null;
|
||||
}
|
||||
|
||||
notification.ClientType = ClientType.All;
|
||||
var expectedNotification = ToNotificationPushNotification(notification, notificationStatus, null);
|
||||
|
||||
await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus);
|
||||
|
||||
await AssertSendTemplateNotificationAsync(sutProvider, PushType.NotificationStatus,
|
||||
expectedNotification,
|
||||
$"(template:payload_userId:{notification.UserId})");
|
||||
await sutProvider.GetDependency<IInstallationDeviceRepository>()
|
||||
.Received(0)
|
||||
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(ClientType.Browser)]
|
||||
[BitAutoData(ClientType.Desktop)]
|
||||
[BitAutoData(ClientType.Web)]
|
||||
[BitAutoData(ClientType.Mobile)]
|
||||
[NotificationCustomize(false)]
|
||||
public async Task PushNotificationStatusAsync_UserIdProvidedOrganizationIdNullClientTypeNotAll_SentToUser(
|
||||
ClientType clientType, SutProvider<NotificationHubPushNotificationService> sutProvider,
|
||||
Notification notification, NotificationStatus notificationStatus)
|
||||
{
|
||||
notification.OrganizationId = null;
|
||||
notification.ClientType = clientType;
|
||||
var expectedNotification = ToNotificationPushNotification(notification, notificationStatus, null);
|
||||
|
||||
await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus);
|
||||
|
||||
await AssertSendTemplateNotificationAsync(sutProvider, PushType.NotificationStatus,
|
||||
expectedNotification,
|
||||
$"(template:payload_userId:{notification.UserId} && clientType:{clientType})");
|
||||
await sutProvider.GetDependency<IInstallationDeviceRepository>()
|
||||
.Received(0)
|
||||
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(ClientType.Browser)]
|
||||
[BitAutoData(ClientType.Desktop)]
|
||||
[BitAutoData(ClientType.Web)]
|
||||
[BitAutoData(ClientType.Mobile)]
|
||||
[NotificationCustomize(false)]
|
||||
public async Task PushNotificationStatusAsync_UserIdProvidedOrganizationIdProvidedClientTypeNotAll_SentToUser(
|
||||
ClientType clientType, SutProvider<NotificationHubPushNotificationService> sutProvider,
|
||||
Notification notification, NotificationStatus notificationStatus)
|
||||
{
|
||||
notification.ClientType = clientType;
|
||||
var expectedNotification = ToNotificationPushNotification(notification, notificationStatus, null);
|
||||
|
||||
await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus);
|
||||
|
||||
await AssertSendTemplateNotificationAsync(sutProvider, PushType.NotificationStatus,
|
||||
expectedNotification,
|
||||
$"(template:payload_userId:{notification.UserId} && clientType:{clientType})");
|
||||
await sutProvider.GetDependency<IInstallationDeviceRepository>()
|
||||
.Received(0)
|
||||
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
[NotificationCustomize(false)]
|
||||
public async Task PushNotificationStatusAsync_UserIdNullOrganizationIdProvidedClientTypeAll_SentToOrganization(
|
||||
SutProvider<NotificationHubPushNotificationService> sutProvider, Notification notification,
|
||||
NotificationStatus notificationStatus)
|
||||
{
|
||||
notification.UserId = null;
|
||||
notification.ClientType = ClientType.All;
|
||||
var expectedNotification = ToNotificationPushNotification(notification, notificationStatus, null);
|
||||
|
||||
await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus);
|
||||
|
||||
await AssertSendTemplateNotificationAsync(sutProvider, PushType.NotificationStatus,
|
||||
expectedNotification,
|
||||
$"(template:payload && organizationId:{notification.OrganizationId})");
|
||||
await sutProvider.GetDependency<IInstallationDeviceRepository>()
|
||||
.Received(0)
|
||||
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(ClientType.Browser)]
|
||||
[BitAutoData(ClientType.Desktop)]
|
||||
[BitAutoData(ClientType.Web)]
|
||||
[BitAutoData(ClientType.Mobile)]
|
||||
[NotificationCustomize(false)]
|
||||
public async Task
|
||||
PushNotificationStatusAsync_UserIdNullOrganizationIdProvidedClientTypeNotAll_SentToOrganization(
|
||||
ClientType clientType, SutProvider<NotificationHubPushNotificationService> sutProvider,
|
||||
Notification notification, NotificationStatus notificationStatus)
|
||||
{
|
||||
notification.UserId = null;
|
||||
notification.ClientType = clientType;
|
||||
var expectedNotification = ToNotificationPushNotification(notification, notificationStatus, null);
|
||||
|
||||
await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus);
|
||||
|
||||
await AssertSendTemplateNotificationAsync(sutProvider, PushType.NotificationStatus,
|
||||
expectedNotification,
|
||||
$"(template:payload && organizationId:{notification.OrganizationId} && clientType:{clientType})");
|
||||
await sutProvider.GetDependency<IInstallationDeviceRepository>()
|
||||
.Received(0)
|
||||
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData([null])]
|
||||
[BitAutoData(ClientType.All)]
|
||||
public async Task SendPayloadToUserAsync_ClientTypeNullOrAll_SentToUser(ClientType? clientType,
|
||||
SutProvider<NotificationHubPushNotificationService> sutProvider, Guid userId, PushType pushType, string payload,
|
||||
string identifier)
|
||||
{
|
||||
await sutProvider.Sut.SendPayloadToUserAsync(userId.ToString(), pushType, payload, identifier, null,
|
||||
clientType);
|
||||
|
||||
await AssertSendTemplateNotificationAsync(sutProvider, pushType, payload,
|
||||
$"(template:payload_userId:{userId} && !deviceIdentifier:{identifier})");
|
||||
await sutProvider.GetDependency<IInstallationDeviceRepository>()
|
||||
.Received(0)
|
||||
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(ClientType.Browser)]
|
||||
[BitAutoData(ClientType.Desktop)]
|
||||
[BitAutoData(ClientType.Mobile)]
|
||||
[BitAutoData(ClientType.Web)]
|
||||
public async Task SendPayloadToUserAsync_ClientTypeExplicit_SentToUserAndClientType(ClientType clientType,
|
||||
SutProvider<NotificationHubPushNotificationService> sutProvider, Guid userId, PushType pushType, string payload,
|
||||
string identifier)
|
||||
{
|
||||
await sutProvider.Sut.SendPayloadToUserAsync(userId.ToString(), pushType, payload, identifier, null,
|
||||
clientType);
|
||||
|
||||
await AssertSendTemplateNotificationAsync(sutProvider, pushType, payload,
|
||||
$"(template:payload_userId:{userId} && !deviceIdentifier:{identifier} && clientType:{clientType})");
|
||||
await sutProvider.GetDependency<IInstallationDeviceRepository>()
|
||||
.Received(0)
|
||||
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData([null])]
|
||||
[BitAutoData(ClientType.All)]
|
||||
public async Task SendPayloadToOrganizationAsync_ClientTypeNullOrAll_SentToOrganization(ClientType? clientType,
|
||||
SutProvider<NotificationHubPushNotificationService> sutProvider, Guid organizationId, PushType pushType,
|
||||
string payload, string identifier)
|
||||
{
|
||||
await sutProvider.Sut.SendPayloadToOrganizationAsync(organizationId.ToString(), pushType, payload, identifier,
|
||||
null, clientType);
|
||||
|
||||
await AssertSendTemplateNotificationAsync(sutProvider, pushType, payload,
|
||||
$"(template:payload && organizationId:{organizationId} && !deviceIdentifier:{identifier})");
|
||||
await sutProvider.GetDependency<IInstallationDeviceRepository>()
|
||||
.Received(0)
|
||||
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(ClientType.Browser)]
|
||||
[BitAutoData(ClientType.Desktop)]
|
||||
[BitAutoData(ClientType.Mobile)]
|
||||
[BitAutoData(ClientType.Web)]
|
||||
public async Task SendPayloadToOrganizationAsync_ClientTypeExplicit_SentToOrganizationAndClientType(
|
||||
ClientType clientType, SutProvider<NotificationHubPushNotificationService> sutProvider, Guid organizationId,
|
||||
PushType pushType, string payload, string identifier)
|
||||
{
|
||||
await sutProvider.Sut.SendPayloadToOrganizationAsync(organizationId.ToString(), pushType, payload, identifier,
|
||||
null, clientType);
|
||||
|
||||
await AssertSendTemplateNotificationAsync(sutProvider, pushType, payload,
|
||||
$"(template:payload && organizationId:{organizationId} && !deviceIdentifier:{identifier} && clientType:{clientType})");
|
||||
await sutProvider.GetDependency<IInstallationDeviceRepository>()
|
||||
.Received(0)
|
||||
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData([null])]
|
||||
[BitAutoData(ClientType.All)]
|
||||
public async Task SendPayloadToInstallationAsync_ClientTypeNullOrAll_SentToInstallation(ClientType? clientType,
|
||||
SutProvider<NotificationHubPushNotificationService> sutProvider, Guid installationId, PushType pushType,
|
||||
string payload, string identifier)
|
||||
{
|
||||
await sutProvider.Sut.SendPayloadToInstallationAsync(installationId.ToString(), pushType, payload, identifier,
|
||||
null, clientType);
|
||||
|
||||
await AssertSendTemplateNotificationAsync(sutProvider, pushType, payload,
|
||||
$"(template:payload && installationId:{installationId} && !deviceIdentifier:{identifier})");
|
||||
await sutProvider.GetDependency<IInstallationDeviceRepository>()
|
||||
.Received(0)
|
||||
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData(ClientType.Browser)]
|
||||
[BitAutoData(ClientType.Desktop)]
|
||||
[BitAutoData(ClientType.Mobile)]
|
||||
[BitAutoData(ClientType.Web)]
|
||||
public async Task SendPayloadToInstallationAsync_ClientTypeExplicit_SentToInstallationAndClientType(
|
||||
ClientType clientType, SutProvider<NotificationHubPushNotificationService> sutProvider, Guid installationId,
|
||||
PushType pushType, string payload, string identifier)
|
||||
{
|
||||
await sutProvider.Sut.SendPayloadToInstallationAsync(installationId.ToString(), pushType, payload, identifier,
|
||||
null, clientType);
|
||||
|
||||
await AssertSendTemplateNotificationAsync(sutProvider, pushType, payload,
|
||||
$"(template:payload && installationId:{installationId} && !deviceIdentifier:{identifier} && clientType:{clientType})");
|
||||
await sutProvider.GetDependency<IInstallationDeviceRepository>()
|
||||
.Received(0)
|
||||
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PushSyncCipherCreateAsync_SendExpectedData()
|
||||
{
|
||||
@ -1066,7 +588,7 @@ public class NotificationHubPushNotificationServiceTests
|
||||
);
|
||||
}
|
||||
|
||||
private async Task VerifyNotificationAsync(Func<NotificationHubPushNotificationService, Task> test,
|
||||
private async Task VerifyNotificationAsync(Func<IPushNotificationService, Task> test,
|
||||
PushType type, JsonNode expectedPayload, string tag)
|
||||
{
|
||||
var installationDeviceRepository = Substitute.For<IInstallationDeviceRepository>();
|
||||
@ -1104,12 +626,11 @@ public class NotificationHubPushNotificationServiceTests
|
||||
notificationHubPool,
|
||||
httpContextAccessor,
|
||||
NullLogger<NotificationHubPushNotificationService>.Instance,
|
||||
globalSettings,
|
||||
fakeTimeProvider
|
||||
globalSettings
|
||||
);
|
||||
|
||||
// Act
|
||||
await test(sut);
|
||||
await test(new EngineWrapper(sut, fakeTimeProvider, _installationId));
|
||||
|
||||
// Assert
|
||||
var calls = notificationHubProxy.ReceivedCalls();
|
||||
|
@ -9,14 +9,11 @@ using Bit.Core.Enums;
|
||||
using Bit.Core.Models;
|
||||
using Bit.Core.NotificationCenter.Entities;
|
||||
using Bit.Core.NotificationCenter.Enums;
|
||||
using Bit.Core.Platform.Push;
|
||||
using Bit.Core.Platform.Push.Internal;
|
||||
using Bit.Core.Settings;
|
||||
using Bit.Core.Test.AutoFixture;
|
||||
using Bit.Core.Test.AutoFixture.CurrentContextFixtures;
|
||||
using Bit.Core.Test.NotificationCenter.AutoFixture;
|
||||
using Bit.Core.Tools.Entities;
|
||||
using Bit.Core.Vault.Entities;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@ -42,96 +39,6 @@ public class AzureQueuePushNotificationServiceTests
|
||||
_fakeTimeProvider.SetUtcNow(DateTime.UtcNow);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
[NotificationCustomize]
|
||||
[CurrentContextCustomize]
|
||||
public async Task PushNotificationAsync_NotificationGlobal_Sent(
|
||||
SutProvider<AzureQueuePushNotificationService> sutProvider, Notification notification, Guid deviceIdentifier,
|
||||
ICurrentContext currentContext, Guid installationId)
|
||||
{
|
||||
currentContext.DeviceIdentifier.Returns(deviceIdentifier.ToString());
|
||||
sutProvider.GetDependency<IHttpContextAccessor>().HttpContext!.RequestServices
|
||||
.GetService(Arg.Any<Type>()).Returns(currentContext);
|
||||
sutProvider.GetDependency<IGlobalSettings>().Installation.Id = installationId;
|
||||
|
||||
await sutProvider.Sut.PushNotificationAsync(notification);
|
||||
|
||||
await sutProvider.GetDependency<QueueClient>().Received(1)
|
||||
.SendMessageAsync(Arg.Is<string>(message =>
|
||||
MatchMessage(PushType.Notification, message,
|
||||
new NotificationPushNotificationEquals(notification, null, installationId),
|
||||
deviceIdentifier.ToString())));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
[NotificationCustomize(false)]
|
||||
[CurrentContextCustomize]
|
||||
public async Task PushNotificationAsync_NotificationNotGlobal_Sent(
|
||||
SutProvider<AzureQueuePushNotificationService> sutProvider, Notification notification, Guid deviceIdentifier,
|
||||
ICurrentContext currentContext, Guid installationId)
|
||||
{
|
||||
currentContext.DeviceIdentifier.Returns(deviceIdentifier.ToString());
|
||||
sutProvider.GetDependency<IHttpContextAccessor>().HttpContext!.RequestServices
|
||||
.GetService(Arg.Any<Type>()).Returns(currentContext);
|
||||
sutProvider.GetDependency<IGlobalSettings>().Installation.Id = installationId;
|
||||
|
||||
await sutProvider.Sut.PushNotificationAsync(notification);
|
||||
|
||||
await sutProvider.GetDependency<QueueClient>().Received(1)
|
||||
.SendMessageAsync(Arg.Is<string>(message =>
|
||||
MatchMessage(PushType.Notification, message,
|
||||
new NotificationPushNotificationEquals(notification, null, null),
|
||||
deviceIdentifier.ToString())));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
[NotificationCustomize]
|
||||
[NotificationStatusCustomize]
|
||||
[CurrentContextCustomize]
|
||||
public async Task PushNotificationStatusAsync_NotificationGlobal_Sent(
|
||||
SutProvider<AzureQueuePushNotificationService> sutProvider, Notification notification, Guid deviceIdentifier,
|
||||
ICurrentContext currentContext, NotificationStatus notificationStatus, Guid installationId)
|
||||
{
|
||||
currentContext.DeviceIdentifier.Returns(deviceIdentifier.ToString());
|
||||
sutProvider.GetDependency<IHttpContextAccessor>().HttpContext!.RequestServices
|
||||
.GetService(Arg.Any<Type>()).Returns(currentContext);
|
||||
sutProvider.GetDependency<IGlobalSettings>().Installation.Id = installationId;
|
||||
|
||||
await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus);
|
||||
|
||||
await sutProvider.GetDependency<QueueClient>().Received(1)
|
||||
.SendMessageAsync(Arg.Is<string>(message =>
|
||||
MatchMessage(PushType.NotificationStatus, message,
|
||||
new NotificationPushNotificationEquals(notification, notificationStatus, installationId),
|
||||
deviceIdentifier.ToString())));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
[NotificationCustomize(false)]
|
||||
[NotificationStatusCustomize]
|
||||
[CurrentContextCustomize]
|
||||
public async Task PushNotificationStatusAsync_NotificationNotGlobal_Sent(
|
||||
SutProvider<AzureQueuePushNotificationService> sutProvider, Notification notification, Guid deviceIdentifier,
|
||||
ICurrentContext currentContext, NotificationStatus notificationStatus, Guid installationId)
|
||||
{
|
||||
currentContext.DeviceIdentifier.Returns(deviceIdentifier.ToString());
|
||||
sutProvider.GetDependency<IHttpContextAccessor>().HttpContext!.RequestServices
|
||||
.GetService(Arg.Any<Type>()).Returns(currentContext);
|
||||
sutProvider.GetDependency<IGlobalSettings>().Installation.Id = installationId;
|
||||
|
||||
await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus);
|
||||
|
||||
await sutProvider.GetDependency<QueueClient>().Received(1)
|
||||
.SendMessageAsync(Arg.Is<string>(message =>
|
||||
MatchMessage(PushType.NotificationStatus, message,
|
||||
new NotificationPushNotificationEquals(notification, notificationStatus, null),
|
||||
deviceIdentifier.ToString())));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("6a5bbe1b-cf16-49a6-965f-5c2eac56a531", null)]
|
||||
[InlineData(null, "b9a3fcb4-2447-45c1-aad2-24de43c88c44")]
|
||||
@ -844,7 +751,7 @@ public class AzureQueuePushNotificationServiceTests
|
||||
// );
|
||||
// }
|
||||
|
||||
private async Task VerifyNotificationAsync(Func<AzureQueuePushNotificationService, Task> test, JsonNode expectedMessage)
|
||||
private async Task VerifyNotificationAsync(Func<IPushNotificationService, Task> test, JsonNode expectedMessage)
|
||||
{
|
||||
var queueClient = Substitute.For<QueueClient>();
|
||||
|
||||
@ -872,7 +779,7 @@ public class AzureQueuePushNotificationServiceTests
|
||||
_fakeTimeProvider
|
||||
);
|
||||
|
||||
await test(sut);
|
||||
await test(new EngineWrapper(sut, _fakeTimeProvider, _globalSettings.Installation.Id));
|
||||
|
||||
// Hoist equality checker outside the expression so that we
|
||||
// can more easily place a breakpoint
|
||||
|
@ -1,98 +1,8 @@
|
||||
#nullable enable
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.NotificationCenter.Entities;
|
||||
using Bit.Core.Platform.Push;
|
||||
using Bit.Core.Platform.Push.Internal;
|
||||
using Bit.Core.Test.NotificationCenter.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Platform.Push.Services;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class MultiServicePushNotificationServiceTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
[NotificationCustomize]
|
||||
public async Task PushNotificationAsync_Notification_Sent(
|
||||
SutProvider<MultiServicePushNotificationService> sutProvider, Notification notification)
|
||||
{
|
||||
await sutProvider.Sut.PushNotificationAsync(notification);
|
||||
|
||||
await sutProvider.GetDependency<IEnumerable<IPushNotificationService>>()
|
||||
.First()
|
||||
.Received(1)
|
||||
.PushNotificationAsync(notification);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
[NotificationCustomize]
|
||||
[NotificationStatusCustomize]
|
||||
public async Task PushNotificationStatusAsync_Notification_Sent(
|
||||
SutProvider<MultiServicePushNotificationService> sutProvider, Notification notification,
|
||||
NotificationStatus notificationStatus)
|
||||
{
|
||||
await sutProvider.Sut.PushNotificationStatusAsync(notification, notificationStatus);
|
||||
|
||||
await sutProvider.GetDependency<IEnumerable<IPushNotificationService>>()
|
||||
.First()
|
||||
.Received(1)
|
||||
.PushNotificationStatusAsync(notification, notificationStatus);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData([null, null])]
|
||||
[BitAutoData(ClientType.All, null)]
|
||||
[BitAutoData([null, "test device id"])]
|
||||
[BitAutoData(ClientType.All, "test device id")]
|
||||
public async Task SendPayloadToUserAsync_Message_Sent(ClientType? clientType, string? deviceId, string userId,
|
||||
PushType type, object payload, string identifier, SutProvider<MultiServicePushNotificationService> sutProvider)
|
||||
{
|
||||
await sutProvider.Sut.SendPayloadToUserAsync(userId, type, payload, identifier, deviceId, clientType);
|
||||
|
||||
await sutProvider.GetDependency<IEnumerable<IPushNotificationService>>()
|
||||
.First()
|
||||
.Received(1)
|
||||
.SendPayloadToUserAsync(userId, type, payload, identifier, deviceId, clientType);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData([null, null])]
|
||||
[BitAutoData(ClientType.All, null)]
|
||||
[BitAutoData([null, "test device id"])]
|
||||
[BitAutoData(ClientType.All, "test device id")]
|
||||
public async Task SendPayloadToOrganizationAsync_Message_Sent(ClientType? clientType, string? deviceId,
|
||||
string organizationId, PushType type, object payload, string identifier,
|
||||
SutProvider<MultiServicePushNotificationService> sutProvider)
|
||||
{
|
||||
await sutProvider.Sut.SendPayloadToOrganizationAsync(organizationId, type, payload, identifier, deviceId,
|
||||
clientType);
|
||||
|
||||
await sutProvider.GetDependency<IEnumerable<IPushNotificationService>>()
|
||||
.First()
|
||||
.Received(1)
|
||||
.SendPayloadToOrganizationAsync(organizationId, type, payload, identifier, deviceId, clientType);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData([null, null])]
|
||||
[BitAutoData(ClientType.All, null)]
|
||||
[BitAutoData([null, "test device id"])]
|
||||
[BitAutoData(ClientType.All, "test device id")]
|
||||
public async Task SendPayloadToInstallationAsync_Message_Sent(ClientType? clientType, string? deviceId,
|
||||
string installationId, PushType type, object payload, string identifier,
|
||||
SutProvider<MultiServicePushNotificationService> sutProvider)
|
||||
{
|
||||
await sutProvider.Sut.SendPayloadToInstallationAsync(installationId, type, payload, identifier, deviceId,
|
||||
clientType);
|
||||
|
||||
await sutProvider.GetDependency<IEnumerable<IPushNotificationService>>()
|
||||
.First()
|
||||
.Received(1)
|
||||
.SendPayloadToInstallationAsync(installationId, type, payload, identifier, deviceId, clientType);
|
||||
}
|
||||
// TODO: Can add a couple tests here
|
||||
}
|
||||
|
@ -19,14 +19,13 @@ public class NotificationsApiPushNotificationServiceTests : PushTestBase
|
||||
|
||||
protected override string ExpectedClientUrl() => "https://localhost:7777/send";
|
||||
|
||||
protected override IPushNotificationService CreateService()
|
||||
protected override IPushEngine CreateService()
|
||||
{
|
||||
return new NotificationsApiPushNotificationService(
|
||||
HttpClientFactory,
|
||||
GlobalSettings,
|
||||
HttpContextAccessor,
|
||||
NullLogger<NotificationsApiPushNotificationService>.Instance,
|
||||
FakeTimeProvider
|
||||
NullLogger<NotificationsApiPushNotificationService>.Instance
|
||||
);
|
||||
}
|
||||
|
||||
@ -221,7 +220,7 @@ public class NotificationsApiPushNotificationServiceTests : PushTestBase
|
||||
["UserId"] = send.UserId,
|
||||
["RevisionDate"] = send.RevisionDate,
|
||||
},
|
||||
["ContextId"] = null,
|
||||
["ContextId"] = DeviceIdentifier,
|
||||
};
|
||||
}
|
||||
|
||||
@ -236,7 +235,7 @@ public class NotificationsApiPushNotificationServiceTests : PushTestBase
|
||||
["UserId"] = send.UserId,
|
||||
["RevisionDate"] = send.RevisionDate,
|
||||
},
|
||||
["ContextId"] = null,
|
||||
["ContextId"] = DeviceIdentifier,
|
||||
};
|
||||
}
|
||||
|
||||
@ -251,7 +250,7 @@ public class NotificationsApiPushNotificationServiceTests : PushTestBase
|
||||
["UserId"] = send.UserId,
|
||||
["RevisionDate"] = send.RevisionDate,
|
||||
},
|
||||
["ContextId"] = null,
|
||||
["ContextId"] = DeviceIdentifier,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -15,11 +15,28 @@ using Bit.Core.Tools.Entities;
|
||||
using Bit.Core.Vault.Entities;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Time.Testing;
|
||||
using NSubstitute;
|
||||
using RichardSzalay.MockHttp;
|
||||
using Xunit;
|
||||
|
||||
public class EngineWrapper(IPushEngine pushEngine, FakeTimeProvider fakeTimeProvider, Guid installationId) : IPushNotificationService
|
||||
{
|
||||
public Guid InstallationId { get; } = installationId;
|
||||
|
||||
public TimeProvider TimeProvider { get; } = fakeTimeProvider;
|
||||
|
||||
public ILogger Logger => NullLogger<EngineWrapper>.Instance;
|
||||
|
||||
public Task PushAsync<T>(PushNotification<T> pushNotification) where T : class
|
||||
=> pushEngine.PushAsync(pushNotification);
|
||||
|
||||
public Task PushCipherAsync(Cipher cipher, PushType pushType, IEnumerable<Guid>? collectionIds)
|
||||
=> pushEngine.PushCipherAsync(cipher, pushType, collectionIds);
|
||||
}
|
||||
|
||||
public abstract class PushTestBase
|
||||
{
|
||||
protected static readonly string DeviceIdentifier = "test_device_identifier";
|
||||
@ -51,7 +68,7 @@ public abstract class PushTestBase
|
||||
FakeTimeProvider.SetUtcNow(DateTimeOffset.UtcNow);
|
||||
}
|
||||
|
||||
protected abstract IPushNotificationService CreateService();
|
||||
protected abstract IPushEngine CreateService();
|
||||
|
||||
protected abstract string ExpectedClientUrl();
|
||||
|
||||
@ -480,7 +497,7 @@ public abstract class PushTestBase
|
||||
})
|
||||
.Respond(HttpStatusCode.OK);
|
||||
|
||||
await test(CreateService());
|
||||
await test(new EngineWrapper(CreateService(), FakeTimeProvider, GlobalSettings.Installation.Id));
|
||||
|
||||
Assert.NotNull(actualNode);
|
||||
|
||||
|
@ -4,8 +4,8 @@ using System.Text.Json.Nodes;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Auth.Entities;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.NotificationCenter.Entities;
|
||||
using Bit.Core.Platform.Push;
|
||||
using Bit.Core.Platform.Push.Internal;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Settings;
|
||||
@ -14,7 +14,6 @@ using Bit.Core.Vault.Entities;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Time.Testing;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Core.Test.Platform.Push.Services;
|
||||
|
||||
@ -38,47 +37,19 @@ public class RelayPushNotificationServiceTests : PushTestBase
|
||||
GlobalSettings.Installation.IdentityUri = "https://localhost:8888";
|
||||
}
|
||||
|
||||
protected override RelayPushNotificationService CreateService()
|
||||
protected override IPushEngine CreateService()
|
||||
{
|
||||
return new RelayPushNotificationService(
|
||||
HttpClientFactory,
|
||||
_deviceRepository,
|
||||
GlobalSettings,
|
||||
HttpContextAccessor,
|
||||
NullLogger<RelayPushNotificationService>.Instance,
|
||||
FakeTimeProvider
|
||||
NullLogger<RelayPushNotificationService>.Instance
|
||||
);
|
||||
}
|
||||
|
||||
protected override string ExpectedClientUrl() => "https://localhost:7777/push/send";
|
||||
|
||||
[Fact]
|
||||
public async Task SendPayloadToInstallationAsync_ThrowsNotImplementedException()
|
||||
{
|
||||
var sut = CreateService();
|
||||
await Assert.ThrowsAsync<NotImplementedException>(
|
||||
async () => await sut.SendPayloadToInstallationAsync("installation_id", PushType.AuthRequest, new { }, null)
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SendPayloadToUserAsync_ThrowsNotImplementedException()
|
||||
{
|
||||
var sut = CreateService();
|
||||
await Assert.ThrowsAsync<NotImplementedException>(
|
||||
async () => await sut.SendPayloadToUserAsync("user_id", PushType.AuthRequest, new { }, null)
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SendPayloadToOrganizationAsync_ThrowsNotImplementedException()
|
||||
{
|
||||
var sut = CreateService();
|
||||
await Assert.ThrowsAsync<NotImplementedException>(
|
||||
async () => await sut.SendPayloadToOrganizationAsync("organization_id", PushType.AuthRequest, new { }, null)
|
||||
);
|
||||
}
|
||||
|
||||
protected override JsonNode GetPushSyncCipherCreatePayload(Cipher cipher, Guid collectionIds)
|
||||
{
|
||||
return new JsonObject
|
||||
|
@ -25,7 +25,8 @@ public class SendGridMailDeliveryServiceTests : IDisposable
|
||||
{
|
||||
Mail =
|
||||
{
|
||||
SendGridApiKey = "SendGridApiKey"
|
||||
SendGridApiKey = "SendGridApiKey",
|
||||
SendGridApiHost = "https://api.sendgrid.com"
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -62,7 +62,9 @@ public class ImportCiphersAsyncCommandTests
|
||||
|
||||
sutProvider.GetDependency<IPolicyRequirementQuery>()
|
||||
.GetAsync<PersonalOwnershipPolicyRequirement>(importingUserId)
|
||||
.Returns(new PersonalOwnershipPolicyRequirement { DisablePersonalOwnership = false });
|
||||
.Returns(new PersonalOwnershipPolicyRequirement(
|
||||
PersonalOwnershipState.Allowed,
|
||||
[]));
|
||||
|
||||
sutProvider.GetDependency<IFolderRepository>()
|
||||
.GetManyByUserIdAsync(importingUserId)
|
||||
@ -116,7 +118,9 @@ public class ImportCiphersAsyncCommandTests
|
||||
|
||||
sutProvider.GetDependency<IPolicyRequirementQuery>()
|
||||
.GetAsync<PersonalOwnershipPolicyRequirement>(userId)
|
||||
.Returns(new PersonalOwnershipPolicyRequirement { DisablePersonalOwnership = true });
|
||||
.Returns(new PersonalOwnershipPolicyRequirement(
|
||||
PersonalOwnershipState.Restricted,
|
||||
[Guid.NewGuid()]));
|
||||
|
||||
var folderRelationships = new List<KeyValuePair<int, int>>();
|
||||
|
||||
|
@ -10,10 +10,10 @@ using Bit.Core.Tools.Entities;
|
||||
using Bit.Core.Tools.Enums;
|
||||
using Bit.Core.Tools.Models.Data;
|
||||
using Bit.Core.Tools.Repositories;
|
||||
using Bit.Core.Tools.SendFeatures;
|
||||
using Bit.Core.Tools.SendFeatures.Commands;
|
||||
using Bit.Core.Tools.Services;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NSubstitute;
|
||||
using NSubstitute.ExceptionExtensions;
|
||||
using Xunit;
|
||||
@ -35,6 +35,8 @@ public class NonAnonymousSendCommandTests
|
||||
private readonly ISendCoreHelperService _sendCoreHelperService;
|
||||
private readonly NonAnonymousSendCommand _nonAnonymousSendCommand;
|
||||
|
||||
private readonly ILogger<NonAnonymousSendCommand> _logger;
|
||||
|
||||
public NonAnonymousSendCommandTests()
|
||||
{
|
||||
_sendRepository = Substitute.For<ISendRepository>();
|
||||
@ -45,6 +47,7 @@ public class NonAnonymousSendCommandTests
|
||||
_sendValidationService = Substitute.For<ISendValidationService>();
|
||||
_currentContext = Substitute.For<ICurrentContext>();
|
||||
_sendCoreHelperService = Substitute.For<ISendCoreHelperService>();
|
||||
_logger = Substitute.For<ILogger<NonAnonymousSendCommand>>();
|
||||
|
||||
_nonAnonymousSendCommand = new NonAnonymousSendCommand(
|
||||
_sendRepository,
|
||||
@ -52,7 +55,8 @@ public class NonAnonymousSendCommandTests
|
||||
_pushNotificationService,
|
||||
_sendAuthorizationService,
|
||||
_sendValidationService,
|
||||
_sendCoreHelperService
|
||||
_sendCoreHelperService,
|
||||
_logger
|
||||
);
|
||||
}
|
||||
|
||||
@ -652,11 +656,11 @@ public class NonAnonymousSendCommandTests
|
||||
UserId = userId
|
||||
};
|
||||
var fileData = new SendFileData();
|
||||
var fileLength = 15L * 1024L * 1024L * 1024L; // 15GB
|
||||
var fileLength = 15L * 1024L * 1024L; // 15 MB
|
||||
|
||||
// Configure validation service to return large but insufficient storage (10GB for self-hosted non-premium)
|
||||
// Configure validation service to return insufficient storage
|
||||
_sendValidationService.StorageRemainingForSendAsync(send)
|
||||
.Returns(10L * 1024L * 1024L * 1024L); // 10GB remaining (self-hosted default)
|
||||
.Returns(10L * 1024L * 1024L); // 10 MB remaining
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
@ -687,11 +691,40 @@ public class NonAnonymousSendCommandTests
|
||||
UserId = userId
|
||||
};
|
||||
var fileData = new SendFileData();
|
||||
var fileLength = 2L * 1024L * 1024L * 1024L; // 2GB
|
||||
var fileLength = 2L * 1024L * 1024L * 1024L; // 2MB
|
||||
|
||||
// Configure validation service to return 1GB storage (cloud non-premium default)
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
_nonAnonymousSendCommand.SaveFileSendAsync(send, fileData, fileLength));
|
||||
|
||||
Assert.Contains("Max file size is ", exception.Message);
|
||||
|
||||
// Verify no further methods were called
|
||||
await _sendValidationService.DidNotReceive().StorageRemainingForSendAsync(Arg.Any<Send>());
|
||||
await _sendRepository.DidNotReceive().CreateAsync(Arg.Any<Send>());
|
||||
await _sendRepository.DidNotReceive().UpsertAsync(Arg.Any<Send>());
|
||||
await _sendFileStorageService.DidNotReceive().GetSendFileUploadUrlAsync(Arg.Any<Send>(), Arg.Any<string>());
|
||||
await _pushNotificationService.DidNotReceive().PushSyncSendCreateAsync(Arg.Any<Send>());
|
||||
await _pushNotificationService.DidNotReceive().PushSyncSendUpdateAsync(Arg.Any<Send>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SaveFileSendAsync_UserCanAccessPremium_IsNotPremium_IsNotSelfHosted_NotEnoughSpace_ThrowsBadRequest()
|
||||
{
|
||||
// Arrange
|
||||
var userId = Guid.NewGuid();
|
||||
var send = new Send
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Type = SendType.File,
|
||||
UserId = userId
|
||||
};
|
||||
var fileData = new SendFileData();
|
||||
var fileLength = 2L * 1024L * 1024L; // 2MB
|
||||
|
||||
// Configure validation service to return 1 MB storage remaining
|
||||
_sendValidationService.StorageRemainingForSendAsync(send)
|
||||
.Returns(1L * 1024L * 1024L * 1024L); // 1GB remaining (cloud default)
|
||||
.Returns(1L * 1024L * 1024L);
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
@ -756,7 +789,7 @@ public class NonAnonymousSendCommandTests
|
||||
UserId = null
|
||||
};
|
||||
var fileData = new SendFileData();
|
||||
var fileLength = 2L * 1024L * 1024L * 1024L; // 2GB
|
||||
var fileLength = 2L * 1024L * 1024L; // 2 MB
|
||||
|
||||
// Configure validation service to throw BadRequest when checking storage for org without storage
|
||||
_sendValidationService.StorageRemainingForSendAsync(send)
|
||||
@ -792,11 +825,10 @@ public class NonAnonymousSendCommandTests
|
||||
UserId = null
|
||||
};
|
||||
var fileData = new SendFileData();
|
||||
var fileLength = 2L * 1024L * 1024L * 1024L; // 2GB
|
||||
var fileLength = 2L * 1024L * 1024L; // 2 MB
|
||||
|
||||
// Configure validation service to return 1GB storage (org's max storage limit)
|
||||
_sendValidationService.StorageRemainingForSendAsync(send)
|
||||
.Returns(1L * 1024L * 1024L * 1024L); // 1GB remaining
|
||||
.Returns(1L * 1024L * 1024L); // 1 MB remaining
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(() =>
|
||||
@ -980,7 +1012,7 @@ public class NonAnonymousSendCommandTests
|
||||
};
|
||||
|
||||
// Setup validation to succeed
|
||||
_sendFileStorageService.ValidateFileAsync(send, sendFileData.Id, sendFileData.Size, SendFileSettingHelper.FILE_SIZE_LEEWAY).Returns((true, sendFileData.Size));
|
||||
_sendFileStorageService.ValidateFileAsync(send, sendFileData.Id, Arg.Any<long>(), Arg.Any<long>()).Returns((true, sendFileData.Size));
|
||||
|
||||
// Act
|
||||
await _nonAnonymousSendCommand.UploadFileToExistingSendAsync(stream, send);
|
||||
@ -1014,7 +1046,7 @@ public class NonAnonymousSendCommandTests
|
||||
Data = JsonSerializer.Serialize(sendFileData)
|
||||
};
|
||||
|
||||
_sendFileStorageService.ValidateFileAsync(send, sendFileData.Id, sendFileData.Size, SendFileSettingHelper.FILE_SIZE_LEEWAY).Returns((true, sendFileData.Size));
|
||||
_sendFileStorageService.ValidateFileAsync(send, sendFileData.Id, Arg.Any<long>(), Arg.Any<long>()).Returns((true, sendFileData.Size));
|
||||
|
||||
// Act
|
||||
await _nonAnonymousSendCommand.UploadFileToExistingSendAsync(stream, send);
|
||||
|
@ -171,7 +171,9 @@ public class CipherServiceTests
|
||||
|
||||
sutProvider.GetDependency<IPolicyRequirementQuery>()
|
||||
.GetAsync<PersonalOwnershipPolicyRequirement>(savingUserId)
|
||||
.Returns(new PersonalOwnershipPolicyRequirement { DisablePersonalOwnership = true });
|
||||
.Returns(new PersonalOwnershipPolicyRequirement(
|
||||
PersonalOwnershipState.Restricted,
|
||||
[Guid.NewGuid()]));
|
||||
|
||||
var exception = await Assert.ThrowsAsync<BadRequestException>(
|
||||
() => sutProvider.Sut.SaveDetailsAsync(cipher, savingUserId, null));
|
||||
@ -195,7 +197,9 @@ public class CipherServiceTests
|
||||
|
||||
sutProvider.GetDependency<IPolicyRequirementQuery>()
|
||||
.GetAsync<PersonalOwnershipPolicyRequirement>(savingUserId)
|
||||
.Returns(new PersonalOwnershipPolicyRequirement { DisablePersonalOwnership = false });
|
||||
.Returns(new PersonalOwnershipPolicyRequirement(
|
||||
PersonalOwnershipState.Allowed,
|
||||
[]));
|
||||
|
||||
await sutProvider.Sut.SaveDetailsAsync(cipher, savingUserId, null);
|
||||
|
||||
|
Reference in New Issue
Block a user