1
0
mirror of https://github.com/bitwarden/server synced 2025-12-15 07:43:54 +00:00

PM-10564: Push notification updates to other clients

When a notification is updated, marked as read or deleted, a push notification is sent with updated push type event. The push notification includes the ReadDate and DeletedDate fields.
This commit is contained in:
Maciej Zieniuk
2024-11-21 21:39:28 +00:00
parent d028029270
commit d9711b6031
23 changed files with 663 additions and 155 deletions

View File

@@ -26,5 +26,6 @@ public enum PushType : byte
SyncOrganizations = 17, SyncOrganizations = 17,
SyncNotification = 18, SyncNotificationCreate = 18,
SyncNotificationUpdate = 19
} }

View File

@@ -1,10 +1,11 @@
using Bit.Core.Enums; #nullable enable
using Bit.Core.Enums;
namespace Bit.Core.Models; namespace Bit.Core.Models;
public class PushNotificationData<T> public class PushNotificationData<T>
{ {
public PushNotificationData(PushType type, T payload, string contextId) public PushNotificationData(PushType type, T payload, string? contextId)
{ {
Type = type; Type = type;
Payload = payload; Payload = payload;
@@ -13,7 +14,7 @@ public class PushNotificationData<T>
public PushType Type { get; set; } public PushType Type { get; set; }
public T Payload { get; set; } public T Payload { get; set; }
public string ContextId { get; set; } public string? ContextId { get; set; }
} }
public class SyncCipherPushNotification public class SyncCipherPushNotification
@@ -21,7 +22,7 @@ public class SyncCipherPushNotification
public Guid Id { get; set; } public Guid Id { get; set; }
public Guid? UserId { get; set; } public Guid? UserId { get; set; }
public Guid? OrganizationId { get; set; } public Guid? OrganizationId { get; set; }
public IEnumerable<Guid> CollectionIds { get; set; } public IEnumerable<Guid>? CollectionIds { get; set; }
public DateTime RevisionDate { get; set; } public DateTime RevisionDate { get; set; }
} }
@@ -52,6 +53,8 @@ public class SyncNotificationPushNotification
public Guid? OrganizationId { get; set; } public Guid? OrganizationId { get; set; }
public ClientType ClientType { get; set; } public ClientType ClientType { get; set; }
public DateTime RevisionDate { get; set; } public DateTime RevisionDate { get; set; }
public DateTime? ReadDate { get; set; }
public DateTime? DeletedDate { get; set; }
} }
public class AuthRequestPushNotification public class AuthRequestPushNotification

View File

@@ -37,7 +37,7 @@ public class CreateNotificationCommand : ICreateNotificationCommand
var newNotification = await _notificationRepository.CreateAsync(notification); var newNotification = await _notificationRepository.CreateAsync(notification);
await _pushNotificationService.PushSyncNotificationAsync(newNotification); await _pushNotificationService.PushSyncNotificationCreateAsync(newNotification, null);
return newNotification; return newNotification;
} }

View File

@@ -5,6 +5,7 @@ using Bit.Core.NotificationCenter.Authorization;
using Bit.Core.NotificationCenter.Commands.Interfaces; using Bit.Core.NotificationCenter.Commands.Interfaces;
using Bit.Core.NotificationCenter.Entities; using Bit.Core.NotificationCenter.Entities;
using Bit.Core.NotificationCenter.Repositories; using Bit.Core.NotificationCenter.Repositories;
using Bit.Core.Services;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
@@ -16,16 +17,19 @@ public class CreateNotificationStatusCommand : ICreateNotificationStatusCommand
private readonly IAuthorizationService _authorizationService; private readonly IAuthorizationService _authorizationService;
private readonly INotificationRepository _notificationRepository; private readonly INotificationRepository _notificationRepository;
private readonly INotificationStatusRepository _notificationStatusRepository; private readonly INotificationStatusRepository _notificationStatusRepository;
private readonly IPushNotificationService _pushNotificationService;
public CreateNotificationStatusCommand(ICurrentContext currentContext, public CreateNotificationStatusCommand(ICurrentContext currentContext,
IAuthorizationService authorizationService, IAuthorizationService authorizationService,
INotificationRepository notificationRepository, INotificationRepository notificationRepository,
INotificationStatusRepository notificationStatusRepository) INotificationStatusRepository notificationStatusRepository,
IPushNotificationService pushNotificationService)
{ {
_currentContext = currentContext; _currentContext = currentContext;
_authorizationService = authorizationService; _authorizationService = authorizationService;
_notificationRepository = notificationRepository; _notificationRepository = notificationRepository;
_notificationStatusRepository = notificationStatusRepository; _notificationStatusRepository = notificationStatusRepository;
_pushNotificationService = pushNotificationService;
} }
public async Task<NotificationStatus> CreateAsync(NotificationStatus notificationStatus) public async Task<NotificationStatus> CreateAsync(NotificationStatus notificationStatus)
@@ -42,6 +46,10 @@ public class CreateNotificationStatusCommand : ICreateNotificationStatusCommand
await _authorizationService.AuthorizeOrThrowAsync(_currentContext.HttpContext.User, notificationStatus, await _authorizationService.AuthorizeOrThrowAsync(_currentContext.HttpContext.User, notificationStatus,
NotificationStatusOperations.Create); NotificationStatusOperations.Create);
return await _notificationStatusRepository.CreateAsync(notificationStatus); var newNotificationStatus = await _notificationStatusRepository.CreateAsync(notificationStatus);
await _pushNotificationService.PushSyncNotificationCreateAsync(notification, newNotificationStatus);
return newNotificationStatus;
} }
} }

View File

@@ -5,6 +5,7 @@ using Bit.Core.NotificationCenter.Authorization;
using Bit.Core.NotificationCenter.Commands.Interfaces; using Bit.Core.NotificationCenter.Commands.Interfaces;
using Bit.Core.NotificationCenter.Entities; using Bit.Core.NotificationCenter.Entities;
using Bit.Core.NotificationCenter.Repositories; using Bit.Core.NotificationCenter.Repositories;
using Bit.Core.Services;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
@@ -16,16 +17,19 @@ public class MarkNotificationDeletedCommand : IMarkNotificationDeletedCommand
private readonly IAuthorizationService _authorizationService; private readonly IAuthorizationService _authorizationService;
private readonly INotificationRepository _notificationRepository; private readonly INotificationRepository _notificationRepository;
private readonly INotificationStatusRepository _notificationStatusRepository; private readonly INotificationStatusRepository _notificationStatusRepository;
private readonly IPushNotificationService _pushNotificationService;
public MarkNotificationDeletedCommand(ICurrentContext currentContext, public MarkNotificationDeletedCommand(ICurrentContext currentContext,
IAuthorizationService authorizationService, IAuthorizationService authorizationService,
INotificationRepository notificationRepository, INotificationRepository notificationRepository,
INotificationStatusRepository notificationStatusRepository) INotificationStatusRepository notificationStatusRepository,
IPushNotificationService pushNotificationService)
{ {
_currentContext = currentContext; _currentContext = currentContext;
_authorizationService = authorizationService; _authorizationService = authorizationService;
_notificationRepository = notificationRepository; _notificationRepository = notificationRepository;
_notificationStatusRepository = notificationStatusRepository; _notificationStatusRepository = notificationStatusRepository;
_pushNotificationService = pushNotificationService;
} }
public async Task MarkDeletedAsync(Guid notificationId) public async Task MarkDeletedAsync(Guid notificationId)
@@ -59,7 +63,9 @@ public class MarkNotificationDeletedCommand : IMarkNotificationDeletedCommand
await _authorizationService.AuthorizeOrThrowAsync(_currentContext.HttpContext.User, notificationStatus, await _authorizationService.AuthorizeOrThrowAsync(_currentContext.HttpContext.User, notificationStatus,
NotificationStatusOperations.Create); NotificationStatusOperations.Create);
await _notificationStatusRepository.CreateAsync(notificationStatus); var newNotificationStatus = await _notificationStatusRepository.CreateAsync(notificationStatus);
await _pushNotificationService.PushSyncNotificationCreateAsync(notification, newNotificationStatus);
} }
else else
{ {
@@ -69,6 +75,8 @@ public class MarkNotificationDeletedCommand : IMarkNotificationDeletedCommand
notificationStatus.DeletedDate = DateTime.UtcNow; notificationStatus.DeletedDate = DateTime.UtcNow;
await _notificationStatusRepository.UpdateAsync(notificationStatus); await _notificationStatusRepository.UpdateAsync(notificationStatus);
await _pushNotificationService.PushSyncNotificationCreateAsync(notification, notificationStatus);
} }
} }
} }

View File

@@ -5,6 +5,7 @@ using Bit.Core.NotificationCenter.Authorization;
using Bit.Core.NotificationCenter.Commands.Interfaces; using Bit.Core.NotificationCenter.Commands.Interfaces;
using Bit.Core.NotificationCenter.Entities; using Bit.Core.NotificationCenter.Entities;
using Bit.Core.NotificationCenter.Repositories; using Bit.Core.NotificationCenter.Repositories;
using Bit.Core.Services;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
@@ -16,16 +17,19 @@ public class MarkNotificationReadCommand : IMarkNotificationReadCommand
private readonly IAuthorizationService _authorizationService; private readonly IAuthorizationService _authorizationService;
private readonly INotificationRepository _notificationRepository; private readonly INotificationRepository _notificationRepository;
private readonly INotificationStatusRepository _notificationStatusRepository; private readonly INotificationStatusRepository _notificationStatusRepository;
private readonly IPushNotificationService _pushNotificationService;
public MarkNotificationReadCommand(ICurrentContext currentContext, public MarkNotificationReadCommand(ICurrentContext currentContext,
IAuthorizationService authorizationService, IAuthorizationService authorizationService,
INotificationRepository notificationRepository, INotificationRepository notificationRepository,
INotificationStatusRepository notificationStatusRepository) INotificationStatusRepository notificationStatusRepository,
IPushNotificationService pushNotificationService)
{ {
_currentContext = currentContext; _currentContext = currentContext;
_authorizationService = authorizationService; _authorizationService = authorizationService;
_notificationRepository = notificationRepository; _notificationRepository = notificationRepository;
_notificationStatusRepository = notificationStatusRepository; _notificationStatusRepository = notificationStatusRepository;
_pushNotificationService = pushNotificationService;
} }
public async Task MarkReadAsync(Guid notificationId) public async Task MarkReadAsync(Guid notificationId)
@@ -59,7 +63,9 @@ public class MarkNotificationReadCommand : IMarkNotificationReadCommand
await _authorizationService.AuthorizeOrThrowAsync(_currentContext.HttpContext.User, notificationStatus, await _authorizationService.AuthorizeOrThrowAsync(_currentContext.HttpContext.User, notificationStatus,
NotificationStatusOperations.Create); NotificationStatusOperations.Create);
await _notificationStatusRepository.CreateAsync(notificationStatus); var newNotificationStatus = await _notificationStatusRepository.CreateAsync(notificationStatus);
await _pushNotificationService.PushSyncNotificationCreateAsync(notification, newNotificationStatus);
} }
else else
{ {
@@ -69,6 +75,8 @@ public class MarkNotificationReadCommand : IMarkNotificationReadCommand
notificationStatus.ReadDate = DateTime.UtcNow; notificationStatus.ReadDate = DateTime.UtcNow;
await _notificationStatusRepository.UpdateAsync(notificationStatus); await _notificationStatusRepository.UpdateAsync(notificationStatus);
await _pushNotificationService.PushSyncNotificationCreateAsync(notification, notificationStatus);
} }
} }
} }

