mirror of
https://github.com/bitwarden/server.git
synced 2025-12-10 00:42:07 -06:00
[PM-24011] Create new policy sync push notification (#6594)
* create new policy sync push notification * CR feedback * add tests, fix typo
This commit is contained in:
parent
62cbe36ce1
commit
a5ea603817
@ -4,6 +4,8 @@ using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
|
|||||||
using Bit.Core.AdminConsole.Repositories;
|
using Bit.Core.AdminConsole.Repositories;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
|
using Bit.Core.Models;
|
||||||
|
using Bit.Core.Platform.Push;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
|
|
||||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.Implementations;
|
namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.Implementations;
|
||||||
@ -16,19 +18,22 @@ public class SavePolicyCommand : ISavePolicyCommand
|
|||||||
private readonly IReadOnlyDictionary<PolicyType, IPolicyValidator> _policyValidators;
|
private readonly IReadOnlyDictionary<PolicyType, IPolicyValidator> _policyValidators;
|
||||||
private readonly TimeProvider _timeProvider;
|
private readonly TimeProvider _timeProvider;
|
||||||
private readonly IPostSavePolicySideEffect _postSavePolicySideEffect;
|
private readonly IPostSavePolicySideEffect _postSavePolicySideEffect;
|
||||||
|
private readonly IPushNotificationService _pushNotificationService;
|
||||||
|
|
||||||
public SavePolicyCommand(IApplicationCacheService applicationCacheService,
|
public SavePolicyCommand(IApplicationCacheService applicationCacheService,
|
||||||
IEventService eventService,
|
IEventService eventService,
|
||||||
IPolicyRepository policyRepository,
|
IPolicyRepository policyRepository,
|
||||||
IEnumerable<IPolicyValidator> policyValidators,
|
IEnumerable<IPolicyValidator> policyValidators,
|
||||||
TimeProvider timeProvider,
|
TimeProvider timeProvider,
|
||||||
IPostSavePolicySideEffect postSavePolicySideEffect)
|
IPostSavePolicySideEffect postSavePolicySideEffect,
|
||||||
|
IPushNotificationService pushNotificationService)
|
||||||
{
|
{
|
||||||
_applicationCacheService = applicationCacheService;
|
_applicationCacheService = applicationCacheService;
|
||||||
_eventService = eventService;
|
_eventService = eventService;
|
||||||
_policyRepository = policyRepository;
|
_policyRepository = policyRepository;
|
||||||
_timeProvider = timeProvider;
|
_timeProvider = timeProvider;
|
||||||
_postSavePolicySideEffect = postSavePolicySideEffect;
|
_postSavePolicySideEffect = postSavePolicySideEffect;
|
||||||
|
_pushNotificationService = pushNotificationService;
|
||||||
|
|
||||||
var policyValidatorsDict = new Dictionary<PolicyType, IPolicyValidator>();
|
var policyValidatorsDict = new Dictionary<PolicyType, IPolicyValidator>();
|
||||||
foreach (var policyValidator in policyValidators)
|
foreach (var policyValidator in policyValidators)
|
||||||
@ -75,6 +80,8 @@ public class SavePolicyCommand : ISavePolicyCommand
|
|||||||
await _policyRepository.UpsertAsync(policy);
|
await _policyRepository.UpsertAsync(policy);
|
||||||
await _eventService.LogPolicyEventAsync(policy, EventType.Policy_Updated);
|
await _eventService.LogPolicyEventAsync(policy, EventType.Policy_Updated);
|
||||||
|
|
||||||
|
await PushPolicyUpdateToClients(policy.OrganizationId, policy);
|
||||||
|
|
||||||
return policy;
|
return policy;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,4 +159,17 @@ public class SavePolicyCommand : ISavePolicyCommand
|
|||||||
var currentPolicy = savedPoliciesDict.GetValueOrDefault(policyUpdate.Type);
|
var currentPolicy = savedPoliciesDict.GetValueOrDefault(policyUpdate.Type);
|
||||||
return (savedPoliciesDict, currentPolicy);
|
return (savedPoliciesDict, currentPolicy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Task PushPolicyUpdateToClients(Guid organizationId, Policy policy) => this._pushNotificationService.PushAsync(new PushNotification<SyncPolicyPushNotification>
|
||||||
|
{
|
||||||
|
Type = PushType.PolicyChanged,
|
||||||
|
Target = NotificationTarget.Organization,
|
||||||
|
TargetId = organizationId,
|
||||||
|
ExcludeCurrentContext = false,
|
||||||
|
Payload = new SyncPolicyPushNotification
|
||||||
|
{
|
||||||
|
Policy = policy,
|
||||||
|
OrganizationId = organizationId
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,8 @@ using Bit.Core.AdminConsole.OrganizationFeatures.Policies.PolicyUpdateEvents.Int
|
|||||||
using Bit.Core.AdminConsole.Repositories;
|
using Bit.Core.AdminConsole.Repositories;
|
||||||
using Bit.Core.Enums;
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
|
using Bit.Core.Models;
|
||||||
|
using Bit.Core.Platform.Push;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
|
|
||||||
namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.Implementations;
|
namespace Bit.Core.AdminConsole.OrganizationFeatures.Policies.Implementations;
|
||||||
@ -15,7 +17,8 @@ public class VNextSavePolicyCommand(
|
|||||||
IPolicyRepository policyRepository,
|
IPolicyRepository policyRepository,
|
||||||
IEnumerable<IPolicyUpdateEvent> policyUpdateEventHandlers,
|
IEnumerable<IPolicyUpdateEvent> policyUpdateEventHandlers,
|
||||||
TimeProvider timeProvider,
|
TimeProvider timeProvider,
|
||||||
IPolicyEventHandlerFactory policyEventHandlerFactory)
|
IPolicyEventHandlerFactory policyEventHandlerFactory,
|
||||||
|
IPushNotificationService pushNotificationService)
|
||||||
: IVNextSavePolicyCommand
|
: IVNextSavePolicyCommand
|
||||||
{
|
{
|
||||||
|
|
||||||
@ -74,7 +77,7 @@ public class VNextSavePolicyCommand(
|
|||||||
policy.RevisionDate = timeProvider.GetUtcNow().UtcDateTime;
|
policy.RevisionDate = timeProvider.GetUtcNow().UtcDateTime;
|
||||||
|
|
||||||
await policyRepository.UpsertAsync(policy);
|
await policyRepository.UpsertAsync(policy);
|
||||||
|
await PushPolicyUpdateToClients(policyUpdateRequest.OrganizationId, policy);
|
||||||
return policy;
|
return policy;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,4 +195,17 @@ public class VNextSavePolicyCommand(
|
|||||||
var savedPoliciesDict = savedPolicies.ToDictionary(p => p.Type);
|
var savedPoliciesDict = savedPolicies.ToDictionary(p => p.Type);
|
||||||
return savedPoliciesDict;
|
return savedPoliciesDict;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Task PushPolicyUpdateToClients(Guid organizationId, Policy policy) => pushNotificationService.PushAsync(new PushNotification<SyncPolicyPushNotification>
|
||||||
|
{
|
||||||
|
Type = PushType.PolicyChanged,
|
||||||
|
Target = NotificationTarget.Organization,
|
||||||
|
TargetId = organizationId,
|
||||||
|
ExcludeCurrentContext = false,
|
||||||
|
Payload = new SyncPolicyPushNotification
|
||||||
|
{
|
||||||
|
Policy = policy,
|
||||||
|
OrganizationId = organizationId
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
using Bit.Core.Enums;
|
using Bit.Core.AdminConsole.Entities;
|
||||||
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.NotificationCenter.Enums;
|
using Bit.Core.NotificationCenter.Enums;
|
||||||
|
|
||||||
namespace Bit.Core.Models;
|
namespace Bit.Core.Models;
|
||||||
@ -103,3 +104,9 @@ public class LogOutPushNotification
|
|||||||
public Guid UserId { get; set; }
|
public Guid UserId { get; set; }
|
||||||
public PushNotificationLogOutReason? Reason { get; set; }
|
public PushNotificationLogOutReason? Reason { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class SyncPolicyPushNotification
|
||||||
|
{
|
||||||
|
public Guid OrganizationId { get; set; }
|
||||||
|
public required Policy Policy { get; set; }
|
||||||
|
}
|
||||||
|
|||||||
@ -95,5 +95,8 @@ public enum PushType : byte
|
|||||||
OrganizationBankAccountVerified = 23,
|
OrganizationBankAccountVerified = 23,
|
||||||
|
|
||||||
[NotificationInfo("@bitwarden/team-billing-dev", typeof(Models.ProviderBankAccountVerifiedPushNotification))]
|
[NotificationInfo("@bitwarden/team-billing-dev", typeof(Models.ProviderBankAccountVerifiedPushNotification))]
|
||||||
ProviderBankAccountVerified = 24
|
ProviderBankAccountVerified = 24,
|
||||||
|
|
||||||
|
[NotificationInfo("@bitwarden/team-admin-console-dev", typeof(Models.SyncPolicyPushNotification))]
|
||||||
|
PolicyChanged = 25,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -231,9 +231,26 @@ public class HubHelpers
|
|||||||
await _hubContext.Clients.User(pendingTasksData.Payload.UserId.ToString())
|
await _hubContext.Clients.User(pendingTasksData.Payload.UserId.ToString())
|
||||||
.SendAsync(_receiveMessageMethod, pendingTasksData, cancellationToken);
|
.SendAsync(_receiveMessageMethod, pendingTasksData, cancellationToken);
|
||||||
break;
|
break;
|
||||||
|
case PushType.PolicyChanged:
|
||||||
|
await policyChangedNotificationHandler(notificationJson, cancellationToken);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
_logger.LogWarning("Notification type '{NotificationType}' has not been registered in HubHelpers and will not be pushed as as result", notification.Type);
|
_logger.LogWarning("Notification type '{NotificationType}' has not been registered in HubHelpers and will not be pushed as as result", notification.Type);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task policyChangedNotificationHandler(string notificationJson, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var policyData = JsonSerializer.Deserialize<PushNotificationData<SyncPolicyPushNotification>>(notificationJson, _deserializerOptions);
|
||||||
|
if (policyData is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _hubContext.Clients
|
||||||
|
.Group(NotificationsHub.GetOrganizationGroup(policyData.Payload.OrganizationId))
|
||||||
|
.SendAsync(_receiveMessageMethod, policyData, cancellationToken);
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,8 +6,11 @@ using Bit.Core.AdminConsole.OrganizationFeatures.Policies;
|
|||||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Implementations;
|
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Implementations;
|
||||||
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
|
using Bit.Core.AdminConsole.OrganizationFeatures.Policies.Models;
|
||||||
using Bit.Core.AdminConsole.Repositories;
|
using Bit.Core.AdminConsole.Repositories;
|
||||||
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
|
using Bit.Core.Models;
|
||||||
using Bit.Core.Models.Data.Organizations;
|
using Bit.Core.Models.Data.Organizations;
|
||||||
|
using Bit.Core.Platform.Push;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Test.AdminConsole.AutoFixture;
|
using Bit.Core.Test.AdminConsole.AutoFixture;
|
||||||
using Bit.Test.Common.AutoFixture;
|
using Bit.Test.Common.AutoFixture;
|
||||||
@ -95,7 +98,8 @@ public class SavePolicyCommandTests
|
|||||||
Substitute.For<IPolicyRepository>(),
|
Substitute.For<IPolicyRepository>(),
|
||||||
[new FakeSingleOrgPolicyValidator(), new FakeSingleOrgPolicyValidator()],
|
[new FakeSingleOrgPolicyValidator(), new FakeSingleOrgPolicyValidator()],
|
||||||
Substitute.For<TimeProvider>(),
|
Substitute.For<TimeProvider>(),
|
||||||
Substitute.For<IPostSavePolicySideEffect>()));
|
Substitute.For<IPostSavePolicySideEffect>(),
|
||||||
|
Substitute.For<IPushNotificationService>()));
|
||||||
Assert.Contains("Duplicate PolicyValidator for SingleOrg policy", exception.Message);
|
Assert.Contains("Duplicate PolicyValidator for SingleOrg policy", exception.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -360,6 +364,103 @@ public class SavePolicyCommandTests
|
|||||||
.ExecuteSideEffectsAsync(default!, default!, default!);
|
.ExecuteSideEffectsAsync(default!, default!, default!);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task VNextSaveAsync_SendsPushNotification(
|
||||||
|
[PolicyUpdate(PolicyType.SingleOrg)] PolicyUpdate policyUpdate,
|
||||||
|
[Policy(PolicyType.SingleOrg, false)] Policy currentPolicy)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var fakePolicyValidator = new FakeSingleOrgPolicyValidator();
|
||||||
|
fakePolicyValidator.ValidateAsyncMock(policyUpdate, null).Returns("");
|
||||||
|
var sutProvider = SutProviderFactory([fakePolicyValidator]);
|
||||||
|
var savePolicyModel = new SavePolicyModel(policyUpdate);
|
||||||
|
|
||||||
|
currentPolicy.OrganizationId = policyUpdate.OrganizationId;
|
||||||
|
sutProvider.GetDependency<IPolicyRepository>()
|
||||||
|
.GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, policyUpdate.Type)
|
||||||
|
.Returns(currentPolicy);
|
||||||
|
|
||||||
|
ArrangeOrganization(sutProvider, policyUpdate);
|
||||||
|
sutProvider.GetDependency<IPolicyRepository>()
|
||||||
|
.GetManyByOrganizationIdAsync(policyUpdate.OrganizationId)
|
||||||
|
.Returns([currentPolicy]);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = await sutProvider.Sut.VNextSaveAsync(savePolicyModel);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
await sutProvider.GetDependency<IPushNotificationService>().Received(1)
|
||||||
|
.PushAsync(Arg.Is<PushNotification<SyncPolicyPushNotification>>(p =>
|
||||||
|
p.Type == PushType.PolicyChanged &&
|
||||||
|
p.Target == NotificationTarget.Organization &&
|
||||||
|
p.TargetId == policyUpdate.OrganizationId &&
|
||||||
|
p.ExcludeCurrentContext == false &&
|
||||||
|
p.Payload.OrganizationId == policyUpdate.OrganizationId &&
|
||||||
|
p.Payload.Policy.Id == result.Id &&
|
||||||
|
p.Payload.Policy.Type == policyUpdate.Type &&
|
||||||
|
p.Payload.Policy.Enabled == policyUpdate.Enabled &&
|
||||||
|
p.Payload.Policy.Data == policyUpdate.Data));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task SaveAsync_SendsPushNotification([PolicyUpdate(PolicyType.SingleOrg)] PolicyUpdate policyUpdate)
|
||||||
|
{
|
||||||
|
var fakePolicyValidator = new FakeSingleOrgPolicyValidator();
|
||||||
|
fakePolicyValidator.ValidateAsyncMock(policyUpdate, null).Returns("");
|
||||||
|
var sutProvider = SutProviderFactory([fakePolicyValidator]);
|
||||||
|
|
||||||
|
ArrangeOrganization(sutProvider, policyUpdate);
|
||||||
|
sutProvider.GetDependency<IPolicyRepository>().GetManyByOrganizationIdAsync(policyUpdate.OrganizationId).Returns([]);
|
||||||
|
|
||||||
|
var result = await sutProvider.Sut.SaveAsync(policyUpdate);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IPushNotificationService>().Received(1)
|
||||||
|
.PushAsync(Arg.Is<PushNotification<SyncPolicyPushNotification>>(p =>
|
||||||
|
p.Type == PushType.PolicyChanged &&
|
||||||
|
p.Target == NotificationTarget.Organization &&
|
||||||
|
p.TargetId == policyUpdate.OrganizationId &&
|
||||||
|
p.ExcludeCurrentContext == false &&
|
||||||
|
p.Payload.OrganizationId == policyUpdate.OrganizationId &&
|
||||||
|
p.Payload.Policy.Id == result.Id &&
|
||||||
|
p.Payload.Policy.Type == policyUpdate.Type &&
|
||||||
|
p.Payload.Policy.Enabled == policyUpdate.Enabled &&
|
||||||
|
p.Payload.Policy.Data == policyUpdate.Data));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task SaveAsync_ExistingPolicy_SendsPushNotificationWithUpdatedPolicy(
|
||||||
|
[PolicyUpdate(PolicyType.SingleOrg)] PolicyUpdate policyUpdate,
|
||||||
|
[Policy(PolicyType.SingleOrg, false)] Policy currentPolicy)
|
||||||
|
{
|
||||||
|
var fakePolicyValidator = new FakeSingleOrgPolicyValidator();
|
||||||
|
fakePolicyValidator.ValidateAsyncMock(policyUpdate, null).Returns("");
|
||||||
|
var sutProvider = SutProviderFactory([fakePolicyValidator]);
|
||||||
|
|
||||||
|
currentPolicy.OrganizationId = policyUpdate.OrganizationId;
|
||||||
|
sutProvider.GetDependency<IPolicyRepository>()
|
||||||
|
.GetByOrganizationIdTypeAsync(policyUpdate.OrganizationId, policyUpdate.Type)
|
||||||
|
.Returns(currentPolicy);
|
||||||
|
|
||||||
|
ArrangeOrganization(sutProvider, policyUpdate);
|
||||||
|
sutProvider.GetDependency<IPolicyRepository>()
|
||||||
|
.GetManyByOrganizationIdAsync(policyUpdate.OrganizationId)
|
||||||
|
.Returns([currentPolicy]);
|
||||||
|
|
||||||
|
var result = await sutProvider.Sut.SaveAsync(policyUpdate);
|
||||||
|
|
||||||
|
await sutProvider.GetDependency<IPushNotificationService>().Received(1)
|
||||||
|
.PushAsync(Arg.Is<PushNotification<SyncPolicyPushNotification>>(p =>
|
||||||
|
p.Type == PushType.PolicyChanged &&
|
||||||
|
p.Target == NotificationTarget.Organization &&
|
||||||
|
p.TargetId == policyUpdate.OrganizationId &&
|
||||||
|
p.ExcludeCurrentContext == false &&
|
||||||
|
p.Payload.OrganizationId == policyUpdate.OrganizationId &&
|
||||||
|
p.Payload.Policy.Id == result.Id &&
|
||||||
|
p.Payload.Policy.Type == policyUpdate.Type &&
|
||||||
|
p.Payload.Policy.Enabled == policyUpdate.Enabled &&
|
||||||
|
p.Payload.Policy.Data == policyUpdate.Data));
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns a new SutProvider with the PolicyValidators registered in the Sut.
|
/// Returns a new SutProvider with the PolicyValidators registered in the Sut.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -225,6 +225,30 @@ public class HubHelpersTest
|
|||||||
.Group(Arg.Any<string>());
|
.Group(Arg.Any<string>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData]
|
||||||
|
public async Task SendNotificationToHubAsync_PolicyChanged_SentToOrganizationGroup(
|
||||||
|
SutProvider<HubHelpers> sutProvider,
|
||||||
|
SyncPolicyPushNotification notification,
|
||||||
|
string contextId,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var json = ToNotificationJson(notification, PushType.PolicyChanged, contextId);
|
||||||
|
await sutProvider.Sut.SendNotificationToHubAsync(json, cancellationToken);
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IHubContext<NotificationsHub>>().Clients.Received(0).User(Arg.Any<string>());
|
||||||
|
await sutProvider.GetDependency<IHubContext<NotificationsHub>>().Clients.Received(1)
|
||||||
|
.Group($"Organization_{notification.OrganizationId}")
|
||||||
|
.Received(1)
|
||||||
|
.SendCoreAsync("ReceiveMessage", Arg.Is<object?[]>(objects =>
|
||||||
|
objects.Length == 1 && AssertSyncPolicyPushNotification(notification, objects[0],
|
||||||
|
PushType.PolicyChanged, contextId)),
|
||||||
|
cancellationToken);
|
||||||
|
sutProvider.GetDependency<IHubContext<AnonymousNotificationsHub>>().Clients.Received(0).User(Arg.Any<string>());
|
||||||
|
sutProvider.GetDependency<IHubContext<AnonymousNotificationsHub>>().Clients.Received(0)
|
||||||
|
.Group(Arg.Any<string>());
|
||||||
|
}
|
||||||
|
|
||||||
private static string ToNotificationJson(object payload, PushType type, string contextId)
|
private static string ToNotificationJson(object payload, PushType type, string contextId)
|
||||||
{
|
{
|
||||||
var notification = new PushNotificationData<object>(type, payload, contextId);
|
var notification = new PushNotificationData<object>(type, payload, contextId);
|
||||||
@ -247,4 +271,20 @@ public class HubHelpersTest
|
|||||||
expected.ClientType == pushNotificationData.Payload.ClientType &&
|
expected.ClientType == pushNotificationData.Payload.ClientType &&
|
||||||
expected.RevisionDate == pushNotificationData.Payload.RevisionDate;
|
expected.RevisionDate == pushNotificationData.Payload.RevisionDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool AssertSyncPolicyPushNotification(SyncPolicyPushNotification expected, object? actual,
|
||||||
|
PushType type, string contextId)
|
||||||
|
{
|
||||||
|
if (actual is not PushNotificationData<SyncPolicyPushNotification> pushNotificationData)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pushNotificationData.Type == type &&
|
||||||
|
pushNotificationData.ContextId == contextId &&
|
||||||
|
expected.OrganizationId == pushNotificationData.Payload.OrganizationId &&
|
||||||
|
expected.Policy.Id == pushNotificationData.Payload.Policy.Id &&
|
||||||
|
expected.Policy.Type == pushNotificationData.Payload.Policy.Type &&
|
||||||
|
expected.Policy.Enabled == pushNotificationData.Payload.Policy.Enabled;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user