mirror of
https://github.com/bitwarden/server.git
synced 2025-06-30 23:52:50 -05:00
[PM-10563] Notification Center API (#4852)
* PM-10563: Notification Center API * PM-10563: continuation token hack * PM-10563: Resolving merge conflicts * PM-10563: Unit Tests * PM-10563: Paging simplification by page number and size in database * PM-10563: Request validation * PM-10563: Read, Deleted status filters change * PM-10563: Plural name for tests * PM-10563: Request validation to always for int type * PM-10563: Continuation Token returns null on response when no more records available * PM-10563: Integration tests for GET * PM-10563: Mark notification read, deleted commands date typos fix * PM-10563: Integration tests for PATCH read, deleted * PM-10563: Request, Response models tests * PM-10563: EditorConfig compliance * PM-10563: Extracting to const * PM-10563: Update db migration script date * PM-10563: Update migration script date
This commit is contained in:
@ -0,0 +1,582 @@
|
||||
using System.Net;
|
||||
using Bit.Api.IntegrationTest.Factories;
|
||||
using Bit.Api.IntegrationTest.Helpers;
|
||||
using Bit.Api.Models.Response;
|
||||
using Bit.Api.NotificationCenter.Models.Response;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Api;
|
||||
using Bit.Core.NotificationCenter.Entities;
|
||||
using Bit.Core.NotificationCenter.Enums;
|
||||
using Bit.Core.NotificationCenter.Repositories;
|
||||
using Bit.Core.Repositories;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Api.IntegrationTest.NotificationCenter.Controllers;
|
||||
|
||||
public class NotificationsControllerTests : IClassFixture<ApiApplicationFactory>, IAsyncLifetime
|
||||
{
|
||||
private static readonly string _mockEncryptedBody =
|
||||
"2.AOs41Hd8OQiCPXjyJKCiDA==|O6OHgt2U2hJGBSNGnimJmg==|iD33s8B69C8JhYYhSa4V1tArjvLr8eEaGqOV7BRo5Jk=";
|
||||
|
||||
private static readonly string _mockEncryptedTitle =
|
||||
"2.06CDSJjTZaigYHUuswIq5A==|trxgZl2RCkYrrmCvGE9WNA==|w5p05eI5wsaYeSyWtsAPvBX63vj798kIMxBTfSB0BQg=";
|
||||
|
||||
private static readonly Random _random = new();
|
||||
|
||||
private static TimeSpan OneMinuteTimeSpan => TimeSpan.FromMinutes(1);
|
||||
|
||||
private readonly HttpClient _client;
|
||||
private readonly ApiApplicationFactory _factory;
|
||||
private readonly LoginHelper _loginHelper;
|
||||
private readonly INotificationRepository _notificationRepository;
|
||||
private readonly INotificationStatusRepository _notificationStatusRepository;
|
||||
private readonly IUserRepository _userRepository;
|
||||
private Organization _organization = null!;
|
||||
private OrganizationUser _organizationUserOwner = null!;
|
||||
private string _ownerEmail = null!;
|
||||
private List<(Notification, NotificationStatus?)> _notificationsWithStatuses = null!;
|
||||
|
||||
public NotificationsControllerTests(ApiApplicationFactory factory)
|
||||
{
|
||||
_factory = factory;
|
||||
_client = factory.CreateClient();
|
||||
_loginHelper = new LoginHelper(_factory, _client);
|
||||
_notificationRepository = _factory.GetService<INotificationRepository>();
|
||||
_notificationStatusRepository = _factory.GetService<INotificationStatusRepository>();
|
||||
_userRepository = _factory.GetService<IUserRepository>();
|
||||
}
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
// Create the owner account
|
||||
_ownerEmail = $"integration-test{Guid.NewGuid()}@bitwarden.com";
|
||||
await _factory.LoginWithNewAccount(_ownerEmail);
|
||||
|
||||
// Create the organization
|
||||
(_organization, _organizationUserOwner) = await OrganizationTestHelpers.SignUpAsync(_factory,
|
||||
plan: PlanType.EnterpriseAnnually, ownerEmail: _ownerEmail, passwordManagerSeats: 10,
|
||||
paymentMethod: PaymentMethodType.Card);
|
||||
|
||||
_notificationsWithStatuses = await CreateNotificationsWithStatusesAsync();
|
||||
}
|
||||
|
||||
public Task DisposeAsync()
|
||||
{
|
||||
_client.Dispose();
|
||||
|
||||
foreach (var (notification, _) in _notificationsWithStatuses)
|
||||
{
|
||||
_notificationRepository.DeleteAsync(notification);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("invalid")]
|
||||
[InlineData("-1")]
|
||||
[InlineData("0")]
|
||||
public async Task ListAsync_RequestValidationContinuationInvalidNumber_BadRequest(string continuationToken)
|
||||
{
|
||||
await _loginHelper.LoginAsync(_ownerEmail);
|
||||
|
||||
var response = await _client.GetAsync($"/notifications?continuationToken={continuationToken}");
|
||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
var result = await response.Content.ReadFromJsonAsync<ErrorResponseModel>();
|
||||
Assert.NotNull(result);
|
||||
Assert.Contains("ContinuationToken", result.ValidationErrors);
|
||||
Assert.Contains("Continuation token must be a positive, non zero integer.",
|
||||
result.ValidationErrors["ContinuationToken"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ListAsync_RequestValidationContinuationTokenMaxLengthExceeded_BadRequest()
|
||||
{
|
||||
await _loginHelper.LoginAsync(_ownerEmail);
|
||||
|
||||
var response = await _client.GetAsync("/notifications?continuationToken=1234567890");
|
||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
var result = await response.Content.ReadFromJsonAsync<ErrorResponseModel>();
|
||||
Assert.NotNull(result);
|
||||
Assert.Contains("ContinuationToken", result.ValidationErrors);
|
||||
Assert.Contains("The field ContinuationToken must be a string with a maximum length of 9.",
|
||||
result.ValidationErrors["ContinuationToken"]);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("9")]
|
||||
[InlineData("1001")]
|
||||
public async Task ListAsync_RequestValidationPageSizeInvalidRange_BadRequest(string pageSize)
|
||||
{
|
||||
await _loginHelper.LoginAsync(_ownerEmail);
|
||||
|
||||
var response = await _client.GetAsync($"/notifications?pageSize={pageSize}");
|
||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
var result = await response.Content.ReadFromJsonAsync<ErrorResponseModel>();
|
||||
Assert.NotNull(result);
|
||||
Assert.Contains("PageSize", result.ValidationErrors);
|
||||
Assert.Contains("The field PageSize must be between 10 and 1000.",
|
||||
result.ValidationErrors["PageSize"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ListAsync_NotLoggedIn_Unauthorized()
|
||||
{
|
||||
var response = await _client.GetAsync("/notifications");
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null, null, "2", 10)]
|
||||
[InlineData(10, null, "2", 10)]
|
||||
[InlineData(10, 2, "3", 10)]
|
||||
[InlineData(10, 3, null, 0)]
|
||||
[InlineData(15, null, "2", 15)]
|
||||
[InlineData(15, 2, null, 5)]
|
||||
[InlineData(20, null, "2", 20)]
|
||||
[InlineData(20, 2, null, 0)]
|
||||
[InlineData(1000, null, null, 20)]
|
||||
public async Task ListAsync_PaginationFilter_ReturnsNextPageOfNotificationsCorrectOrder(
|
||||
int? pageSize, int? pageNumber, string? expectedContinuationToken, int expectedCount)
|
||||
{
|
||||
var pageSizeWithDefault = pageSize ?? 10;
|
||||
|
||||
await _loginHelper.LoginAsync(_ownerEmail);
|
||||
|
||||
var skip = pageNumber == null ? 0 : (pageNumber.Value - 1) * pageSizeWithDefault;
|
||||
|
||||
var notificationsInOrder = _notificationsWithStatuses.OrderByDescending(e => e.Item1.Priority)
|
||||
.ThenByDescending(e => e.Item1.CreationDate)
|
||||
.Skip(skip)
|
||||
.Take(pageSizeWithDefault)
|
||||
.ToList();
|
||||
|
||||
var url = "/notifications";
|
||||
if (pageNumber != null)
|
||||
{
|
||||
url += $"?continuationToken={pageNumber}";
|
||||
}
|
||||
|
||||
if (pageSize != null)
|
||||
{
|
||||
url += url.Contains('?') ? "&" : "?";
|
||||
url += $"pageSize={pageSize}";
|
||||
}
|
||||
|
||||
var response = await _client.GetAsync(url);
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var result = await response.Content.ReadFromJsonAsync<ListResponseModel<NotificationResponseModel>>();
|
||||
Assert.NotNull(result?.Data);
|
||||
Assert.InRange(result.Data.Count(), 0, pageSizeWithDefault);
|
||||
Assert.Equal(expectedCount, notificationsInOrder.Count);
|
||||
Assert.Equal(notificationsInOrder.Count, result.Data.Count());
|
||||
AssertNotificationResponseModels(result.Data, notificationsInOrder);
|
||||
|
||||
Assert.Equal(expectedContinuationToken, result.ContinuationToken);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null, null)]
|
||||
[InlineData(null, false)]
|
||||
[InlineData(null, true)]
|
||||
[InlineData(false, null)]
|
||||
[InlineData(true, null)]
|
||||
[InlineData(false, false)]
|
||||
[InlineData(false, true)]
|
||||
[InlineData(true, false)]
|
||||
[InlineData(true, true)]
|
||||
public async Task ListAsync_ReadStatusDeletedStatusFilter_ReturnsFilteredNotificationsCorrectOrder(
|
||||
bool? readStatusFilter, bool? deletedStatusFilter)
|
||||
{
|
||||
await _loginHelper.LoginAsync(_ownerEmail);
|
||||
var notificationsInOrder = _notificationsWithStatuses.FindAll(e =>
|
||||
(readStatusFilter == null || readStatusFilter == (e.Item2?.ReadDate != null)) &&
|
||||
(deletedStatusFilter == null || deletedStatusFilter == (e.Item2?.DeletedDate != null)))
|
||||
.OrderByDescending(e => e.Item1.Priority)
|
||||
.ThenByDescending(e => e.Item1.CreationDate)
|
||||
.Take(10)
|
||||
.ToList();
|
||||
|
||||
var url = "/notifications";
|
||||
if (readStatusFilter != null)
|
||||
{
|
||||
url += $"?readStatusFilter={readStatusFilter}";
|
||||
}
|
||||
|
||||
if (deletedStatusFilter != null)
|
||||
{
|
||||
url += url.Contains('?') ? "&" : "?";
|
||||
url += $"deletedStatusFilter={deletedStatusFilter}";
|
||||
}
|
||||
|
||||
var response = await _client.GetAsync(url);
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var result = await response.Content.ReadFromJsonAsync<ListResponseModel<NotificationResponseModel>>();
|
||||
Assert.NotNull(result?.Data);
|
||||
Assert.InRange(result.Data.Count(), 0, 10);
|
||||
Assert.Equal(notificationsInOrder.Count, result.Data.Count());
|
||||
AssertNotificationResponseModels(result.Data, notificationsInOrder);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
private async void MarkAsDeletedAsync_NotLoggedIn_Unauthorized()
|
||||
{
|
||||
var url = $"/notifications/{Guid.NewGuid().ToString()}/delete";
|
||||
var response = await _client.PatchAsync(url, new StringContent(""));
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
private async void MarkAsDeletedAsync_NonExistentNotificationId_NotFound()
|
||||
{
|
||||
await _loginHelper.LoginAsync(_ownerEmail);
|
||||
|
||||
var url = $"/notifications/{Guid.NewGuid()}/delete";
|
||||
var response = await _client.PatchAsync(url, new StringContent(""));
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
private async void MarkAsDeletedAsync_UserIdNotMatching_NotFound()
|
||||
{
|
||||
var email = $"integration-test{Guid.NewGuid()}@bitwarden.com";
|
||||
await _factory.LoginWithNewAccount(email);
|
||||
var user = (await _userRepository.GetByEmailAsync(email))!;
|
||||
var notifications = await CreateNotificationsAsync(user.Id);
|
||||
|
||||
await _loginHelper.LoginAsync(_ownerEmail);
|
||||
|
||||
var url = $"/notifications/{notifications[0].Id}/delete";
|
||||
var response = await _client.PatchAsync(url, new StringContent(""));
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
private async void MarkAsDeletedAsync_OrganizationIdNotMatchingUserNotPartOfOrganization_NotFound()
|
||||
{
|
||||
var email = $"integration-test{Guid.NewGuid()}@bitwarden.com";
|
||||
await _factory.LoginWithNewAccount(email);
|
||||
var user = (await _userRepository.GetByEmailAsync(email))!;
|
||||
var notifications = await CreateNotificationsAsync(user.Id, _organization.Id);
|
||||
|
||||
await _loginHelper.LoginAsync(email);
|
||||
|
||||
var url = $"/notifications/{notifications[0].Id}/delete";
|
||||
var response = await _client.PatchAsync(url, new StringContent(""));
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
private async void MarkAsDeletedAsync_OrganizationIdNotMatchingUserPartOfDifferentOrganization_NotFound()
|
||||
{
|
||||
var (organization, _) = await OrganizationTestHelpers.SignUpAsync(_factory,
|
||||
plan: PlanType.EnterpriseAnnually, ownerEmail: _ownerEmail, passwordManagerSeats: 10,
|
||||
paymentMethod: PaymentMethodType.Card);
|
||||
var email = $"integration-test{Guid.NewGuid()}@bitwarden.com";
|
||||
await _factory.LoginWithNewAccount(email);
|
||||
var user = (await _userRepository.GetByEmailAsync(email))!;
|
||||
await OrganizationTestHelpers.CreateUserAsync(_factory, organization.Id, email, OrganizationUserType.User);
|
||||
var notifications = await CreateNotificationsAsync(user.Id, _organization.Id);
|
||||
|
||||
await _loginHelper.LoginAsync(email);
|
||||
|
||||
var url = $"/notifications/{notifications[0].Id}/delete";
|
||||
var response = await _client.PatchAsync(url, new StringContent(""));
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
private async void MarkAsDeletedAsync_NotificationStatusNotExisting_Created()
|
||||
{
|
||||
var notifications = await CreateNotificationsAsync(_organizationUserOwner.UserId);
|
||||
|
||||
await _loginHelper.LoginAsync(_ownerEmail);
|
||||
|
||||
var url = $"/notifications/{notifications[0].Id}/delete";
|
||||
var response = await _client.PatchAsync(url, new StringContent(""));
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var notificationStatus = await _notificationStatusRepository.GetByNotificationIdAndUserIdAsync(
|
||||
notifications[0].Id, _organizationUserOwner.UserId!.Value);
|
||||
Assert.NotNull(notificationStatus);
|
||||
Assert.NotNull(notificationStatus.DeletedDate);
|
||||
Assert.Equal(DateTime.UtcNow, notificationStatus.DeletedDate.Value, OneMinuteTimeSpan);
|
||||
Assert.Null(notificationStatus.ReadDate);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
private async void MarkAsDeletedAsync_NotificationStatusExisting_Updated(bool deletedDateNull)
|
||||
{
|
||||
var notifications = await CreateNotificationsAsync(_organizationUserOwner.UserId);
|
||||
await _notificationStatusRepository.CreateAsync(new NotificationStatus
|
||||
{
|
||||
NotificationId = notifications[0].Id,
|
||||
UserId = _organizationUserOwner.UserId!.Value,
|
||||
ReadDate = null,
|
||||
DeletedDate = deletedDateNull ? null : DateTime.UtcNow - TimeSpan.FromMinutes(_random.Next(3600))
|
||||
});
|
||||
|
||||
await _loginHelper.LoginAsync(_ownerEmail);
|
||||
|
||||
var url = $"/notifications/{notifications[0].Id}/delete";
|
||||
var response = await _client.PatchAsync(url, new StringContent(""));
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var notificationStatus = await _notificationStatusRepository.GetByNotificationIdAndUserIdAsync(
|
||||
notifications[0].Id, _organizationUserOwner.UserId!.Value);
|
||||
Assert.NotNull(notificationStatus);
|
||||
Assert.NotNull(notificationStatus.DeletedDate);
|
||||
Assert.Equal(DateTime.UtcNow, notificationStatus.DeletedDate.Value, OneMinuteTimeSpan);
|
||||
Assert.Null(notificationStatus.ReadDate);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
private async void MarkAsReadAsync_NotLoggedIn_Unauthorized()
|
||||
{
|
||||
var url = $"/notifications/{Guid.NewGuid().ToString()}/read";
|
||||
var response = await _client.PatchAsync(url, new StringContent(""));
|
||||
Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
private async void MarkAsReadAsync_NonExistentNotificationId_NotFound()
|
||||
{
|
||||
await _loginHelper.LoginAsync(_ownerEmail);
|
||||
|
||||
var url = $"/notifications/{Guid.NewGuid()}/read";
|
||||
var response = await _client.PatchAsync(url, new StringContent(""));
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
private async void MarkAsReadAsync_UserIdNotMatching_NotFound()
|
||||
{
|
||||
var email = $"integration-test{Guid.NewGuid()}@bitwarden.com";
|
||||
await _factory.LoginWithNewAccount(email);
|
||||
var user = (await _userRepository.GetByEmailAsync(email))!;
|
||||
var notifications = await CreateNotificationsAsync(user.Id);
|
||||
|
||||
await _loginHelper.LoginAsync(_ownerEmail);
|
||||
|
||||
var url = $"/notifications/{notifications[0].Id}/read";
|
||||
var response = await _client.PatchAsync(url, new StringContent(""));
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
private async void MarkAsReadAsync_OrganizationIdNotMatchingUserNotPartOfOrganization_NotFound()
|
||||
{
|
||||
var email = $"integration-test{Guid.NewGuid()}@bitwarden.com";
|
||||
await _factory.LoginWithNewAccount(email);
|
||||
var user = (await _userRepository.GetByEmailAsync(email))!;
|
||||
var notifications = await CreateNotificationsAsync(user.Id, _organization.Id);
|
||||
|
||||
await _loginHelper.LoginAsync(email);
|
||||
|
||||
var url = $"/notifications/{notifications[0].Id}/read";
|
||||
var response = await _client.PatchAsync(url, new StringContent(""));
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
private async void MarkAsReadAsync_OrganizationIdNotMatchingUserPartOfDifferentOrganization_NotFound()
|
||||
{
|
||||
var (organization, _) = await OrganizationTestHelpers.SignUpAsync(_factory,
|
||||
plan: PlanType.EnterpriseAnnually, ownerEmail: _ownerEmail, passwordManagerSeats: 10,
|
||||
paymentMethod: PaymentMethodType.Card);
|
||||
var email = $"integration-test{Guid.NewGuid()}@bitwarden.com";
|
||||
await _factory.LoginWithNewAccount(email);
|
||||
var user = (await _userRepository.GetByEmailAsync(email))!;
|
||||
await OrganizationTestHelpers.CreateUserAsync(_factory, organization.Id, email, OrganizationUserType.User);
|
||||
var notifications = await CreateNotificationsAsync(user.Id, _organization.Id);
|
||||
|
||||
await _loginHelper.LoginAsync(email);
|
||||
|
||||
var url = $"/notifications/{notifications[0].Id}/read";
|
||||
var response = await _client.PatchAsync(url, new StringContent(""));
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
private async void MarkAsReadAsync_NotificationStatusNotExisting_Created()
|
||||
{
|
||||
var notifications = await CreateNotificationsAsync(_organizationUserOwner.UserId);
|
||||
|
||||
await _loginHelper.LoginAsync(_ownerEmail);
|
||||
|
||||
var url = $"/notifications/{notifications[0].Id}/read";
|
||||
var response = await _client.PatchAsync(url, new StringContent(""));
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var notificationStatus = await _notificationStatusRepository.GetByNotificationIdAndUserIdAsync(
|
||||
notifications[0].Id, _organizationUserOwner.UserId!.Value);
|
||||
Assert.NotNull(notificationStatus);
|
||||
Assert.NotNull(notificationStatus.ReadDate);
|
||||
Assert.Equal(DateTime.UtcNow, notificationStatus.ReadDate.Value, OneMinuteTimeSpan);
|
||||
Assert.Null(notificationStatus.DeletedDate);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
[InlineData(true)]
|
||||
private async void MarkAsReadAsync_NotificationStatusExisting_Updated(bool readDateNull)
|
||||
{
|
||||
var notifications = await CreateNotificationsAsync(_organizationUserOwner.UserId);
|
||||
await _notificationStatusRepository.CreateAsync(new NotificationStatus
|
||||
{
|
||||
NotificationId = notifications[0].Id,
|
||||
UserId = _organizationUserOwner.UserId!.Value,
|
||||
ReadDate = readDateNull ? null : DateTime.UtcNow - TimeSpan.FromMinutes(_random.Next(3600)),
|
||||
DeletedDate = null
|
||||
});
|
||||
|
||||
await _loginHelper.LoginAsync(_ownerEmail);
|
||||
|
||||
var url = $"/notifications/{notifications[0].Id}/read";
|
||||
var response = await _client.PatchAsync(url, new StringContent(""));
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
|
||||
var notificationStatus = await _notificationStatusRepository.GetByNotificationIdAndUserIdAsync(
|
||||
notifications[0].Id, _organizationUserOwner.UserId!.Value);
|
||||
Assert.NotNull(notificationStatus);
|
||||
Assert.NotNull(notificationStatus.ReadDate);
|
||||
Assert.Equal(DateTime.UtcNow, notificationStatus.ReadDate.Value, OneMinuteTimeSpan);
|
||||
Assert.Null(notificationStatus.DeletedDate);
|
||||
}
|
||||
|
||||
private static void AssertNotificationResponseModels(
|
||||
IEnumerable<NotificationResponseModel> notificationResponseModels,
|
||||
List<(Notification, NotificationStatus?)> expectedNotificationsWithStatuses)
|
||||
{
|
||||
var i = 0;
|
||||
foreach (var notificationResponseModel in notificationResponseModels)
|
||||
{
|
||||
Assert.Contains(expectedNotificationsWithStatuses, e => e.Item1.Id == notificationResponseModel.Id);
|
||||
var (expectedNotification, expectedNotificationStatus) = expectedNotificationsWithStatuses[i];
|
||||
Assert.NotNull(expectedNotification);
|
||||
Assert.Equal(expectedNotification.Priority, notificationResponseModel.Priority);
|
||||
Assert.Equal(expectedNotification.Title, notificationResponseModel.Title);
|
||||
Assert.Equal(expectedNotification.Body, notificationResponseModel.Body);
|
||||
Assert.Equal(expectedNotification.RevisionDate, notificationResponseModel.Date);
|
||||
if (expectedNotificationStatus != null)
|
||||
{
|
||||
Assert.Equal(expectedNotificationStatus.ReadDate, notificationResponseModel.ReadDate);
|
||||
Assert.Equal(expectedNotificationStatus.DeletedDate, notificationResponseModel.DeletedDate);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.Null(notificationResponseModel.ReadDate);
|
||||
Assert.Null(notificationResponseModel.DeletedDate);
|
||||
}
|
||||
|
||||
Assert.Equal("notification", notificationResponseModel.Object);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<List<(Notification, NotificationStatus?)>> CreateNotificationsWithStatusesAsync()
|
||||
{
|
||||
var userId = (Guid)_organizationUserOwner.UserId!;
|
||||
|
||||
var globalNotifications = await CreateNotificationsAsync();
|
||||
var userWithoutOrganizationNotifications = await CreateNotificationsAsync(userId: userId);
|
||||
var organizationWithoutUserNotifications = await CreateNotificationsAsync(organizationId: _organization.Id);
|
||||
var userPartOrOrganizationNotifications = await CreateNotificationsAsync(userId: userId,
|
||||
organizationId: _organization.Id);
|
||||
|
||||
var globalNotificationWithStatuses = await CreateNotificationStatusesAsync(globalNotifications, userId);
|
||||
var userWithoutOrganizationNotificationWithStatuses =
|
||||
await CreateNotificationStatusesAsync(userWithoutOrganizationNotifications, userId);
|
||||
var organizationWithoutUserNotificationWithStatuses =
|
||||
await CreateNotificationStatusesAsync(organizationWithoutUserNotifications, userId);
|
||||
var userPartOrOrganizationNotificationWithStatuses =
|
||||
await CreateNotificationStatusesAsync(userPartOrOrganizationNotifications, userId);
|
||||
|
||||
return new List<List<(Notification, NotificationStatus?)>>
|
||||
{
|
||||
globalNotificationWithStatuses,
|
||||
userWithoutOrganizationNotificationWithStatuses,
|
||||
organizationWithoutUserNotificationWithStatuses,
|
||||
userPartOrOrganizationNotificationWithStatuses
|
||||
}
|
||||
.SelectMany(n => n)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private async Task<List<Notification>> CreateNotificationsAsync(Guid? userId = null, Guid? organizationId = null,
|
||||
int numberToCreate = 5)
|
||||
{
|
||||
var priorities = Enum.GetValues<Priority>();
|
||||
var clientTypes = Enum.GetValues<ClientType>();
|
||||
|
||||
var notifications = new List<Notification>();
|
||||
|
||||
foreach (var clientType in clientTypes)
|
||||
{
|
||||
for (var i = 0; i < numberToCreate; i++)
|
||||
{
|
||||
var notification = new Notification
|
||||
{
|
||||
Global = userId == null && organizationId == null,
|
||||
UserId = userId,
|
||||
OrganizationId = organizationId,
|
||||
Title = _mockEncryptedTitle,
|
||||
Body = _mockEncryptedBody,
|
||||
Priority = (Priority)priorities.GetValue(_random.Next(priorities.Length))!,
|
||||
ClientType = clientType,
|
||||
CreationDate = DateTime.UtcNow - TimeSpan.FromMinutes(_random.Next(3600)),
|
||||
RevisionDate = DateTime.UtcNow - TimeSpan.FromMinutes(_random.Next(3600))
|
||||
};
|
||||
|
||||
notification = await _notificationRepository.CreateAsync(notification);
|
||||
|
||||
notifications.Add(notification);
|
||||
}
|
||||
}
|
||||
|
||||
return notifications;
|
||||
}
|
||||
|
||||
private async Task<List<(Notification, NotificationStatus?)>> CreateNotificationStatusesAsync(
|
||||
List<Notification> notifications, Guid userId)
|
||||
{
|
||||
var readDateNotificationStatus = await _notificationStatusRepository.CreateAsync(new NotificationStatus
|
||||
{
|
||||
NotificationId = notifications[0].Id,
|
||||
UserId = userId,
|
||||
ReadDate = DateTime.UtcNow - TimeSpan.FromMinutes(_random.Next(3600)),
|
||||
DeletedDate = null
|
||||
});
|
||||
|
||||
var deletedDateNotificationStatus = await _notificationStatusRepository.CreateAsync(new NotificationStatus
|
||||
{
|
||||
NotificationId = notifications[1].Id,
|
||||
UserId = userId,
|
||||
ReadDate = null,
|
||||
DeletedDate = DateTime.UtcNow - TimeSpan.FromMinutes(_random.Next(3600))
|
||||
});
|
||||
|
||||
var readDateAndDeletedDateNotificationStatus = await _notificationStatusRepository.CreateAsync(
|
||||
new NotificationStatus
|
||||
{
|
||||
NotificationId = notifications[2].Id,
|
||||
UserId = userId,
|
||||
ReadDate = DateTime.UtcNow - TimeSpan.FromMinutes(_random.Next(3600)),
|
||||
DeletedDate = DateTime.UtcNow - TimeSpan.FromMinutes(_random.Next(3600))
|
||||
});
|
||||
|
||||
return
|
||||
[
|
||||
(notifications[0], readDateNotificationStatus),
|
||||
(notifications[1], deletedDateNotificationStatus),
|
||||
(notifications[2], readDateAndDeletedDateNotificationStatus),
|
||||
(notifications[3], null),
|
||||
(notifications[4], null)
|
||||
];
|
||||
}
|
||||
}
|
@ -0,0 +1,202 @@
|
||||
#nullable enable
|
||||
using Bit.Api.NotificationCenter.Controllers;
|
||||
using Bit.Api.NotificationCenter.Models.Request;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.NotificationCenter.Commands.Interfaces;
|
||||
using Bit.Core.NotificationCenter.Models.Data;
|
||||
using Bit.Core.NotificationCenter.Models.Filter;
|
||||
using Bit.Core.NotificationCenter.Queries.Interfaces;
|
||||
using Bit.Core.Test.NotificationCenter.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Api.Test.NotificationCenter.Controllers;
|
||||
|
||||
[ControllerCustomize(typeof(NotificationsController))]
|
||||
[SutProviderCustomize]
|
||||
public class NotificationsControllerTests
|
||||
{
|
||||
[Theory]
|
||||
[BitAutoData([null, null])]
|
||||
[BitAutoData([null, false])]
|
||||
[BitAutoData([null, true])]
|
||||
[BitAutoData(false, null)]
|
||||
[BitAutoData(true, null)]
|
||||
[BitAutoData(false, false)]
|
||||
[BitAutoData(false, true)]
|
||||
[BitAutoData(true, false)]
|
||||
[BitAutoData(true, true)]
|
||||
[NotificationStatusDetailsListCustomize(5)]
|
||||
public async Task ListAsync_StatusFilter_ReturnedMatchingNotifications(bool? readStatusFilter, bool? deletedStatusFilter,
|
||||
SutProvider<NotificationsController> sutProvider,
|
||||
IEnumerable<NotificationStatusDetails> notificationStatusDetailsEnumerable)
|
||||
{
|
||||
var notificationStatusDetailsList = notificationStatusDetailsEnumerable
|
||||
.OrderByDescending(n => n.Priority)
|
||||
.ThenByDescending(n => n.CreationDate)
|
||||
.ToList();
|
||||
|
||||
sutProvider.GetDependency<IGetNotificationStatusDetailsForUserQuery>()
|
||||
.GetByUserIdStatusFilterAsync(Arg.Any<NotificationStatusFilter>(), Arg.Any<PageOptions>())
|
||||
.Returns(new PagedResult<NotificationStatusDetails> { Data = notificationStatusDetailsList });
|
||||
|
||||
var expectedNotificationStatusDetailsMap = notificationStatusDetailsList
|
||||
.Take(10)
|
||||
.ToDictionary(n => n.Id);
|
||||
|
||||
var listResponse = await sutProvider.Sut.ListAsync(new NotificationFilterRequestModel
|
||||
{
|
||||
ReadStatusFilter = readStatusFilter,
|
||||
DeletedStatusFilter = deletedStatusFilter
|
||||
});
|
||||
|
||||
Assert.Equal("list", listResponse.Object);
|
||||
Assert.Equal(5, listResponse.Data.Count());
|
||||
Assert.All(listResponse.Data, notificationResponseModel =>
|
||||
{
|
||||
Assert.Equal("notification", notificationResponseModel.Object);
|
||||
Assert.True(expectedNotificationStatusDetailsMap.ContainsKey(notificationResponseModel.Id));
|
||||
var expectedNotificationStatusDetails = expectedNotificationStatusDetailsMap[notificationResponseModel.Id];
|
||||
Assert.NotNull(expectedNotificationStatusDetails);
|
||||
Assert.Equal(expectedNotificationStatusDetails.Id, notificationResponseModel.Id);
|
||||
Assert.Equal(expectedNotificationStatusDetails.Priority, notificationResponseModel.Priority);
|
||||
Assert.Equal(expectedNotificationStatusDetails.Title, notificationResponseModel.Title);
|
||||
Assert.Equal(expectedNotificationStatusDetails.Body, notificationResponseModel.Body);
|
||||
Assert.Equal(expectedNotificationStatusDetails.RevisionDate, notificationResponseModel.Date);
|
||||
Assert.Equal(expectedNotificationStatusDetails.ReadDate, notificationResponseModel.ReadDate);
|
||||
Assert.Equal(expectedNotificationStatusDetails.DeletedDate, notificationResponseModel.DeletedDate);
|
||||
});
|
||||
Assert.Null(listResponse.ContinuationToken);
|
||||
|
||||
await sutProvider.GetDependency<IGetNotificationStatusDetailsForUserQuery>()
|
||||
.Received(1)
|
||||
.GetByUserIdStatusFilterAsync(Arg.Is<NotificationStatusFilter>(filter =>
|
||||
filter.Read == readStatusFilter && filter.Deleted == deletedStatusFilter),
|
||||
Arg.Is<PageOptions>(pageOptions =>
|
||||
pageOptions.ContinuationToken == null && pageOptions.PageSize == 10));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
[NotificationStatusDetailsListCustomize(19)]
|
||||
public async Task ListAsync_PagingRequestNoContinuationToken_ReturnedFirst10MatchingNotifications(
|
||||
SutProvider<NotificationsController> sutProvider,
|
||||
IEnumerable<NotificationStatusDetails> notificationStatusDetailsEnumerable)
|
||||
{
|
||||
var notificationStatusDetailsList = notificationStatusDetailsEnumerable
|
||||
.OrderByDescending(n => n.Priority)
|
||||
.ThenByDescending(n => n.CreationDate)
|
||||
.ToList();
|
||||
|
||||
sutProvider.GetDependency<IGetNotificationStatusDetailsForUserQuery>()
|
||||
.GetByUserIdStatusFilterAsync(Arg.Any<NotificationStatusFilter>(), Arg.Any<PageOptions>())
|
||||
.Returns(new PagedResult<NotificationStatusDetails>
|
||||
{ Data = notificationStatusDetailsList.Take(10).ToList(), ContinuationToken = "2" });
|
||||
|
||||
var expectedNotificationStatusDetailsMap = notificationStatusDetailsList
|
||||
.Take(10)
|
||||
.ToDictionary(n => n.Id);
|
||||
|
||||
var listResponse = await sutProvider.Sut.ListAsync(new NotificationFilterRequestModel());
|
||||
|
||||
Assert.Equal("list", listResponse.Object);
|
||||
Assert.Equal(10, listResponse.Data.Count());
|
||||
Assert.All(listResponse.Data, notificationResponseModel =>
|
||||
{
|
||||
Assert.Equal("notification", notificationResponseModel.Object);
|
||||
Assert.True(expectedNotificationStatusDetailsMap.ContainsKey(notificationResponseModel.Id));
|
||||
var expectedNotificationStatusDetails = expectedNotificationStatusDetailsMap[notificationResponseModel.Id];
|
||||
Assert.NotNull(expectedNotificationStatusDetails);
|
||||
Assert.Equal(expectedNotificationStatusDetails.Id, notificationResponseModel.Id);
|
||||
Assert.Equal(expectedNotificationStatusDetails.Priority, notificationResponseModel.Priority);
|
||||
Assert.Equal(expectedNotificationStatusDetails.Title, notificationResponseModel.Title);
|
||||
Assert.Equal(expectedNotificationStatusDetails.Body, notificationResponseModel.Body);
|
||||
Assert.Equal(expectedNotificationStatusDetails.RevisionDate, notificationResponseModel.Date);
|
||||
Assert.Equal(expectedNotificationStatusDetails.ReadDate, notificationResponseModel.ReadDate);
|
||||
Assert.Equal(expectedNotificationStatusDetails.DeletedDate, notificationResponseModel.DeletedDate);
|
||||
});
|
||||
Assert.Equal("2", listResponse.ContinuationToken);
|
||||
|
||||
await sutProvider.GetDependency<IGetNotificationStatusDetailsForUserQuery>()
|
||||
.Received(1)
|
||||
.GetByUserIdStatusFilterAsync(Arg.Any<NotificationStatusFilter>(),
|
||||
Arg.Is<PageOptions>(pageOptions =>
|
||||
pageOptions.ContinuationToken == null && pageOptions.PageSize == 10));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
[NotificationStatusDetailsListCustomize(19)]
|
||||
public async Task ListAsync_PagingRequestUsingContinuationToken_ReturnedLast9MatchingNotifications(
|
||||
SutProvider<NotificationsController> sutProvider,
|
||||
IEnumerable<NotificationStatusDetails> notificationStatusDetailsEnumerable)
|
||||
{
|
||||
var notificationStatusDetailsList = notificationStatusDetailsEnumerable
|
||||
.OrderByDescending(n => n.Priority)
|
||||
.ThenByDescending(n => n.CreationDate)
|
||||
.ToList();
|
||||
|
||||
sutProvider.GetDependency<IGetNotificationStatusDetailsForUserQuery>()
|
||||
.GetByUserIdStatusFilterAsync(Arg.Any<NotificationStatusFilter>(), Arg.Any<PageOptions>())
|
||||
.Returns(new PagedResult<NotificationStatusDetails>
|
||||
{ Data = notificationStatusDetailsList.Skip(10).ToList() });
|
||||
|
||||
var expectedNotificationStatusDetailsMap = notificationStatusDetailsList
|
||||
.Skip(10)
|
||||
.ToDictionary(n => n.Id);
|
||||
|
||||
var listResponse = await sutProvider.Sut.ListAsync(new NotificationFilterRequestModel { ContinuationToken = "2" });
|
||||
|
||||
Assert.Equal("list", listResponse.Object);
|
||||
Assert.Equal(9, listResponse.Data.Count());
|
||||
Assert.All(listResponse.Data, notificationResponseModel =>
|
||||
{
|
||||
Assert.Equal("notification", notificationResponseModel.Object);
|
||||
Assert.True(expectedNotificationStatusDetailsMap.ContainsKey(notificationResponseModel.Id));
|
||||
var expectedNotificationStatusDetails = expectedNotificationStatusDetailsMap[notificationResponseModel.Id];
|
||||
Assert.NotNull(expectedNotificationStatusDetails);
|
||||
Assert.Equal(expectedNotificationStatusDetails.Id, notificationResponseModel.Id);
|
||||
Assert.Equal(expectedNotificationStatusDetails.Priority, notificationResponseModel.Priority);
|
||||
Assert.Equal(expectedNotificationStatusDetails.Title, notificationResponseModel.Title);
|
||||
Assert.Equal(expectedNotificationStatusDetails.Body, notificationResponseModel.Body);
|
||||
Assert.Equal(expectedNotificationStatusDetails.RevisionDate, notificationResponseModel.Date);
|
||||
Assert.Equal(expectedNotificationStatusDetails.ReadDate, notificationResponseModel.ReadDate);
|
||||
Assert.Equal(expectedNotificationStatusDetails.DeletedDate, notificationResponseModel.DeletedDate);
|
||||
});
|
||||
Assert.Null(listResponse.ContinuationToken);
|
||||
|
||||
await sutProvider.GetDependency<IGetNotificationStatusDetailsForUserQuery>()
|
||||
.Received(1)
|
||||
.GetByUserIdStatusFilterAsync(Arg.Any<NotificationStatusFilter>(),
|
||||
Arg.Is<PageOptions>(pageOptions =>
|
||||
pageOptions.ContinuationToken == "2" && pageOptions.PageSize == 10));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task MarkAsDeletedAsync_NotificationId_MarkedAsDeleted(
|
||||
SutProvider<NotificationsController> sutProvider,
|
||||
Guid notificationId)
|
||||
{
|
||||
await sutProvider.Sut.MarkAsDeletedAsync(notificationId);
|
||||
|
||||
await sutProvider.GetDependency<IMarkNotificationDeletedCommand>()
|
||||
.Received(1)
|
||||
.MarkDeletedAsync(notificationId);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task MarkAsReadAsync_NotificationId_MarkedAsRead(
|
||||
SutProvider<NotificationsController> sutProvider,
|
||||
Guid notificationId)
|
||||
{
|
||||
await sutProvider.Sut.MarkAsReadAsync(notificationId);
|
||||
|
||||
await sutProvider.GetDependency<IMarkNotificationReadCommand>()
|
||||
.Received(1)
|
||||
.MarkReadAsync(notificationId);
|
||||
}
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
#nullable enable
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Bit.Api.NotificationCenter.Models.Request;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Api.Test.NotificationCenter.Models.Request;
|
||||
|
||||
public class NotificationFilterRequestModelTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("invalid")]
|
||||
[InlineData("-1")]
|
||||
[InlineData("0")]
|
||||
public void Validate_ContinuationTokenInvalidNumber_Invalid(string continuationToken)
|
||||
{
|
||||
var model = new NotificationFilterRequestModel
|
||||
{
|
||||
ContinuationToken = continuationToken,
|
||||
};
|
||||
var result = Validate(model);
|
||||
Assert.Single(result);
|
||||
Assert.Contains("Continuation token must be a positive, non zero integer.", result[0].ErrorMessage);
|
||||
Assert.Contains("ContinuationToken", result[0].MemberNames);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_ContinuationTokenMaxLengthExceeded_Invalid()
|
||||
{
|
||||
var model = new NotificationFilterRequestModel
|
||||
{
|
||||
ContinuationToken = "1234567890"
|
||||
};
|
||||
var result = Validate(model);
|
||||
Assert.Single(result);
|
||||
Assert.Contains("The field ContinuationToken must be a string with a maximum length of 9.",
|
||||
result[0].ErrorMessage);
|
||||
Assert.Contains("ContinuationToken", result[0].MemberNames);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData("")]
|
||||
[InlineData(" ")]
|
||||
[InlineData("1")]
|
||||
[InlineData("123456789")]
|
||||
public void Validate_ContinuationTokenCorrect_Valid(string? continuationToken)
|
||||
{
|
||||
var model = new NotificationFilterRequestModel
|
||||
{
|
||||
ContinuationToken = continuationToken
|
||||
};
|
||||
var result = Validate(model);
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(9)]
|
||||
[InlineData(1001)]
|
||||
public void Validate_PageSizeInvalidRange_Invalid(int pageSize)
|
||||
{
|
||||
var model = new NotificationFilterRequestModel
|
||||
{
|
||||
PageSize = pageSize
|
||||
};
|
||||
var result = Validate(model);
|
||||
Assert.Single(result);
|
||||
Assert.Contains("The field PageSize must be between 10 and 1000.", result[0].ErrorMessage);
|
||||
Assert.Contains("PageSize", result[0].MemberNames);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null)]
|
||||
[InlineData(10)]
|
||||
[InlineData(1000)]
|
||||
public void Validate_PageSizeCorrect_Valid(int? pageSize)
|
||||
{
|
||||
var model = pageSize == null
|
||||
? new NotificationFilterRequestModel()
|
||||
: new NotificationFilterRequestModel
|
||||
{
|
||||
PageSize = pageSize.Value
|
||||
};
|
||||
var result = Validate(model);
|
||||
Assert.Empty(result);
|
||||
}
|
||||
|
||||
private static List<ValidationResult> Validate(NotificationFilterRequestModel model)
|
||||
{
|
||||
var results = new List<ValidationResult>();
|
||||
Validator.TryValidateObject(model, new ValidationContext(model), results, true);
|
||||
return results;
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
#nullable enable
|
||||
using Bit.Api.NotificationCenter.Models.Response;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.NotificationCenter.Enums;
|
||||
using Bit.Core.NotificationCenter.Models.Data;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Api.Test.NotificationCenter.Models.Response;
|
||||
|
||||
public class NotificationResponseModelTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_NotificationStatusDetailsNull_CorrectFields()
|
||||
{
|
||||
Assert.Throws<ArgumentNullException>(() => new NotificationResponseModel(null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_NotificationStatusDetails_CorrectFields()
|
||||
{
|
||||
var notificationStatusDetails = new NotificationStatusDetails
|
||||
{
|
||||
Id = Guid.NewGuid(),
|
||||
Global = true,
|
||||
Priority = Priority.High,
|
||||
ClientType = ClientType.All,
|
||||
Title = "Test Title",
|
||||
Body = "Test Body",
|
||||
RevisionDate = DateTime.UtcNow - TimeSpan.FromMinutes(3),
|
||||
ReadDate = DateTime.UtcNow - TimeSpan.FromMinutes(1),
|
||||
DeletedDate = DateTime.UtcNow,
|
||||
};
|
||||
var model = new NotificationResponseModel(notificationStatusDetails);
|
||||
|
||||
Assert.Equal(model.Id, notificationStatusDetails.Id);
|
||||
Assert.Equal(model.Priority, notificationStatusDetails.Priority);
|
||||
Assert.Equal(model.Title, notificationStatusDetails.Title);
|
||||
Assert.Equal(model.Body, notificationStatusDetails.Body);
|
||||
Assert.Equal(model.Date, notificationStatusDetails.RevisionDate);
|
||||
Assert.Equal(model.ReadDate, notificationStatusDetails.ReadDate);
|
||||
Assert.Equal(model.DeletedDate, notificationStatusDetails.DeletedDate);
|
||||
}
|
||||
}
|
@ -9,9 +9,32 @@ public class NotificationStatusDetailsCustomization : ICustomization
|
||||
{
|
||||
public void Customize(IFixture fixture)
|
||||
{
|
||||
fixture.Customize<NotificationStatusDetails>(composer => composer.With(n => n.Id, Guid.NewGuid())
|
||||
.With(n => n.UserId, Guid.NewGuid())
|
||||
.With(n => n.OrganizationId, Guid.NewGuid()));
|
||||
fixture.Customize<NotificationStatusDetails>(composer =>
|
||||
{
|
||||
return composer.With(n => n.Id, Guid.NewGuid())
|
||||
.With(n => n.UserId, Guid.NewGuid())
|
||||
.With(n => n.OrganizationId, Guid.NewGuid());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class NotificationStatusDetailsListCustomization(int count) : ICustomization
|
||||
{
|
||||
public void Customize(IFixture fixture)
|
||||
{
|
||||
var customization = new NotificationStatusDetailsCustomization();
|
||||
fixture.Customize<IEnumerable<NotificationStatusDetails>>(composer => composer.FromFactory(() =>
|
||||
{
|
||||
var notifications = new List<NotificationStatusDetails>();
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
customization.Customize(fixture);
|
||||
var notificationStatusDetails = fixture.Create<NotificationStatusDetails>();
|
||||
notifications.Add(notificationStatusDetails);
|
||||
}
|
||||
|
||||
return notifications;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,3 +42,8 @@ public class NotificationStatusDetailsCustomizeAttribute : BitCustomizeAttribute
|
||||
{
|
||||
public override ICustomization GetCustomization() => new NotificationStatusDetailsCustomization();
|
||||
}
|
||||
|
||||
public class NotificationStatusDetailsListCustomizeAttribute(int count) : BitCustomizeAttribute
|
||||
{
|
||||
public override ICustomization GetCustomization() => new NotificationStatusDetailsListCustomization(count);
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.NotificationCenter.Models.Data;
|
||||
using Bit.Core.NotificationCenter.Models.Filter;
|
||||
using Bit.Core.NotificationCenter.Queries;
|
||||
@ -19,37 +20,49 @@ namespace Bit.Core.Test.NotificationCenter.Queries;
|
||||
public class GetNotificationStatusDetailsForUserQueryTest
|
||||
{
|
||||
private static void Setup(SutProvider<GetNotificationStatusDetailsForUserQuery> sutProvider,
|
||||
List<NotificationStatusDetails> notificationsStatusDetails, NotificationStatusFilter statusFilter, Guid? userId)
|
||||
List<NotificationStatusDetails> notificationsStatusDetails, NotificationStatusFilter statusFilter, Guid? userId,
|
||||
PageOptions pageOptions, string? continuationToken)
|
||||
{
|
||||
sutProvider.GetDependency<ICurrentContext>().UserId.Returns(userId);
|
||||
sutProvider.GetDependency<INotificationRepository>().GetByUserIdAndStatusAsync(
|
||||
userId.GetValueOrDefault(Guid.NewGuid()), Arg.Any<ClientType>(), statusFilter)
|
||||
.Returns(notificationsStatusDetails);
|
||||
sutProvider.GetDependency<INotificationRepository>()
|
||||
.GetByUserIdAndStatusAsync(userId.GetValueOrDefault(Guid.NewGuid()), Arg.Any<ClientType>(), statusFilter,
|
||||
pageOptions)
|
||||
.Returns(new PagedResult<NotificationStatusDetails>
|
||||
{
|
||||
Data = notificationsStatusDetails,
|
||||
ContinuationToken = continuationToken
|
||||
});
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GetByUserIdStatusFilterAsync_NotLoggedIn_NotFoundException(
|
||||
SutProvider<GetNotificationStatusDetailsForUserQuery> sutProvider,
|
||||
List<NotificationStatusDetails> notificationsStatusDetails, NotificationStatusFilter notificationStatusFilter)
|
||||
List<NotificationStatusDetails> notificationsStatusDetails, NotificationStatusFilter notificationStatusFilter,
|
||||
PageOptions pageOptions, string? continuationToken)
|
||||
{
|
||||
Setup(sutProvider, notificationsStatusDetails, notificationStatusFilter, userId: null);
|
||||
Setup(sutProvider, notificationsStatusDetails, notificationStatusFilter, userId: null, pageOptions,
|
||||
continuationToken);
|
||||
|
||||
await Assert.ThrowsAsync<NotFoundException>(() =>
|
||||
sutProvider.Sut.GetByUserIdStatusFilterAsync(notificationStatusFilter));
|
||||
sutProvider.Sut.GetByUserIdStatusFilterAsync(notificationStatusFilter, pageOptions));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task GetByUserIdStatusFilterAsync_NotificationsFound_Returned(
|
||||
SutProvider<GetNotificationStatusDetailsForUserQuery> sutProvider,
|
||||
List<NotificationStatusDetails> notificationsStatusDetails, NotificationStatusFilter notificationStatusFilter)
|
||||
List<NotificationStatusDetails> notificationsStatusDetails, NotificationStatusFilter notificationStatusFilter,
|
||||
PageOptions pageOptions, string? continuationToken)
|
||||
{
|
||||
Setup(sutProvider, notificationsStatusDetails, notificationStatusFilter, Guid.NewGuid());
|
||||
Setup(sutProvider, notificationsStatusDetails, notificationStatusFilter, Guid.NewGuid(), pageOptions,
|
||||
continuationToken);
|
||||
|
||||
var actualNotificationsStatusDetails =
|
||||
await sutProvider.Sut.GetByUserIdStatusFilterAsync(notificationStatusFilter);
|
||||
var actualNotificationsStatusDetailsPagedResult =
|
||||
await sutProvider.Sut.GetByUserIdStatusFilterAsync(notificationStatusFilter, pageOptions);
|
||||
|
||||
Assert.Equal(notificationsStatusDetails, actualNotificationsStatusDetails);
|
||||
Assert.NotNull(actualNotificationsStatusDetailsPagedResult);
|
||||
Assert.Equal(notificationsStatusDetails, actualNotificationsStatusDetailsPagedResult.Data);
|
||||
Assert.Equal(continuationToken, actualNotificationsStatusDetailsPagedResult.ContinuationToken);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user