View File

@@ -5,6 +5,7 @@ using Bit.Core.NotificationCenter.Authorization;
using Bit.Core.NotificationCenter.Commands.Interfaces; using Bit.Core.NotificationCenter.Commands.Interfaces;
using Bit.Core.NotificationCenter.Entities; using Bit.Core.NotificationCenter.Entities;
using Bit.Core.NotificationCenter.Repositories; using Bit.Core.NotificationCenter.Repositories;
using Bit.Core.Services;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
@@ -15,14 +16,17 @@ public class UpdateNotificationCommand : IUpdateNotificationCommand
private readonly ICurrentContext _currentContext; private readonly ICurrentContext _currentContext;
private readonly IAuthorizationService _authorizationService; private readonly IAuthorizationService _authorizationService;
private readonly INotificationRepository _notificationRepository; private readonly INotificationRepository _notificationRepository;
private readonly IPushNotificationService _pushNotificationService;
public UpdateNotificationCommand(ICurrentContext currentContext, public UpdateNotificationCommand(ICurrentContext currentContext,
IAuthorizationService authorizationService, IAuthorizationService authorizationService,
INotificationRepository notificationRepository) INotificationRepository notificationRepository,
IPushNotificationService pushNotificationService)
{ {
_currentContext = currentContext; _currentContext = currentContext;
_authorizationService = authorizationService; _authorizationService = authorizationService;
_notificationRepository = notificationRepository; _notificationRepository = notificationRepository;
_pushNotificationService = pushNotificationService;
} }
public async Task UpdateAsync(Notification notificationToUpdate) public async Task UpdateAsync(Notification notificationToUpdate)
@@ -43,5 +47,7 @@ public class UpdateNotificationCommand : IUpdateNotificationCommand
notification.RevisionDate = DateTime.UtcNow; notification.RevisionDate = DateTime.UtcNow;
await _notificationRepository.ReplaceAsync(notification); await _notificationRepository.ReplaceAsync(notification);
await _pushNotificationService.PushSyncNotificationCreateAsync(notification, null);
} }
} }

View File

@@ -1,10 +1,12 @@
using System.Text.Json; #nullable enable
using System.Text.Json;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Bit.Core.Auth.Entities; using Bit.Core.Auth.Entities;
using Bit.Core.Context; using Bit.Core.Context;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models; using Bit.Core.Models;
using Bit.Core.Models.Data; using Bit.Core.Models.Data;
using Bit.Core.NotificationCenter.Entities;
using Bit.Core.Repositories; using Bit.Core.Repositories;
using Bit.Core.Services; using Bit.Core.Services;
using Bit.Core.Tools.Entities; using Bit.Core.Tools.Entities;
@@ -50,7 +52,7 @@ public class NotificationHubPushNotificationService : IPushNotificationService
await PushCipherAsync(cipher, PushType.SyncLoginDelete, null); await PushCipherAsync(cipher, PushType.SyncLoginDelete, null);
} }
private async Task PushCipherAsync(Cipher cipher, PushType type, IEnumerable<Guid> collectionIds) private async Task PushCipherAsync(Cipher cipher, PushType type, IEnumerable<Guid>? collectionIds)
{ {
if (cipher.OrganizationId.HasValue) if (cipher.OrganizationId.HasValue)
{ {
@@ -180,7 +182,7 @@ public class NotificationHubPushNotificationService : IPushNotificationService
await PushAuthRequestAsync(authRequest, PushType.AuthRequestResponse); await PushAuthRequestAsync(authRequest, PushType.AuthRequestResponse);
} }
public async Task PushSyncNotificationAsync(Notification notification) public async Task PushSyncNotificationCreateAsync(Notification notification, NotificationStatus? notificationStatus)
{ {
var message = new SyncNotificationPushNotification var message = new SyncNotificationPushNotification
{ {
@@ -188,17 +190,44 @@ public class NotificationHubPushNotificationService : IPushNotificationService
UserId = notification.UserId, UserId = notification.UserId,
OrganizationId = notification.OrganizationId, OrganizationId = notification.OrganizationId,
ClientType = notification.ClientType, ClientType = notification.ClientType,
RevisionDate = notification.RevisionDate RevisionDate = notification.RevisionDate,
ReadDate = notificationStatus?.ReadDate,
DeletedDate = notificationStatus?.DeletedDate
}; };
if (notification.UserId.HasValue) if (notification.UserId.HasValue)
{ {
await SendPayloadToUserAsync(notification.UserId.Value, PushType.SyncNotification, message, true, await SendPayloadToUserAsync(notification.UserId.Value, PushType.SyncNotificationCreate, message, true,
notification.ClientType); notification.ClientType);
} }
else if (notification.OrganizationId.HasValue) else if (notification.OrganizationId.HasValue)
{ {
await SendPayloadToOrganizationAsync(notification.OrganizationId.Value, PushType.SyncNotification, message, await SendPayloadToOrganizationAsync(notification.OrganizationId.Value, PushType.SyncNotificationCreate, message,
true, notification.ClientType);
}
}
public async Task PushSyncNotificationUpdateAsync(Notification notification, NotificationStatus? notificationStatus)
{
var message = new SyncNotificationPushNotification
{
Id = notification.Id,
UserId = notification.UserId,
OrganizationId = notification.OrganizationId,
ClientType = notification.ClientType,
RevisionDate = notification.RevisionDate,
ReadDate = notificationStatus?.ReadDate,
DeletedDate = notificationStatus?.DeletedDate
};
if (notification.UserId.HasValue)
{
await SendPayloadToUserAsync(notification.UserId.Value, PushType.SyncNotificationUpdate, message, true,
notification.ClientType);
}
else if (notification.OrganizationId.HasValue)
{
await SendPayloadToOrganizationAsync(notification.OrganizationId.Value, PushType.SyncNotificationUpdate, message,
true, notification.ClientType); true, notification.ClientType);
} }
} }
@@ -224,8 +253,8 @@ public class NotificationHubPushNotificationService : IPushNotificationService
GetContextIdentifier(excludeCurrentContext), clientType: clientType); GetContextIdentifier(excludeCurrentContext), clientType: clientType);
} }
public async Task SendPayloadToUserAsync(string userId, PushType type, object payload, string identifier, public async Task SendPayloadToUserAsync(string userId, PushType type, object payload, string? identifier,
string deviceId = null, ClientType? clientType = null) string? deviceId = null, ClientType? clientType = null)
{ {
var tag = BuildTag($"template:payload_userId:{SanitizeTagInput(userId)}", identifier, clientType); var tag = BuildTag($"template:payload_userId:{SanitizeTagInput(userId)}", identifier, clientType);
await SendPayloadAsync(tag, type, payload); await SendPayloadAsync(tag, type, payload);
@@ -235,8 +264,8 @@ public class NotificationHubPushNotificationService : IPushNotificationService
} }
} }
public async Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string identifier, public async Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string? identifier,
string deviceId = null, ClientType? clientType = null) string? deviceId = null, ClientType? clientType = null)
{ {
var tag = BuildTag($"template:payload && organizationId:{SanitizeTagInput(orgId)}", identifier, clientType); var tag = BuildTag($"template:payload && organizationId:{SanitizeTagInput(orgId)}", identifier, clientType);
await SendPayloadAsync(tag, type, payload); await SendPayloadAsync(tag, type, payload);
@@ -246,7 +275,7 @@ public class NotificationHubPushNotificationService : IPushNotificationService
} }
} }
private string GetContextIdentifier(bool excludeCurrentContext) private string? GetContextIdentifier(bool excludeCurrentContext)
{ {
if (!excludeCurrentContext) if (!excludeCurrentContext)
{ {
@@ -254,11 +283,11 @@ public class NotificationHubPushNotificationService : IPushNotificationService
} }
var currentContext = var currentContext =
_httpContextAccessor?.HttpContext?.RequestServices.GetService(typeof(ICurrentContext)) as ICurrentContext; _httpContextAccessor.HttpContext?.RequestServices.GetService(typeof(ICurrentContext)) as ICurrentContext;
return currentContext?.DeviceIdentifier; return currentContext?.DeviceIdentifier;
} }
private string BuildTag(string tag, string identifier, ClientType? clientType) private string BuildTag(string tag, string? identifier, ClientType? clientType)
{ {
if (!string.IsNullOrWhiteSpace(identifier)) if (!string.IsNullOrWhiteSpace(identifier))
{ {

View File

@@ -1,4 +1,5 @@
using Bit.Core.Auth.Entities; #nullable enable
using Bit.Core.Auth.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.NotificationCenter.Entities; using Bit.Core.NotificationCenter.Entities;
using Bit.Core.Tools.Entities; using Bit.Core.Tools.Entities;
@@ -23,13 +24,14 @@ public interface IPushNotificationService
Task PushSyncSendCreateAsync(Send send); Task PushSyncSendCreateAsync(Send send);
Task PushSyncSendUpdateAsync(Send send); Task PushSyncSendUpdateAsync(Send send);
Task PushSyncSendDeleteAsync(Send send); Task PushSyncSendDeleteAsync(Send send);
Task PushSyncNotificationAsync(Notification notification); Task PushSyncNotificationCreateAsync(Notification notification, NotificationStatus? notificationStatus);
Task PushSyncNotificationUpdateAsync(Notification notification, NotificationStatus? notificationStatus);
Task PushAuthRequestAsync(AuthRequest authRequest); Task PushAuthRequestAsync(AuthRequest authRequest);
Task PushAuthRequestResponseAsync(AuthRequest authRequest); Task PushAuthRequestResponseAsync(AuthRequest authRequest);
Task SendPayloadToUserAsync(string userId, PushType type, object payload, string identifier, Task SendPayloadToUserAsync(string userId, PushType type, object payload, string identifier,
string deviceId = null, ClientType? clientType = null); string? deviceId = null, ClientType? clientType = null);
Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string identifier, Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string identifier,
string deviceId = null, ClientType? clientType = null); string? deviceId = null, ClientType? clientType = null);
} }

View File

@@ -1,4 +1,5 @@
using System.Text.Json; #nullable enable
using System.Text.Json;
using Azure.Storage.Queues; using Azure.Storage.Queues;
using Bit.Core.Auth.Entities; using Bit.Core.Auth.Entities;
using Bit.Core.Context; using Bit.Core.Context;
@@ -41,7 +42,7 @@ public class AzureQueuePushNotificationService : IPushNotificationService
await PushCipherAsync(cipher, PushType.SyncLoginDelete, null); await PushCipherAsync(cipher, PushType.SyncLoginDelete, null);
} }
private async Task PushCipherAsync(Cipher cipher, PushType type, IEnumerable<Guid> collectionIds) private async Task PushCipherAsync(Cipher cipher, PushType type, IEnumerable<Guid>? collectionIds)
{ {
if (cipher.OrganizationId.HasValue) if (cipher.OrganizationId.HasValue)
{ {
@@ -164,7 +165,7 @@ public class AzureQueuePushNotificationService : IPushNotificationService
await PushSendAsync(send, PushType.SyncSendDelete); await PushSendAsync(send, PushType.SyncSendDelete);
} }
public async Task PushSyncNotificationAsync(Notification notification) public async Task PushSyncNotificationCreateAsync(Notification notification, NotificationStatus? notificationStatus)
{ {
var message = new SyncNotificationPushNotification var message = new SyncNotificationPushNotification
{ {
@@ -172,10 +173,28 @@ public class AzureQueuePushNotificationService : IPushNotificationService
UserId = notification.UserId, UserId = notification.UserId,
OrganizationId = notification.OrganizationId, OrganizationId = notification.OrganizationId,
ClientType = notification.ClientType, ClientType = notification.ClientType,
RevisionDate = notification.RevisionDate RevisionDate = notification.RevisionDate,
ReadDate = notificationStatus?.ReadDate,
DeletedDate = notificationStatus?.DeletedDate
}; };
await SendMessageAsync(PushType.SyncNotification, message, true); await SendMessageAsync(PushType.SyncNotificationCreate, message, true);
}
public async Task PushSyncNotificationUpdateAsync(Notification notification, NotificationStatus? notificationStatus)
{
var message = new SyncNotificationPushNotification
{
Id = notification.Id,
UserId = notification.UserId,
OrganizationId = notification.OrganizationId,
ClientType = notification.ClientType,
RevisionDate = notification.RevisionDate,
ReadDate = notificationStatus?.ReadDate,
DeletedDate = notificationStatus?.DeletedDate
};
await SendMessageAsync(PushType.SyncNotificationUpdate, message, true);
} }
private async Task PushSendAsync(Send send, PushType type) private async Task PushSendAsync(Send send, PushType type)
@@ -201,7 +220,7 @@ public class AzureQueuePushNotificationService : IPushNotificationService
await _queueClient.SendMessageAsync(message); await _queueClient.SendMessageAsync(message);
} }
private string GetContextIdentifier(bool excludeCurrentContext) private string? GetContextIdentifier(bool excludeCurrentContext)
{ {
if (!excludeCurrentContext) if (!excludeCurrentContext)
{ {
@@ -214,14 +233,14 @@ public class AzureQueuePushNotificationService : IPushNotificationService
} }
public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string identifier, public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string identifier,
string deviceId = null, ClientType? clientType = null) string? deviceId = null, ClientType? clientType = null)
{ {
// Noop // Noop
return Task.FromResult(0); return Task.FromResult(0);
} }
public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string identifier, public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string identifier,
string deviceId = null, ClientType? clientType = null) string? deviceId = null, ClientType? clientType = null)
{ {
// Noop // Noop
return Task.FromResult(0); return Task.FromResult(0);

View File

@@ -1,4 +1,5 @@
using Bit.Core.Auth.Entities; #nullable enable
using Bit.Core.Auth.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.NotificationCenter.Entities; using Bit.Core.NotificationCenter.Entities;
using Bit.Core.Settings; using Bit.Core.Settings;
@@ -11,7 +12,7 @@ namespace Bit.Core.Services;
public class MultiServicePushNotificationService : IPushNotificationService public class MultiServicePushNotificationService : IPushNotificationService
{ {
private readonly IEnumerable<IPushNotificationService> _services; private readonly IEnumerable<IPushNotificationService>? _services;
private readonly ILogger<MultiServicePushNotificationService> _logger; private readonly ILogger<MultiServicePushNotificationService> _logger;
public MultiServicePushNotificationService( public MultiServicePushNotificationService(
@@ -23,7 +24,7 @@ public class MultiServicePushNotificationService : IPushNotificationService
_logger = logger; _logger = logger;
_logger.LogInformation("Hub services: {Services}", _services.Count()); _logger.LogInformation("Hub services: {Services}", _services.Count());
globalSettings?.NotificationHubPool?.NotificationHubs?.ForEach(hub => globalSettings.NotificationHubPool?.NotificationHubs?.ForEach(hub =>
{ {
_logger.LogInformation("HubName: {HubName}, EnableSendTracing: {EnableSendTracing}, RegistrationStartDate: {RegistrationStartDate}, RegistrationEndDate: {RegistrationEndDate}", hub.HubName, hub.EnableSendTracing, hub.RegistrationStartDate, hub.RegistrationEndDate); _logger.LogInformation("HubName: {HubName}, EnableSendTracing: {EnableSendTracing}, RegistrationStartDate: {RegistrationStartDate}, RegistrationEndDate: {RegistrationEndDate}", hub.HubName, hub.EnableSendTracing, hub.RegistrationStartDate, hub.RegistrationEndDate);
}); });
@@ -132,33 +133,43 @@ public class MultiServicePushNotificationService : IPushNotificationService
} }
public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string identifier, public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string identifier,
string deviceId = null, ClientType? clientType = null) string? deviceId = null, ClientType? clientType = null)
{ {
PushToServices((s) => s.SendPayloadToUserAsync(userId, type, payload, identifier, deviceId, clientType)); PushToServices((s) => s.SendPayloadToUserAsync(userId, type, payload, identifier, deviceId, clientType));
return Task.FromResult(0); return Task.FromResult(0);
} }
public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string identifier, public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string identifier,
string deviceId = null, ClientType? clientType = null) string? deviceId = null, ClientType? clientType = null)
{ {
PushToServices((s) => s.SendPayloadToOrganizationAsync(orgId, type, payload, identifier, deviceId, clientType)); PushToServices((s) => s.SendPayloadToOrganizationAsync(orgId, type, payload, identifier, deviceId, clientType));
return Task.FromResult(0); return Task.FromResult(0);
} }
public Task PushSyncNotificationAsync(Notification notification) public Task PushSyncNotificationCreateAsync(Notification notification, NotificationStatus? notificationStatus)
{ {
PushToServices((s) => s.PushSyncNotificationAsync(notification)); PushToServices((s) => s.PushSyncNotificationCreateAsync(notification, notificationStatus));
return Task.CompletedTask;
}
public Task PushSyncNotificationUpdateAsync(Notification notification, NotificationStatus? notificationStatus)
{
PushToServices((s) => s.PushSyncNotificationUpdateAsync(notification, notificationStatus));
return Task.CompletedTask; return Task.CompletedTask;
} }
private void PushToServices(Func<IPushNotificationService, Task> pushFunc) private void PushToServices(Func<IPushNotificationService, Task> pushFunc)
{ {
if (_services != null) if (_services == null)
{ {
foreach (var service in _services) _logger.LogWarning("No services found to push notification");
{ return;
pushFunc(service); }
}
foreach (var service in _services)
{
_logger.LogDebug("Pushing notification to service {}", service.GetType().Name);
pushFunc(service);
} }
} }
} }

View File

@@ -1,4 +1,5 @@
using Bit.Core.Auth.Entities; #nullable enable
using Bit.Core.Auth.Entities;
using Bit.Core.Context; using Bit.Core.Context;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.Models; using Bit.Core.Models;
@@ -13,7 +14,6 @@ namespace Bit.Core.Services;
public class NotificationsApiPushNotificationService : BaseIdentityClientService, IPushNotificationService public class NotificationsApiPushNotificationService : BaseIdentityClientService, IPushNotificationService
{ {
private readonly GlobalSettings _globalSettings;
private readonly IHttpContextAccessor _httpContextAccessor; private readonly IHttpContextAccessor _httpContextAccessor;
public NotificationsApiPushNotificationService( public NotificationsApiPushNotificationService(
@@ -30,7 +30,6 @@ public class NotificationsApiPushNotificationService : BaseIdentityClientService
globalSettings.InternalIdentityKey, globalSettings.InternalIdentityKey,
logger) logger)
{ {
_globalSettings = globalSettings;
_httpContextAccessor = httpContextAccessor; _httpContextAccessor = httpContextAccessor;
} }
@@ -49,7 +48,7 @@ public class NotificationsApiPushNotificationService : BaseIdentityClientService
await PushCipherAsync(cipher, PushType.SyncLoginDelete, null); await PushCipherAsync(cipher, PushType.SyncLoginDelete, null);
} }
private async Task PushCipherAsync(Cipher cipher, PushType type, IEnumerable<Guid> collectionIds) private async Task PushCipherAsync(Cipher cipher, PushType type, IEnumerable<Guid>? collectionIds)
{ {
if (cipher.OrganizationId.HasValue) if (cipher.OrganizationId.HasValue)
{ {
@@ -173,7 +172,7 @@ public class NotificationsApiPushNotificationService : BaseIdentityClientService
await PushSendAsync(send, PushType.SyncSendDelete); await PushSendAsync(send, PushType.SyncSendDelete);
} }
public async Task PushSyncNotificationAsync(Notification notification) public async Task PushSyncNotificationCreateAsync(Notification notification, NotificationStatus? notificationStatus)
{ {
var message = new SyncNotificationPushNotification var message = new SyncNotificationPushNotification
{ {
@@ -181,10 +180,28 @@ public class NotificationsApiPushNotificationService : BaseIdentityClientService
UserId = notification.UserId, UserId = notification.UserId,
OrganizationId = notification.OrganizationId, OrganizationId = notification.OrganizationId,
ClientType = notification.ClientType, ClientType = notification.ClientType,
RevisionDate = notification.RevisionDate RevisionDate = notification.RevisionDate,
ReadDate = notificationStatus?.ReadDate,
DeletedDate = notificationStatus?.DeletedDate
}; };
await SendMessageAsync(PushType.SyncNotification, message, true); await SendMessageAsync(PushType.SyncNotificationCreate, message, true);
}
public async Task PushSyncNotificationUpdateAsync(Notification notification, NotificationStatus? notificationStatus)
{
var message = new SyncNotificationPushNotification
{
Id = notification.Id,
UserId = notification.UserId,
OrganizationId = notification.OrganizationId,
ClientType = notification.ClientType,
RevisionDate = notification.RevisionDate,
ReadDate = notificationStatus?.ReadDate,
DeletedDate = notificationStatus?.DeletedDate
};
await SendMessageAsync(PushType.SyncNotificationUpdate, message, true);
} }
private async Task PushSendAsync(Send send, PushType type) private async Task PushSendAsync(Send send, PushType type)
@@ -209,7 +226,7 @@ public class NotificationsApiPushNotificationService : BaseIdentityClientService
await SendAsync(HttpMethod.Post, "send", request); await SendAsync(HttpMethod.Post, "send", request);
} }
private string GetContextIdentifier(bool excludeCurrentContext) private string? GetContextIdentifier(bool excludeCurrentContext)
{ {
if (!excludeCurrentContext) if (!excludeCurrentContext)
{ {
@@ -217,19 +234,19 @@ public class NotificationsApiPushNotificationService : BaseIdentityClientService
} }
var currentContext = var currentContext =
_httpContextAccessor?.HttpContext?.RequestServices.GetService(typeof(ICurrentContext)) as ICurrentContext; _httpContextAccessor.HttpContext?.RequestServices.GetService(typeof(ICurrentContext)) as ICurrentContext;
return currentContext?.DeviceIdentifier; return currentContext?.DeviceIdentifier;
} }
public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string identifier, public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string identifier,
string deviceId = null, ClientType? clientType = null) string? deviceId = null, ClientType? clientType = null)
{ {
// Noop // Noop
return Task.FromResult(0); return Task.FromResult(0);
} }
public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string identifier, public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string identifier,
string deviceId = null, ClientType? clientType = null) string? deviceId = null, ClientType? clientType = null)
{ {
// Noop // Noop
return Task.FromResult(0); return Task.FromResult(0);

View File

@@ -1,4 +1,5 @@
using Bit.Core.Auth.Entities; #nullable enable
using Bit.Core.Auth.Entities;
using Bit.Core.Context; using Bit.Core.Context;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.IdentityServer; using Bit.Core.IdentityServer;
@@ -53,7 +54,7 @@ public class RelayPushNotificationService : BaseIdentityClientService, IPushNoti
await PushCipherAsync(cipher, PushType.SyncLoginDelete, null); await PushCipherAsync(cipher, PushType.SyncLoginDelete, null);
} }
private async Task PushCipherAsync(Cipher cipher, PushType type, IEnumerable<Guid> collectionIds) private async Task PushCipherAsync(Cipher cipher, PushType type, IEnumerable<Guid>? collectionIds)
{ {
if (cipher.OrganizationId.HasValue) if (cipher.OrganizationId.HasValue)
{ {
@@ -189,7 +190,7 @@ public class RelayPushNotificationService : BaseIdentityClientService, IPushNoti
await SendPayloadToUserAsync(authRequest.UserId, type, message, true); await SendPayloadToUserAsync(authRequest.UserId, type, message, true);
} }
public async Task PushSyncNotificationAsync(Notification notification) public async Task PushSyncNotificationCreateAsync(Notification notification, NotificationStatus? notificationStatus)
{ {
var message = new SyncNotificationPushNotification var message = new SyncNotificationPushNotification
{ {
@@ -197,17 +198,44 @@ public class RelayPushNotificationService : BaseIdentityClientService, IPushNoti
UserId = notification.UserId, UserId = notification.UserId,
OrganizationId = notification.OrganizationId, OrganizationId = notification.OrganizationId,
ClientType = notification.ClientType, ClientType = notification.ClientType,
RevisionDate = notification.RevisionDate RevisionDate = notification.RevisionDate,
ReadDate = notificationStatus?.ReadDate,
DeletedDate = notificationStatus?.DeletedDate
}; };
if (notification.UserId.HasValue) if (notification.UserId.HasValue)
{ {
await SendPayloadToUserAsync(notification.UserId.Value, PushType.SyncNotification, message, true, await SendPayloadToUserAsync(notification.UserId.Value, PushType.SyncNotificationCreate, message, true,
notification.ClientType); notification.ClientType);
} }
else if (notification.OrganizationId.HasValue) else if (notification.OrganizationId.HasValue)
{ {
await SendPayloadToOrganizationAsync(notification.OrganizationId.Value, PushType.SyncNotification, message, await SendPayloadToOrganizationAsync(notification.OrganizationId.Value, PushType.SyncNotificationCreate, message,
true, notification.ClientType);
}
}
public async Task PushSyncNotificationUpdateAsync(Notification notification, NotificationStatus? notificationStatus)
{
var message = new SyncNotificationPushNotification
{
Id = notification.Id,
UserId = notification.UserId,
OrganizationId = notification.OrganizationId,
ClientType = notification.ClientType,
RevisionDate = notification.RevisionDate,
ReadDate = notificationStatus?.ReadDate,
DeletedDate = notificationStatus?.DeletedDate
};
if (notification.UserId.HasValue)
{
await SendPayloadToUserAsync(notification.UserId.Value, PushType.SyncNotificationUpdate, message, true,
notification.ClientType);
}
else if (notification.OrganizationId.HasValue)
{
await SendPayloadToOrganizationAsync(notification.OrganizationId.Value, PushType.SyncNotificationUpdate, message,
true, notification.ClientType); true, notification.ClientType);
} }
} }
@@ -245,7 +273,7 @@ public class RelayPushNotificationService : BaseIdentityClientService, IPushNoti
private async Task AddCurrentContextAsync(PushSendRequestModel request, bool addIdentifier) private async Task AddCurrentContextAsync(PushSendRequestModel request, bool addIdentifier)
{ {
var currentContext = var currentContext =
_httpContextAccessor?.HttpContext?.RequestServices.GetService(typeof(ICurrentContext)) as ICurrentContext; _httpContextAccessor.HttpContext?.RequestServices.GetService(typeof(ICurrentContext)) as ICurrentContext;
if (!string.IsNullOrWhiteSpace(currentContext?.DeviceIdentifier)) if (!string.IsNullOrWhiteSpace(currentContext?.DeviceIdentifier))
{ {
var device = await _deviceRepository.GetByIdentifierAsync(currentContext.DeviceIdentifier); var device = await _deviceRepository.GetByIdentifierAsync(currentContext.DeviceIdentifier);
@@ -262,13 +290,13 @@ public class RelayPushNotificationService : BaseIdentityClientService, IPushNoti
} }
public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string identifier, public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string identifier,
string deviceId = null, ClientType? clientType = null) string? deviceId = null, ClientType? clientType = null)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string identifier, public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string identifier,
string deviceId = null, ClientType? clientType = null) string? deviceId = null, ClientType? clientType = null)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }

View File

@@ -1,4 +1,5 @@
using Bit.Core.Auth.Entities; #nullable enable
using Bit.Core.Auth.Entities;
using Bit.Core.Enums; using Bit.Core.Enums;
using Bit.Core.NotificationCenter.Entities; using Bit.Core.NotificationCenter.Entities;
using Bit.Core.Tools.Entities; using Bit.Core.Tools.Entities;
@@ -84,7 +85,7 @@ public class NoopPushNotificationService : IPushNotificationService
} }
public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string identifier, public Task SendPayloadToOrganizationAsync(string orgId, PushType type, object payload, string identifier,
string deviceId = null, ClientType? clientType = null) string? deviceId = null, ClientType? clientType = null)
{ {
return Task.FromResult(0); return Task.FromResult(0);
} }
@@ -100,10 +101,12 @@ public class NoopPushNotificationService : IPushNotificationService
} }
public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string identifier, public Task SendPayloadToUserAsync(string userId, PushType type, object payload, string identifier,
string deviceId = null, ClientType? clientType = null) string? deviceId = null, ClientType? clientType = null)
{ {
return Task.FromResult(0); return Task.FromResult(0);
} }
public Task PushSyncNotificationAsync(Notification notification) => Task.CompletedTask; public Task PushSyncNotificationCreateAsync(Notification notification, NotificationStatus? notificationStatus) => Task.CompletedTask;
public Task PushSyncNotificationUpdateAsync(Notification notification, NotificationStatus? notificationStatus) => Task.CompletedTask;
} }

View File

@@ -89,7 +89,8 @@ public static class HubHelpers
await hubContext.Clients.User(authRequestNotification.Payload.UserId.ToString()) await hubContext.Clients.User(authRequestNotification.Payload.UserId.ToString())
.SendAsync(_receiveMessageMethod, authRequestNotification, cancellationToken); .SendAsync(_receiveMessageMethod, authRequestNotification, cancellationToken);
break; break;
case PushType.SyncNotification: case PushType.SyncNotificationCreate:
case PushType.SyncNotificationUpdate:
var syncNotification = var syncNotification =
JsonSerializer.Deserialize<PushNotificationData<SyncNotificationPushNotification>>( JsonSerializer.Deserialize<PushNotificationData<SyncNotificationPushNotification>>(
notificationJson, _deserializerOptions); notificationJson, _deserializerOptions);

View File

@@ -41,6 +41,9 @@ public class CreateNotificationCommandTest
Setup(sutProvider, notification, authorized: false); Setup(sutProvider, notification, authorized: false);
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.CreateAsync(notification)); await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.CreateAsync(notification));
await sutProvider.GetDependency<IPushNotificationService>()
.Received(0)
.PushSyncNotificationCreateAsync(Arg.Any<Notification>(), Arg.Any<NotificationStatus?>());
} }
[Theory] [Theory]
@@ -58,6 +61,6 @@ public class CreateNotificationCommandTest
Assert.Equal(notification.CreationDate, notification.RevisionDate); Assert.Equal(notification.CreationDate, notification.RevisionDate);
await sutProvider.GetDependency<IPushNotificationService>() await sutProvider.GetDependency<IPushNotificationService>()
.Received(1) .Received(1)
.PushSyncNotificationAsync(newNotification); .PushSyncNotificationCreateAsync(newNotification, null);
} }
} }

View File

@@ -5,6 +5,7 @@ using Bit.Core.NotificationCenter.Authorization;
using Bit.Core.NotificationCenter.Commands; using Bit.Core.NotificationCenter.Commands;
using Bit.Core.NotificationCenter.Entities; using Bit.Core.NotificationCenter.Entities;
using Bit.Core.NotificationCenter.Repositories; using Bit.Core.NotificationCenter.Repositories;
using Bit.Core.Services;
using Bit.Core.Test.NotificationCenter.AutoFixture; using Bit.Core.Test.NotificationCenter.AutoFixture;
using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes; using Bit.Test.Common.AutoFixture.Attributes;
@@ -50,6 +51,9 @@ public class CreateNotificationStatusCommandTest
Setup(sutProvider, notification: null, notificationStatus, true, true); Setup(sutProvider, notification: null, notificationStatus, true, true);
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.CreateAsync(notificationStatus)); await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.CreateAsync(notificationStatus));
await sutProvider.GetDependency<IPushNotificationService>()
.Received(0)
.PushSyncNotificationCreateAsync(Arg.Any<Notification>(), Arg.Any<NotificationStatus?>());
} }
[Theory] [Theory]
@@ -61,6 +65,9 @@ public class CreateNotificationStatusCommandTest
Setup(sutProvider, notification, notificationStatus, authorizedNotification: false, true); Setup(sutProvider, notification, notificationStatus, authorizedNotification: false, true);
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.CreateAsync(notificationStatus)); await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.CreateAsync(notificationStatus));
await sutProvider.GetDependency<IPushNotificationService>()
.Received(0)
.PushSyncNotificationCreateAsync(Arg.Any<Notification>(), Arg.Any<NotificationStatus?>());
} }
[Theory] [Theory]
@@ -72,6 +79,9 @@ public class CreateNotificationStatusCommandTest
Setup(sutProvider, notification, notificationStatus, true, authorizedCreate: false); Setup(sutProvider, notification, notificationStatus, true, authorizedCreate: false);
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.CreateAsync(notificationStatus)); await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.CreateAsync(notificationStatus));
await sutProvider.GetDependency<IPushNotificationService>()
.Received(0)
.PushSyncNotificationCreateAsync(Arg.Any<Notification>(), Arg.Any<NotificationStatus?>());
} }
[Theory] [Theory]
@@ -85,5 +95,8 @@ public class CreateNotificationStatusCommandTest
var newNotificationStatus = await sutProvider.Sut.CreateAsync(notificationStatus); var newNotificationStatus = await sutProvider.Sut.CreateAsync(notificationStatus);
Assert.Equal(notificationStatus, newNotificationStatus); Assert.Equal(notificationStatus, newNotificationStatus);
await sutProvider.GetDependency<IPushNotificationService>()
.Received(1)
.PushSyncNotificationCreateAsync(notification, notificationStatus);
} }
} }

View File

@@ -6,6 +6,7 @@ using Bit.Core.NotificationCenter.Authorization;
using Bit.Core.NotificationCenter.Commands; using Bit.Core.NotificationCenter.Commands;
using Bit.Core.NotificationCenter.Entities; using Bit.Core.NotificationCenter.Entities;
using Bit.Core.NotificationCenter.Repositories; using Bit.Core.NotificationCenter.Repositories;
using Bit.Core.Services;
using Bit.Core.Test.NotificationCenter.AutoFixture; using Bit.Core.Test.NotificationCenter.AutoFixture;
using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes; using Bit.Test.Common.AutoFixture.Attributes;
@@ -63,6 +64,9 @@ public class MarkNotificationDeletedCommandTest
Setup(sutProvider, notificationId, userId: null, notification, notificationStatus, true, true, true); Setup(sutProvider, notificationId, userId: null, notification, notificationStatus, true, true, true);
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.MarkDeletedAsync(notificationId)); await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.MarkDeletedAsync(notificationId));
await sutProvider.GetDependency<IPushNotificationService>()
.Received(0)
.PushSyncNotificationCreateAsync(Arg.Any<Notification>(), Arg.Any<NotificationStatus?>());
} }
[Theory] [Theory]
@@ -74,6 +78,9 @@ public class MarkNotificationDeletedCommandTest
Setup(sutProvider, notificationId, userId, notification: null, notificationStatus, true, true, true); Setup(sutProvider, notificationId, userId, notification: null, notificationStatus, true, true, true);
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.MarkDeletedAsync(notificationId)); await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.MarkDeletedAsync(notificationId));
await sutProvider.GetDependency<IPushNotificationService>()
.Received(0)
.PushSyncNotificationCreateAsync(Arg.Any<Notification>(), Arg.Any<NotificationStatus?>());
} }
[Theory] [Theory]
@@ -86,6 +93,9 @@ public class MarkNotificationDeletedCommandTest
true, true); true, true);
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.MarkDeletedAsync(notificationId)); await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.MarkDeletedAsync(notificationId));
await sutProvider.GetDependency<IPushNotificationService>()
.Received(0)
.PushSyncNotificationCreateAsync(Arg.Any<Notification>(), Arg.Any<NotificationStatus?>());
} }
[Theory] [Theory]
@@ -98,6 +108,9 @@ public class MarkNotificationDeletedCommandTest
authorizedCreate: false, true); authorizedCreate: false, true);
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.MarkDeletedAsync(notificationId)); await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.MarkDeletedAsync(notificationId));
await sutProvider.GetDependency<IPushNotificationService>()
.Received(0)
.PushSyncNotificationCreateAsync(Arg.Any<Notification>(), Arg.Any<NotificationStatus?>());
} }
[Theory] [Theory]
@@ -110,6 +123,9 @@ public class MarkNotificationDeletedCommandTest
authorizedUpdate: false); authorizedUpdate: false);
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.MarkDeletedAsync(notificationId)); await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.MarkDeletedAsync(notificationId));
await sutProvider.GetDependency<IPushNotificationService>()
.Received(0)
.PushSyncNotificationCreateAsync(Arg.Any<Notification>(), Arg.Any<NotificationStatus?>());
} }
[Theory] [Theory]
@@ -119,13 +135,22 @@ public class MarkNotificationDeletedCommandTest
Guid notificationId, Guid userId, Notification notification) Guid notificationId, Guid userId, Notification notification)
{ {
Setup(sutProvider, notificationId, userId, notification, notificationStatus: null, true, true, true); Setup(sutProvider, notificationId, userId, notification, notificationStatus: null, true, true, true);
var expectedNotificationStatus = new NotificationStatus
{
NotificationId = notificationId,
UserId = userId,
ReadDate = null,
DeletedDate = DateTime.UtcNow
};
await sutProvider.Sut.MarkDeletedAsync(notificationId); await sutProvider.Sut.MarkDeletedAsync(notificationId);
await sutProvider.GetDependency<INotificationStatusRepository>().Received(1) await sutProvider.GetDependency<INotificationStatusRepository>().Received(1)
.CreateAsync(Arg.Is<NotificationStatus>(ns => .CreateAsync(Arg.Do<NotificationStatus>(ns => AssertNotificationStatus(expectedNotificationStatus, ns)));
ns.NotificationId == notificationId && ns.UserId == userId && !ns.ReadDate.HasValue && await sutProvider.GetDependency<IPushNotificationService>()
ns.DeletedDate.HasValue && DateTime.UtcNow - ns.DeletedDate.Value < TimeSpan.FromMinutes(1))); .Received(1)
.PushSyncNotificationCreateAsync(notification,
Arg.Do<NotificationStatus>(ns => AssertNotificationStatus(expectedNotificationStatus, ns)));
} }
[Theory] [Theory]
@@ -134,18 +159,27 @@ public class MarkNotificationDeletedCommandTest
SutProvider<MarkNotificationDeletedCommand> sutProvider, SutProvider<MarkNotificationDeletedCommand> sutProvider,
Guid notificationId, Guid userId, Notification notification, NotificationStatus notificationStatus) Guid notificationId, Guid userId, Notification notification, NotificationStatus notificationStatus)
{ {
var deletedDate = notificationStatus.DeletedDate;
Setup(sutProvider, notificationId, userId, notification, notificationStatus, true, true, true); Setup(sutProvider, notificationId, userId, notification, notificationStatus, true, true, true);
await sutProvider.Sut.MarkDeletedAsync(notificationId); await sutProvider.Sut.MarkDeletedAsync(notificationId);
await sutProvider.GetDependency<INotificationStatusRepository>().Received(1) await sutProvider.GetDependency<INotificationStatusRepository>().Received(1)
.UpdateAsync(Arg.Is<NotificationStatus>(ns => .UpdateAsync(Arg.Do<NotificationStatus>(ns => AssertNotificationStatus(notificationStatus, ns)));
ns.Equals(notificationStatus) && await sutProvider.GetDependency<IPushNotificationService>()
ns.NotificationId == notificationStatus.NotificationId && ns.UserId == notificationStatus.UserId && .Received(1)
ns.ReadDate == notificationStatus.ReadDate && ns.DeletedDate != deletedDate && .PushSyncNotificationCreateAsync(notification,
ns.DeletedDate.HasValue && Arg.Do<NotificationStatus?>(ns => AssertNotificationStatus(notificationStatus, ns)));
DateTime.UtcNow - ns.DeletedDate.Value < TimeSpan.FromMinutes(1))); }
private static void AssertNotificationStatus(NotificationStatus expectedNotificationStatus,
NotificationStatus? actualNotificationStatus)
{
Assert.NotNull(actualNotificationStatus);
Assert.Equal(expectedNotificationStatus.NotificationId, actualNotificationStatus.NotificationId);
Assert.Equal(expectedNotificationStatus.UserId, actualNotificationStatus.UserId);
Assert.Equal(expectedNotificationStatus.ReadDate, actualNotificationStatus.ReadDate);
Assert.NotEqual(expectedNotificationStatus.DeletedDate, actualNotificationStatus.DeletedDate);
Assert.NotNull(actualNotificationStatus.DeletedDate);
Assert.Equal(DateTime.UtcNow, actualNotificationStatus.DeletedDate.Value, TimeSpan.FromMinutes(1));
} }
} }

View File

@@ -6,6 +6,7 @@ using Bit.Core.NotificationCenter.Authorization;
using Bit.Core.NotificationCenter.Commands; using Bit.Core.NotificationCenter.Commands;
using Bit.Core.NotificationCenter.Entities; using Bit.Core.NotificationCenter.Entities;
using Bit.Core.NotificationCenter.Repositories; using Bit.Core.NotificationCenter.Repositories;
using Bit.Core.Services;
using Bit.Core.Test.NotificationCenter.AutoFixture; using Bit.Core.Test.NotificationCenter.AutoFixture;
using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture;
using Bit.Test.Common.AutoFixture.Attributes; using Bit.Test.Common.AutoFixture.Attributes;
@@ -63,6 +64,9 @@ public class MarkNotificationReadCommandTest
Setup(sutProvider, notificationId, userId: null, notification, notificationStatus, true, true, true); Setup(sutProvider, notificationId, userId: null, notification, notificationStatus, true, true, true);
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.MarkReadAsync(notificationId)); await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.MarkReadAsync(notificationId));
await sutProvider.GetDependency<IPushNotificationService>()
.Received(0)
.PushSyncNotificationCreateAsync(Arg.Any<Notification>(), Arg.Any<NotificationStatus?>());
} }
[Theory] [Theory]
@@ -74,6 +78,9 @@ public class MarkNotificationReadCommandTest
Setup(sutProvider, notificationId, userId, notification: null, notificationStatus, true, true, true); Setup(sutProvider, notificationId, userId, notification: null, notificationStatus, true, true, true);
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.MarkReadAsync(notificationId)); await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.MarkReadAsync(notificationId));
await sutProvider.GetDependency<IPushNotificationService>()
.Received(0)
.PushSyncNotificationCreateAsync(Arg.Any<Notification>(), Arg.Any<NotificationStatus?>());
} }
[Theory] [Theory]
@@ -86,6 +93,9 @@ public class MarkNotificationReadCommandTest
true, true); true, true);
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.MarkReadAsync(notificationId)); await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.MarkReadAsync(notificationId));
await sutProvider.GetDependency<IPushNotificationService>()
.Received(0)
.PushSyncNotificationCreateAsync(Arg.Any<Notification>(), Arg.Any<NotificationStatus?>());
} }
[Theory] [Theory]
@@ -98,6 +108,9 @@ public class MarkNotificationReadCommandTest
authorizedCreate: false, true); authorizedCreate: false, true);
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.MarkReadAsync(notificationId)); await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.MarkReadAsync(notificationId));
await sutProvider.GetDependency<IPushNotificationService>()
.Received(0)
.PushSyncNotificationCreateAsync(Arg.Any<Notification>(), Arg.Any<NotificationStatus?>());
} }
[Theory] [Theory]
@@ -110,6 +123,9 @@ public class MarkNotificationReadCommandTest
authorizedUpdate: false); authorizedUpdate: false);
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.MarkReadAsync(notificationId)); await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.MarkReadAsync(notificationId));
await sutProvider.GetDependency<IPushNotificationService>()
.Received(0)
.PushSyncNotificationCreateAsync(Arg.Any<Notification>(), Arg.Any<NotificationStatus?>());
} }
[Theory] [Theory]
@@ -119,13 +135,22 @@ public class MarkNotificationReadCommandTest
Guid notificationId, Guid userId, Notification notification) Guid notificationId, Guid userId, Notification notification)
{ {
Setup(sutProvider, notificationId, userId, notification, notificationStatus: null, true, true, true); Setup(sutProvider, notificationId, userId, notification, notificationStatus: null, true, true, true);
var expectedNotificationStatus = new NotificationStatus
{
NotificationId = notificationId,
UserId = userId,
ReadDate = DateTime.UtcNow,
DeletedDate = null
};
await sutProvider.Sut.MarkReadAsync(notificationId); await sutProvider.Sut.MarkReadAsync(notificationId);
await sutProvider.GetDependency<INotificationStatusRepository>().Received(1) await sutProvider.GetDependency<INotificationStatusRepository>().Received(1)
.CreateAsync(Arg.Is<NotificationStatus>(ns => .CreateAsync(Arg.Do<NotificationStatus>(ns => AssertNotificationStatus(expectedNotificationStatus, ns)));
ns.NotificationId == notificationId && ns.UserId == userId && !ns.DeletedDate.HasValue && await sutProvider.GetDependency<IPushNotificationService>()
ns.ReadDate.HasValue && DateTime.UtcNow - ns.ReadDate.Value < TimeSpan.FromMinutes(1))); .Received(1)
.PushSyncNotificationCreateAsync(notification,
Arg.Do<NotificationStatus>(ns => AssertNotificationStatus(expectedNotificationStatus, ns)));
} }
[Theory] [Theory]
@@ -134,18 +159,27 @@ public class MarkNotificationReadCommandTest
SutProvider<MarkNotificationReadCommand> sutProvider, SutProvider<MarkNotificationReadCommand> sutProvider,
Guid notificationId, Guid userId, Notification notification, NotificationStatus notificationStatus) Guid notificationId, Guid userId, Notification notification, NotificationStatus notificationStatus)
{ {
var readDate = notificationStatus.ReadDate;
Setup(sutProvider, notificationId, userId, notification, notificationStatus, true, true, true); Setup(sutProvider, notificationId, userId, notification, notificationStatus, true, true, true);
await sutProvider.Sut.MarkReadAsync(notificationId); await sutProvider.Sut.MarkReadAsync(notificationId);
await sutProvider.GetDependency<INotificationStatusRepository>().Received(1) await sutProvider.GetDependency<INotificationStatusRepository>().Received(1)
.UpdateAsync(Arg.Is<NotificationStatus>(ns => .UpdateAsync(Arg.Do<NotificationStatus>(ns => AssertNotificationStatus(notificationStatus, ns)));
ns.Equals(notificationStatus) && await sutProvider.GetDependency<IPushNotificationService>()
ns.NotificationId == notificationStatus.NotificationId && ns.UserId == notificationStatus.UserId && .Received(1)
ns.DeletedDate == notificationStatus.DeletedDate && ns.ReadDate != readDate && .PushSyncNotificationCreateAsync(notification,
ns.ReadDate.HasValue && Arg.Do<NotificationStatus?>(ns => AssertNotificationStatus(notificationStatus, ns)));
DateTime.UtcNow - ns.ReadDate.Value < TimeSpan.FromMinutes(1))); }
private static void AssertNotificationStatus(NotificationStatus expectedNotificationStatus,
NotificationStatus? actualNotificationStatus)
{
Assert.NotNull(actualNotificationStatus);
Assert.Equal(expectedNotificationStatus.NotificationId, actualNotificationStatus.NotificationId);
Assert.Equal(expectedNotificationStatus.UserId, actualNotificationStatus.UserId);
Assert.NotEqual(expectedNotificationStatus.ReadDate, actualNotificationStatus.ReadDate);
Assert.NotNull(actualNotificationStatus.ReadDate);
Assert.Equal(DateTime.UtcNow, actualNotificationStatus.ReadDate.Value, TimeSpan.FromMinutes(1));
Assert.Equal(expectedNotificationStatus.DeletedDate, actualNotificationStatus.DeletedDate);
} }
} }

View File

@@ -7,6 +7,7 @@ using Bit.Core.NotificationCenter.Commands;
using Bit.Core.NotificationCenter.Entities; using Bit.Core.NotificationCenter.Entities;
using Bit.Core.NotificationCenter.Enums; using Bit.Core.NotificationCenter.Enums;
using Bit.Core.NotificationCenter.Repositories; using Bit.Core.NotificationCenter.Repositories;
using Bit.Core.Services;
using Bit.Core.Test.NotificationCenter.AutoFixture; using Bit.Core.Test.NotificationCenter.AutoFixture;
using Bit.Core.Utilities; using Bit.Core.Utilities;
using Bit.Test.Common.AutoFixture; using Bit.Test.Common.AutoFixture;
@@ -45,6 +46,9 @@ public class UpdateNotificationCommandTest
Setup(sutProvider, notification.Id, notification: null, true); Setup(sutProvider, notification.Id, notification: null, true);
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.UpdateAsync(notification)); await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.UpdateAsync(notification));
await sutProvider.GetDependency<IPushNotificationService>()
.Received(0)
.PushSyncNotificationCreateAsync(Arg.Any<Notification>(), Arg.Any<NotificationStatus?>());
} }
[Theory] [Theory]
@@ -56,6 +60,9 @@ public class UpdateNotificationCommandTest
Setup(sutProvider, notification.Id, notification, authorized: false); Setup(sutProvider, notification.Id, notification, authorized: false);
await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.UpdateAsync(notification)); await Assert.ThrowsAsync<NotFoundException>(() => sutProvider.Sut.UpdateAsync(notification));
await sutProvider.GetDependency<IPushNotificationService>()
.Received(0)
.PushSyncNotificationCreateAsync(Arg.Any<Notification>(), Arg.Any<NotificationStatus?>());
} }
[Theory] [Theory]
@@ -91,5 +98,8 @@ public class UpdateNotificationCommandTest
n.Priority == notificationToUpdate.Priority && n.ClientType == notificationToUpdate.ClientType && n.Priority == notificationToUpdate.Priority && n.ClientType == notificationToUpdate.ClientType &&
n.Title == notificationToUpdate.Title && n.Body == notificationToUpdate.Body && n.Title == notificationToUpdate.Title && n.Body == notificationToUpdate.Body &&
DateTime.UtcNow - n.RevisionDate < TimeSpan.FromMinutes(1))); DateTime.UtcNow - n.RevisionDate < TimeSpan.FromMinutes(1)));
await sutProvider.GetDependency<IPushNotificationService>()
.Received(1)
.PushSyncNotificationCreateAsync(notification, null);
} }
} }

View File

@@ -15,15 +15,19 @@ using Xunit;
namespace Bit.Core.Test.NotificationHub; namespace Bit.Core.Test.NotificationHub;
[SutProviderCustomize] [SutProviderCustomize]
[NotificationStatusCustomize]
public class NotificationHubPushNotificationServiceTests public class NotificationHubPushNotificationServiceTests
{ {
[Theory] [Theory]
[BitAutoData] [BitAutoData(false)]
[BitAutoData(true)]
[NotificationCustomize] [NotificationCustomize]
public async void PushSyncNotificationAsync_Global_NotSent( public async Task PushSyncNotificationCreateAsync_Global_NotSent(bool notificationStatusNull,
SutProvider<NotificationHubPushNotificationService> sutProvider, Notification notification) SutProvider<NotificationHubPushNotificationService> sutProvider, Notification notification,
NotificationStatus notificationStatus)
{ {
await sutProvider.Sut.PushSyncNotificationAsync(notification); await sutProvider.Sut.PushSyncNotificationCreateAsync(notification,
notificationStatusNull ? null : notificationStatus);
await sutProvider.GetDependency<INotificationHubPool>() await sutProvider.GetDependency<INotificationHubPool>()
.Received(0) .Received(0)
@@ -36,12 +40,15 @@ public class NotificationHubPushNotificationServiceTests
} }
[Theory] [Theory]
[BitAutoData(false)] [BitAutoData(false, false)]
[BitAutoData(true)] [BitAutoData(false, true)]
[BitAutoData(true, false)]
[BitAutoData(true, true)]
[NotificationCustomize(false)] [NotificationCustomize(false)]
public async void PushSyncNotificationAsync_UserIdProvidedClientTypeAll_SentToUser( public async Task PushSyncNotificationCreateAsync_UserIdProvidedClientTypeAll_SentToUser(
bool organizationIdNull, SutProvider<NotificationHubPushNotificationService> sutProvider, bool organizationIdNull, bool notificationStatusNull,
Notification notification) SutProvider<NotificationHubPushNotificationService> sutProvider,
Notification notification, NotificationStatus notificationStatus)
{ {
if (organizationIdNull) if (organizationIdNull)
{ {
@@ -49,11 +56,13 @@ public class NotificationHubPushNotificationServiceTests
} }
notification.ClientType = ClientType.All; notification.ClientType = ClientType.All;
var expectedSyncNotification = ToSyncNotificationPushNotification(notification); var expectedNotificationStatus = notificationStatusNull ? null : notificationStatus;
var expectedSyncNotification = ToSyncNotificationPushNotification(notification, expectedNotificationStatus);
await sutProvider.Sut.PushSyncNotificationAsync(notification); await sutProvider.Sut.PushSyncNotificationCreateAsync(notification, expectedNotificationStatus);
await AssertSendTemplateNotificationAsync(sutProvider, PushType.SyncNotification, expectedSyncNotification, await AssertSendTemplateNotificationAsync(sutProvider, PushType.SyncNotificationCreate,
expectedSyncNotification,
$"(template:payload_userId:{notification.UserId})"); $"(template:payload_userId:{notification.UserId})");
await sutProvider.GetDependency<IInstallationDeviceRepository>() await sutProvider.GetDependency<IInstallationDeviceRepository>()
.Received(0) .Received(0)
@@ -70,21 +79,20 @@ public class NotificationHubPushNotificationServiceTests
[BitAutoData(true, ClientType.Web)] [BitAutoData(true, ClientType.Web)]
[BitAutoData(true, ClientType.Mobile)] [BitAutoData(true, ClientType.Mobile)]
[NotificationCustomize(false)] [NotificationCustomize(false)]
public async void PushSyncNotificationAsync_UserIdProvidedClientTypeNotAll_SentToUser(bool organizationIdNull, public async Task PushSyncNotificationCreateAsync_UserIdProvidedOrganizationIdNullClientTypeNotAll_SentToUser(
ClientType clientType, SutProvider<NotificationHubPushNotificationService> sutProvider, bool notificationStatusNull, ClientType clientType,
Notification notification) SutProvider<NotificationHubPushNotificationService> sutProvider, Notification notification,
NotificationStatus notificationStatus)
{ {
if (organizationIdNull) notification.OrganizationId = null;
{
notification.OrganizationId = null;
}
notification.ClientType = clientType; notification.ClientType = clientType;
var expectedSyncNotification = ToSyncNotificationPushNotification(notification); var expectedNotificationStatus = notificationStatusNull ? null : notificationStatus;
var expectedSyncNotification = ToSyncNotificationPushNotification(notification, expectedNotificationStatus);
await sutProvider.Sut.PushSyncNotificationAsync(notification); await sutProvider.Sut.PushSyncNotificationCreateAsync(notification, expectedNotificationStatus);
await AssertSendTemplateNotificationAsync(sutProvider, PushType.SyncNotification, expectedSyncNotification, await AssertSendTemplateNotificationAsync(sutProvider, PushType.SyncNotificationCreate,
expectedSyncNotification,
$"(template:payload_userId:{notification.UserId} && clientType:{clientType})"); $"(template:payload_userId:{notification.UserId} && clientType:{clientType})");
await sutProvider.GetDependency<IInstallationDeviceRepository>() await sutProvider.GetDependency<IInstallationDeviceRepository>()
.Received(0) .Received(0)
@@ -92,18 +100,51 @@ public class NotificationHubPushNotificationServiceTests
} }
[Theory] [Theory]
[BitAutoData] [BitAutoData(false, ClientType.Browser)]
[BitAutoData(false, ClientType.Desktop)]
[BitAutoData(false, ClientType.Web)]
[BitAutoData(false, ClientType.Mobile)]
[BitAutoData(true, ClientType.Browser)]
[BitAutoData(true, ClientType.Desktop)]
[BitAutoData(true, ClientType.Web)]
[BitAutoData(true, ClientType.Mobile)]
[NotificationCustomize(false)] [NotificationCustomize(false)]
public async void PushSyncNotificationAsync_UserIdNullOrganizationIdProvidedClientTypeAll_SentToOrganization( public async Task PushSyncNotificationCreateAsync_UserIdProvidedOrganizationIdProvidedClientTypeNotAll_SentToUser(
SutProvider<NotificationHubPushNotificationService> sutProvider, Notification notification) bool notificationStatusNull, ClientType clientType,
SutProvider<NotificationHubPushNotificationService> sutProvider,
Notification notification, NotificationStatus notificationStatus)
{
notification.ClientType = clientType;
var expectedNotificationStatus = notificationStatusNull ? null : notificationStatus;
var expectedSyncNotification = ToSyncNotificationPushNotification(notification, expectedNotificationStatus);
await sutProvider.Sut.PushSyncNotificationCreateAsync(notification, expectedNotificationStatus);
await AssertSendTemplateNotificationAsync(sutProvider, PushType.SyncNotificationCreate,
expectedSyncNotification,
$"(template:payload_userId:{notification.UserId} && clientType:{clientType})");
await sutProvider.GetDependency<IInstallationDeviceRepository>()
.Received(0)
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
}
[Theory]
[BitAutoData(false)]
[BitAutoData(true)]
[NotificationCustomize(false)]
public async Task PushSyncNotificationCreateAsync_UserIdNullOrganizationIdProvidedClientTypeAll_SentToOrganization(
bool notificationStatusNull, SutProvider<NotificationHubPushNotificationService> sutProvider,
Notification notification, NotificationStatus notificationStatus)
{ {
notification.UserId = null; notification.UserId = null;
notification.ClientType = ClientType.All; notification.ClientType = ClientType.All;
var expectedSyncNotification = ToSyncNotificationPushNotification(notification); var expectedNotificationStatus = notificationStatusNull ? null : notificationStatus;
var expectedSyncNotification = ToSyncNotificationPushNotification(notification, expectedNotificationStatus);
await sutProvider.Sut.PushSyncNotificationAsync(notification); await sutProvider.Sut.PushSyncNotificationCreateAsync(notification, expectedNotificationStatus);
await AssertSendTemplateNotificationAsync(sutProvider, PushType.SyncNotification, expectedSyncNotification, await AssertSendTemplateNotificationAsync(sutProvider, PushType.SyncNotificationCreate,
expectedSyncNotification,
$"(template:payload && organizationId:{notification.OrganizationId})"); $"(template:payload && organizationId:{notification.OrganizationId})");
await sutProvider.GetDependency<IInstallationDeviceRepository>() await sutProvider.GetDependency<IInstallationDeviceRepository>()
.Received(0) .Received(0)
@@ -111,23 +152,194 @@ public class NotificationHubPushNotificationServiceTests
} }
[Theory] [Theory]
[BitAutoData(ClientType.Browser)] [BitAutoData(false, ClientType.Browser)]
[BitAutoData(ClientType.Desktop)] [BitAutoData(false, ClientType.Desktop)]
[BitAutoData(ClientType.Web)] [BitAutoData(false, ClientType.Web)]
[BitAutoData(ClientType.Mobile)] [BitAutoData(false, ClientType.Mobile)]
[BitAutoData(true, ClientType.Browser)]
[BitAutoData(true, ClientType.Desktop)]
[BitAutoData(true, ClientType.Web)]
[BitAutoData(true, ClientType.Mobile)]
[NotificationCustomize(false)] [NotificationCustomize(false)]
public async void PushSyncNotificationAsync_UserIdNullOrganizationIdProvidedClientTypeNotAll_SentToOrganization( public async Task
ClientType clientType, SutProvider<NotificationHubPushNotificationService> sutProvider, PushSyncNotificationCreateAsync_UserIdNullOrganizationIdProvidedClientTypeNotAll_SentToOrganization(
Notification notification) bool notificationStatusNull, ClientType clientType,
SutProvider<NotificationHubPushNotificationService> sutProvider, Notification notification,
NotificationStatus notificationStatus)
{ {
notification.UserId = null; notification.UserId = null;
notification.ClientType = clientType; notification.ClientType = clientType;
var expectedNotificationStatus = notificationStatusNull ? null : notificationStatus;
var expectedSyncNotification = ToSyncNotificationPushNotification(notification, expectedNotificationStatus);
var expectedSyncNotification = ToSyncNotificationPushNotification(notification); await sutProvider.Sut.PushSyncNotificationCreateAsync(notification, expectedNotificationStatus);
await sutProvider.Sut.PushSyncNotificationAsync(notification); await AssertSendTemplateNotificationAsync(sutProvider, PushType.SyncNotificationCreate,
expectedSyncNotification,
$"(template:payload && organizationId:{notification.OrganizationId} && clientType:{clientType})");
await sutProvider.GetDependency<IInstallationDeviceRepository>()
.Received(0)
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
}
await AssertSendTemplateNotificationAsync(sutProvider, PushType.SyncNotification, expectedSyncNotification, [Theory]
[BitAutoData(false)]
[BitAutoData(true)]
[NotificationCustomize]
public async Task PushSyncNotificationUpdateAsync_Global_NotSent(bool notificationStatusNull,
SutProvider<NotificationHubPushNotificationService> sutProvider, Notification notification,
NotificationStatus notificationStatus)
{
await sutProvider.Sut.PushSyncNotificationUpdateAsync(notification,
notificationStatusNull ? null : 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(false, false)]
[BitAutoData(false, true)]
[BitAutoData(true, false)]
[BitAutoData(true, true)]
[NotificationCustomize(false)]
public async Task PushSyncNotificationUpdateAsync_UserIdProvidedClientTypeAll_SentToUser(
bool organizationIdNull, bool notificationStatusNull,
SutProvider<NotificationHubPushNotificationService> sutProvider,
Notification notification, NotificationStatus notificationStatus)
{
if (organizationIdNull)
{
notification.OrganizationId = null;
}
notification.ClientType = ClientType.All;
var expectedNotificationStatus = notificationStatusNull ? null : notificationStatus;
var expectedSyncNotification = ToSyncNotificationPushNotification(notification, expectedNotificationStatus);
await sutProvider.Sut.PushSyncNotificationUpdateAsync(notification, expectedNotificationStatus);
await AssertSendTemplateNotificationAsync(sutProvider, PushType.SyncNotificationUpdate,
expectedSyncNotification,
$"(template:payload_userId:{notification.UserId})");
await sutProvider.GetDependency<IInstallationDeviceRepository>()
.Received(0)
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
}
[Theory]
[BitAutoData(false, ClientType.Browser)]
[BitAutoData(false, ClientType.Desktop)]
[BitAutoData(false, ClientType.Web)]
[BitAutoData(false, ClientType.Mobile)]
[BitAutoData(true, ClientType.Browser)]
[BitAutoData(true, ClientType.Desktop)]
[BitAutoData(true, ClientType.Web)]
[BitAutoData(true, ClientType.Mobile)]
[NotificationCustomize(false)]
public async Task PushSyncNotificationUpdateAsync_UserIdProvidedOrganizationIdNullClientTypeNotAll_SentToUser(
bool notificationStatusNull, ClientType clientType,
SutProvider<NotificationHubPushNotificationService> sutProvider, Notification notification,
NotificationStatus notificationStatus)
{
notification.OrganizationId = null;
notification.ClientType = clientType;
var expectedNotificationStatus = notificationStatusNull ? null : notificationStatus;
var expectedSyncNotification = ToSyncNotificationPushNotification(notification, expectedNotificationStatus);
await sutProvider.Sut.PushSyncNotificationUpdateAsync(notification, expectedNotificationStatus);
await AssertSendTemplateNotificationAsync(sutProvider, PushType.SyncNotificationUpdate,
expectedSyncNotification,
$"(template:payload_userId:{notification.UserId} && clientType:{clientType})");
await sutProvider.GetDependency<IInstallationDeviceRepository>()
.Received(0)
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
}
[Theory]
[BitAutoData(false, ClientType.Browser)]
[BitAutoData(false, ClientType.Desktop)]
[BitAutoData(false, ClientType.Web)]
[BitAutoData(false, ClientType.Mobile)]
[BitAutoData(true, ClientType.Browser)]
[BitAutoData(true, ClientType.Desktop)]
[BitAutoData(true, ClientType.Web)]
[BitAutoData(true, ClientType.Mobile)]
[NotificationCustomize(false)]
public async Task PushSyncNotificationUpdateAsync_UserIdProvidedOrganizationIdProvidedClientTypeNotAll_SentToUser(
bool notificationStatusNull, ClientType clientType,
SutProvider<NotificationHubPushNotificationService> sutProvider,
Notification notification, NotificationStatus notificationStatus)
{
notification.ClientType = clientType;
var expectedNotificationStatus = notificationStatusNull ? null : notificationStatus;
var expectedSyncNotification = ToSyncNotificationPushNotification(notification, expectedNotificationStatus);
await sutProvider.Sut.PushSyncNotificationUpdateAsync(notification, expectedNotificationStatus);
await AssertSendTemplateNotificationAsync(sutProvider, PushType.SyncNotificationUpdate,
expectedSyncNotification,
$"(template:payload_userId:{notification.UserId} && clientType:{clientType})");
await sutProvider.GetDependency<IInstallationDeviceRepository>()
.Received(0)
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
}
[Theory]
[BitAutoData(false)]
[BitAutoData(true)]
[NotificationCustomize(false)]
public async Task PushSyncNotificationUpdateAsync_UserIdNullOrganizationIdProvidedClientTypeAll_SentToOrganization(
bool notificationStatusNull, SutProvider<NotificationHubPushNotificationService> sutProvider,
Notification notification, NotificationStatus notificationStatus)
{
notification.UserId = null;
notification.ClientType = ClientType.All;
var expectedNotificationStatus = notificationStatusNull ? null : notificationStatus;
var expectedSyncNotification = ToSyncNotificationPushNotification(notification, expectedNotificationStatus);
await sutProvider.Sut.PushSyncNotificationUpdateAsync(notification, expectedNotificationStatus);
await AssertSendTemplateNotificationAsync(sutProvider, PushType.SyncNotificationUpdate,
expectedSyncNotification,
$"(template:payload && organizationId:{notification.OrganizationId})");
await sutProvider.GetDependency<IInstallationDeviceRepository>()
.Received(0)
.UpsertAsync(Arg.Any<InstallationDeviceEntity>());
}
[Theory]
[BitAutoData(false, ClientType.Browser)]
[BitAutoData(false, ClientType.Desktop)]
[BitAutoData(false, ClientType.Web)]
[BitAutoData(false, ClientType.Mobile)]
[BitAutoData(true, ClientType.Browser)]
[BitAutoData(true, ClientType.Desktop)]
[BitAutoData(true, ClientType.Web)]
[BitAutoData(true, ClientType.Mobile)]
[NotificationCustomize(false)]
public async Task
PushSyncNotificationUpdateAsync_UserIdNullOrganizationIdProvidedClientTypeNotAll_SentToOrganization(
bool notificationStatusNull, ClientType clientType,
SutProvider<NotificationHubPushNotificationService> sutProvider, Notification notification,
NotificationStatus notificationStatus)
{
notification.UserId = null;
notification.ClientType = clientType;
var expectedNotificationStatus = notificationStatusNull ? null : notificationStatus;
var expectedSyncNotification = ToSyncNotificationPushNotification(notification, expectedNotificationStatus);
await sutProvider.Sut.PushSyncNotificationUpdateAsync(notification, expectedNotificationStatus);
await AssertSendTemplateNotificationAsync(sutProvider, PushType.SyncNotificationUpdate,
expectedSyncNotification,
$"(template:payload && organizationId:{notification.OrganizationId} && clientType:{clientType})"); $"(template:payload && organizationId:{notification.OrganizationId} && clientType:{clientType})");
await sutProvider.GetDependency<IInstallationDeviceRepository>() await sutProvider.GetDependency<IInstallationDeviceRepository>()
.Received(0) .Received(0)
@@ -137,7 +349,7 @@ public class NotificationHubPushNotificationServiceTests
[Theory] [Theory]
[BitAutoData([null])] [BitAutoData([null])]
[BitAutoData(ClientType.All)] [BitAutoData(ClientType.All)]
public async void SendPayloadToUserAsync_ClientTypeNullOrAll_SentToUser(ClientType? clientType, public async Task SendPayloadToUserAsync_ClientTypeNullOrAll_SentToUser(ClientType? clientType,
SutProvider<NotificationHubPushNotificationService> sutProvider, Guid userId, PushType pushType, string payload, SutProvider<NotificationHubPushNotificationService> sutProvider, Guid userId, PushType pushType, string payload,
string identifier) string identifier)
{ {
@@ -156,7 +368,7 @@ public class NotificationHubPushNotificationServiceTests
[BitAutoData(ClientType.Desktop)] [BitAutoData(ClientType.Desktop)]
[BitAutoData(ClientType.Mobile)] [BitAutoData(ClientType.Mobile)]
[BitAutoData(ClientType.Web)] [BitAutoData(ClientType.Web)]
public async void SendPayloadToUserAsync_ClientTypeExplicit_SentToUserAndClientType(ClientType clientType, public async Task SendPayloadToUserAsync_ClientTypeExplicit_SentToUserAndClientType(ClientType clientType,
SutProvider<NotificationHubPushNotificationService> sutProvider, Guid userId, PushType pushType, string payload, SutProvider<NotificationHubPushNotificationService> sutProvider, Guid userId, PushType pushType, string payload,
string identifier) string identifier)
{ {
@@ -173,7 +385,7 @@ public class NotificationHubPushNotificationServiceTests
[Theory] [Theory]
[BitAutoData([null])] [BitAutoData([null])]
[BitAutoData(ClientType.All)] [BitAutoData(ClientType.All)]
public async void SendPayloadToOrganizationAsync_ClientTypeNullOrAll_SentToOrganization(ClientType? clientType, public async Task SendPayloadToOrganizationAsync_ClientTypeNullOrAll_SentToOrganization(ClientType? clientType,
SutProvider<NotificationHubPushNotificationService> sutProvider, Guid organizationId, PushType pushType, SutProvider<NotificationHubPushNotificationService> sutProvider, Guid organizationId, PushType pushType,
string payload, string identifier) string payload, string identifier)
{ {
@@ -192,7 +404,7 @@ public class NotificationHubPushNotificationServiceTests
[BitAutoData(ClientType.Desktop)] [BitAutoData(ClientType.Desktop)]
[BitAutoData(ClientType.Mobile)] [BitAutoData(ClientType.Mobile)]
[BitAutoData(ClientType.Web)] [BitAutoData(ClientType.Web)]
public async void SendPayloadToOrganizationAsync_ClientTypeExplicit_SentToOrganizationAndClientType( public async Task SendPayloadToOrganizationAsync_ClientTypeExplicit_SentToOrganizationAndClientType(
ClientType clientType, SutProvider<NotificationHubPushNotificationService> sutProvider, Guid organizationId, ClientType clientType, SutProvider<NotificationHubPushNotificationService> sutProvider, Guid organizationId,
PushType pushType, string payload, string identifier) PushType pushType, string payload, string identifier)
{ {
@@ -206,14 +418,17 @@ public class NotificationHubPushNotificationServiceTests
.UpsertAsync(Arg.Any<InstallationDeviceEntity>()); .UpsertAsync(Arg.Any<InstallationDeviceEntity>());
} }
private static SyncNotificationPushNotification ToSyncNotificationPushNotification(Notification notification) => private static SyncNotificationPushNotification ToSyncNotificationPushNotification(Notification notification,
NotificationStatus? notificationStatus) =>
new() new()
{ {
Id = notification.Id, Id = notification.Id,
UserId = notification.UserId, UserId = notification.UserId,
OrganizationId = notification.OrganizationId, OrganizationId = notification.OrganizationId,
ClientType = notification.ClientType, ClientType = notification.ClientType,
RevisionDate = notification.RevisionDate RevisionDate = notification.RevisionDate,
ReadDate = notificationStatus?.ReadDate,
DeletedDate = notificationStatus?.DeletedDate
}; };
private static async Task AssertSendTemplateNotificationAsync( private static async Task AssertSendTemplateNotificationAsync(

View File

@@ -22,22 +22,50 @@ namespace Bit.Core.Test.Services;
public class AzureQueuePushNotificationServiceTests public class AzureQueuePushNotificationServiceTests
{ {
[Theory] [Theory]
[BitAutoData] [BitAutoData(false)]
[BitAutoData(true)]
[NotificationCustomize] [NotificationCustomize]
[NotificationStatusCustomize]
[CurrentContextCustomize] [CurrentContextCustomize]
public async void PushSyncNotificationAsync_Notification_Sent( public async Task PushSyncNotificationCreateAsync_Notification_Sent(bool notificationStatusNull,
SutProvider<AzureQueuePushNotificationService> sutProvider, Notification notification, Guid deviceIdentifier, SutProvider<AzureQueuePushNotificationService> sutProvider, Notification notification, Guid deviceIdentifier,
ICurrentContext currentContext) ICurrentContext currentContext, NotificationStatus notificationStatus)
{ {
var expectedNotificationStatus = notificationStatusNull ? null : notificationStatus;
currentContext.DeviceIdentifier.Returns(deviceIdentifier.ToString()); currentContext.DeviceIdentifier.Returns(deviceIdentifier.ToString());
sutProvider.GetDependency<IHttpContextAccessor>().HttpContext!.RequestServices sutProvider.GetDependency<IHttpContextAccessor>().HttpContext!.RequestServices
.GetService(Arg.Any<Type>()).Returns(currentContext); .GetService(Arg.Any<Type>()).Returns(currentContext);
await sutProvider.Sut.PushSyncNotificationAsync(notification); await sutProvider.Sut.PushSyncNotificationCreateAsync(notification, expectedNotificationStatus);
await sutProvider.GetDependency<QueueClient>().Received(1) await sutProvider.GetDependency<QueueClient>().Received(1)
.SendMessageAsync(Arg.Is<string>(message => .SendMessageAsync(Arg.Is<string>(message =>
MatchMessage(PushType.SyncNotification, message, new SyncNotificationEquals(notification), MatchMessage(PushType.SyncNotificationCreate, message,
new SyncNotificationEquals(notification, expectedNotificationStatus),
deviceIdentifier.ToString())));
}
[Theory]
[BitAutoData(false)]
[BitAutoData(true)]
[NotificationCustomize]
[NotificationStatusCustomize]
[CurrentContextCustomize]
public async Task PushSyncNotificationUpdateAsync_Notification_Sent(bool notificationStatusNull,
SutProvider<AzureQueuePushNotificationService> sutProvider, Notification notification, Guid deviceIdentifier,
ICurrentContext currentContext, NotificationStatus notificationStatus)
{
var expectedNotificationStatus = notificationStatusNull ? null : notificationStatus;
currentContext.DeviceIdentifier.Returns(deviceIdentifier.ToString());
sutProvider.GetDependency<IHttpContextAccessor>().HttpContext!.RequestServices
.GetService(Arg.Any<Type>()).Returns(currentContext);
await sutProvider.Sut.PushSyncNotificationUpdateAsync(notification, expectedNotificationStatus);
await sutProvider.GetDependency<QueueClient>().Received(1)
.SendMessageAsync(Arg.Is<string>(message =>
MatchMessage(PushType.SyncNotificationUpdate, message,
new SyncNotificationEquals(notification, expectedNotificationStatus),
deviceIdentifier.ToString()))); deviceIdentifier.ToString())));
} }
@@ -52,7 +80,8 @@ public class AzureQueuePushNotificationServiceTests
pushNotificationData.ContextId == contextId; pushNotificationData.ContextId == contextId;
} }
private class SyncNotificationEquals(Notification notification) : IEquatable<SyncNotificationPushNotification> private class SyncNotificationEquals(Notification notification, NotificationStatus? notificationStatus)
: IEquatable<SyncNotificationPushNotification>
{ {
public bool Equals(SyncNotificationPushNotification? other) public bool Equals(SyncNotificationPushNotification? other)
{ {
@@ -61,7 +90,9 @@ public class AzureQueuePushNotificationServiceTests
other.UserId == notification.UserId && other.UserId == notification.UserId &&
other.OrganizationId == notification.OrganizationId && other.OrganizationId == notification.OrganizationId &&
other.ClientType == notification.ClientType && other.ClientType == notification.ClientType &&
other.RevisionDate == notification.RevisionDate; other.RevisionDate == notification.RevisionDate &&
other.ReadDate == notificationStatus?.ReadDate &&
other.DeletedDate == notificationStatus?.DeletedDate;
} }
} }
} }

View File

@@ -14,17 +14,41 @@ namespace Bit.Core.Test.Services;
public class MultiServicePushNotificationServiceTests public class MultiServicePushNotificationServiceTests
{ {
[Theory] [Theory]
[BitAutoData] [BitAutoData(false)]
[BitAutoData(true)]
[NotificationCustomize] [NotificationCustomize]
public async Task PushSyncNotificationAsync_Notification_Sent( [NotificationStatusCustomize]
SutProvider<MultiServicePushNotificationService> sutProvider, Notification notification) public async Task PushSyncNotificationCreateAsync_Notification_Sent(bool notificationStatusNull,
SutProvider<MultiServicePushNotificationService> sutProvider, Notification notification,
NotificationStatus notificationStatus)
{ {
await sutProvider.Sut.PushSyncNotificationAsync(notification); await sutProvider.Sut.PushSyncNotificationCreateAsync(notification,
notificationStatusNull ? null : notificationStatus);
var expectedNotificationStatus = notificationStatusNull ? null : notificationStatus;
await sutProvider.GetDependency<IEnumerable<IPushNotificationService>>() await sutProvider.GetDependency<IEnumerable<IPushNotificationService>>()
.First() .First()
.Received(1) .Received(1)
.PushSyncNotificationAsync(notification); .PushSyncNotificationCreateAsync(notification, expectedNotificationStatus);
}
[Theory]
[BitAutoData(false)]
[BitAutoData(true)]
[NotificationCustomize]
[NotificationStatusCustomize]
public async Task PushSyncNotificationUpdateAsync_Notification_Sent(bool notificationStatusNull,
SutProvider<MultiServicePushNotificationService> sutProvider, Notification notification,
NotificationStatus notificationStatus)
{
await sutProvider.Sut.PushSyncNotificationUpdateAsync(notification,
notificationStatusNull ? null : notificationStatus);
var expectedNotificationStatus = notificationStatusNull ? null : notificationStatus;
await sutProvider.GetDependency<IEnumerable<IPushNotificationService>>()
.First()
.Received(1)
.PushSyncNotificationUpdateAsync(notification, expectedNotificationStatus);
} }
[Theory] [Theory]