public async Task CalculateDeltaForInitialGroupSyncWithMissingTargetGroup()
        {
            var calculator       = new MembershipDifferenceCalculator <AzureADUser>();
            var senderRecipients = new EmailSenderRecipient();

            var syncjobRepository   = new MockSyncJobRepository();
            var loggingRepository   = new MockLoggingRepository();
            var mailRepository      = new MockMailRepository();
            var dryRun              = new DryRunValue();
            var thresholdConfig     = new ThresholdConfig(5, 3, 3, 10);
            var graphUpdaterService = new MockGraphUpdaterService(mailRepository);

            var deltaCalculator = new DeltaCalculatorService(
                calculator,
                syncjobRepository,
                loggingRepository,
                senderRecipients,
                graphUpdaterService,
                dryRun,
                thresholdConfig,
                _gmmResources,
                _localizationRepository);


            syncjobRepository.ExistingSyncJobs.Add((_partitionKey, _rowKey), _job);
            graphUpdaterService.GroupsToUsers.Add(_sources[0].ObjectId, _users);

            var response = await deltaCalculator.CalculateDifferenceAsync(_membership, new List <AzureADUser>());

            Assert.AreEqual(SyncStatus.Error, response.SyncStatus);
            Assert.AreEqual(GraphUpdaterStatus.Error, response.GraphUpdaterStatus);
            Assert.IsTrue(loggingRepository.MessagesLogged.Any(x => x.Message.Contains($"destination group {_membership.Destination} doesn't exist")));
        }
        public async Task CalculateDeltaWithMissingSyncJob()
        {
            var calculator       = new MembershipDifferenceCalculator <AzureADUser>();
            var senderRecipients = new EmailSenderRecipient();

            var syncjobRepository   = new MockSyncJobRepository();
            var loggingRepository   = new MockLoggingRepository();
            var mailRepository      = new MockMailRepository();
            var dryRun              = new DryRunValue();
            var thresholdConfig     = new ThresholdConfig(5, 3, 3, 10);
            var graphUpdaterService = new MockGraphUpdaterService(mailRepository);
            var deltaCalculator     = new DeltaCalculatorService(
                calculator,
                syncjobRepository,
                loggingRepository,
                senderRecipients,
                graphUpdaterService,
                dryRun,
                thresholdConfig,
                _gmmResources,
                _localizationRepository);

            var targetGroupUsers = new List <AzureADUser>();

            graphUpdaterService.GroupsToUsers.Add(_sources[0].ObjectId, _users);
            graphUpdaterService.GroupsToUsers.Add(_targetGroupId, targetGroupUsers);

            var response = await deltaCalculator.CalculateDifferenceAsync(_membership, targetGroupUsers);

            Assert.AreEqual(SyncStatus.Error, response.SyncStatus);
            Assert.AreEqual(GraphUpdaterStatus.Error, response.GraphUpdaterStatus);
        }
        public async Task CanGroupSyncIntoEmptyGroup()
        {
            await _groupRepo.AddUsersToGroup(_testUsers, _sourceGroup);

            await Task.Delay(_waitForGraph);             // Sometimes you have to wait for the graph to catch up.

            var sourceGroupMembers = await _groupRepo.GetUsersInGroupTransitively(_sourceGroup.ObjectId);

            Assert.AreEqual(UserCount, _testUsers.Length);
            Assert.IsTrue(SequencesMatch(_testUsers, sourceGroupMembers));

            var destinationGroupMembers = await _groupRepo.GetUsersInGroupTransitively(_destinationGroup.ObjectId);

            Assert.AreEqual(0, destinationGroupMembers.Count);

            var calc            = new MembershipDifferenceCalculator <AzureADUser>();
            var membershipDelta = calc.CalculateDifference(sourceGroupMembers, destinationGroupMembers);

            Assert.IsTrue(SequencesMatch(_testUsers, membershipDelta.ToAdd));
            Assert.AreEqual(0, membershipDelta.ToRemove.Count);

            await _groupRepo.AddUsersToGroup(membershipDelta.ToAdd, _destinationGroup);

            await _groupRepo.RemoveUsersFromGroup(membershipDelta.ToRemove, _destinationGroup);

            await Task.Delay(_waitForGraph);

            destinationGroupMembers = await _groupRepo.GetUsersInGroupTransitively(_destinationGroup.ObjectId);

            Assert.IsTrue(SequencesMatch(_testUsers, destinationGroupMembers));
        }
        public async Task CanGroupSyncIntoNonEmptyGroup()
        {
            var initialSourceUsers       = _testUsers.Where((_, idx) => idx % 2 == 0).ToArray();
            var inititalDestinationUsers = _testUsers.Where((_, idx) => idx % 2 == 1).ToArray();

            await _groupRepo.AddUsersToGroup(initialSourceUsers, _sourceGroup);

            await _groupRepo.AddUsersToGroup(inititalDestinationUsers, _destinationGroup);

            await Task.Delay(_waitForGraph);             // Sometimes you have to wait for the graph to catch up.

            var sourceGroupMembers = await _groupRepo.GetUsersInGroupTransitively(_sourceGroup.ObjectId);

            Assert.IsTrue(SequencesMatch(initialSourceUsers, sourceGroupMembers));

            var destinationGroupMembers = await _groupRepo.GetUsersInGroupTransitively(_destinationGroup.ObjectId);

            Assert.IsTrue(SequencesMatch(inititalDestinationUsers, destinationGroupMembers));

            var calc            = new MembershipDifferenceCalculator <AzureADUser>();
            var membershipDelta = calc.CalculateDifference(sourceGroupMembers, destinationGroupMembers);

            Assert.IsTrue(SequencesMatch(initialSourceUsers, membershipDelta.ToAdd));
            Assert.IsTrue(SequencesMatch(inititalDestinationUsers, membershipDelta.ToRemove));

            await _groupRepo.AddUsersToGroup(membershipDelta.ToAdd, _destinationGroup);

            await _groupRepo.RemoveUsersFromGroup(membershipDelta.ToRemove, _destinationGroup);

            await Task.Delay(_waitForGraph);             // Sometimes you have to wait for the graph to catch up.

            destinationGroupMembers = await _groupRepo.GetUsersInGroupTransitively(_destinationGroup.ObjectId);

            Assert.IsTrue(SequencesMatch(initialSourceUsers, destinationGroupMembers));
        }
        public async Task SendNotificationToSupportAsFallBack()
        {
            var calculator       = new MembershipDifferenceCalculator <AzureADUser>();
            var senderRecipients = new EmailSenderRecipient
            {
                SupportEmailAddresses = "*****@*****.**"
            };

            var syncjobRepository   = new MockSyncJobRepository();
            var loggingRepository   = new MockLoggingRepository();
            var mailRepository      = new MockMailRepository();
            var dryRun              = new DryRunValue();
            var thresholdConfig     = new ThresholdConfig(5, 3, 3, 10);
            var graphUpdaterService = new MockGraphUpdaterService(mailRepository);

            var deltaCalculator = new DeltaCalculatorService(
                calculator,
                syncjobRepository,
                loggingRepository,
                senderRecipients,
                graphUpdaterService,
                dryRun,
                thresholdConfig,
                _gmmResources,
                _localizationRepository);

            var targetGroupUsers = new List <AzureADUser>();
            var ownersPage       = new GroupOwnersPage();
            var owners           = new List <User>(GenerateGraphUsers(10));

            owners.ForEach(ownersPage.Add);

            _job.LastRunTime         = DateTime.UtcNow.AddDays(-1);
            _job.ThresholdViolations = 2;

            syncjobRepository.ExistingSyncJobs.Add((_partitionKey, _rowKey), _job);
            graphUpdaterService.GroupsToUsers.Add(_sources[0].ObjectId, _users);
            graphUpdaterService.GroupsToUsers.Add(_targetGroupId, targetGroupUsers);
            graphUpdaterService.Groups.Add(_sources[0].ObjectId, new Group {
                Id = _sources[0].ObjectId.ToString(), DisplayName = "Source Group"
            });
            graphUpdaterService.Groups.Add(_targetGroupId, new Group {
                Id = _targetGroupId.ToString(), DisplayName = "Target Group", Owners = ownersPage
            });

            var response = await deltaCalculator.CalculateDifferenceAsync(_membership, targetGroupUsers);

            var emailMessage = mailRepository.SentEmails.Single();

            Assert.AreEqual(SyncStatus.Idle, response.SyncStatus);
            Assert.AreEqual(GraphUpdaterStatus.ThresholdExceeded, response.GraphUpdaterStatus);
            Assert.AreEqual(targetGroupUsers.Count, graphUpdaterService.GroupsToUsers[_targetGroupId].Count);
            Assert.IsTrue(loggingRepository.MessagesLogged.Any(x => x.Message.Contains("is greater than threshold value")));
            Assert.AreEqual(SyncThresholdBothEmailBody, emailMessage.Content);
            Assert.AreEqual(_targetGroupId.ToString(), emailMessage.AdditionalContentParams[1]);
            Assert.AreEqual(5, emailMessage.AdditionalContentParams.Length);
            Assert.AreEqual(senderRecipients.SupportEmailAddresses, emailMessage.ToEmailAddresses);
        }
        public async Task CanGroupSyncWithNestedGroups()
        {
            // the bottom half is in the source
            // the upper two thirds are in the destination
            var initialSourceUsers      = _testUsers.Where((_, idx) => idx < (UserCount / 2)).ToArray();
            var initialDestinationUsers = _testUsers.Where((_, idx) => idx > (UserCount / 3)).ToArray();

            await _groupRepo.AddUsersToGroup(initialDestinationUsers, _destinationGroup);

            const int testGroupCount = 5;
            var       testGroups     = new AzureADGroup[testGroupCount];

            for (int i = 0; i < testGroupCount; i++)
            {
                testGroups[i] = await CreateOrClearGroup(
                    new Group { DisplayName = $"TestSourceGroup{i}", MailEnabled = false, MailNickname = $"testsourcegroup{i}", SecurityEnabled = true });

                _groupsToRemove.Add(testGroups[i]);

                // add all the users to the first group, every other user to the second group, etc.
                await _groupRepo.AddUsersToGroup(initialSourceUsers.Where((_, idx) => idx % (i + 1) == 0), testGroups[i]);

                await _graphServiceClient.Groups[_sourceGroup.ObjectId.ToString()].Members.References.Request().AddAsync(new DirectoryObject {
                    Id = testGroups[i].ObjectId.ToString()
                });
            }

            await Task.Delay(_waitForGraph);             // Sometimes you have to wait for the graph to catch up.

            var sourceGroupMembers = await _groupRepo.GetUsersInGroupTransitively(_sourceGroup.ObjectId);

            Assert.IsTrue(SequencesMatch(initialSourceUsers, sourceGroupMembers));

            var destinationGroupMembers = await _groupRepo.GetUsersInGroupTransitively(_destinationGroup.ObjectId);

            Assert.IsTrue(SequencesMatch(initialDestinationUsers, destinationGroupMembers));

            var calc            = new MembershipDifferenceCalculator <AzureADUser>();
            var membershipDelta = calc.CalculateDifference(sourceGroupMembers, destinationGroupMembers);

            // Have to add the bottom third and remove the top half
            Assert.IsTrue(SequencesMatch(_testUsers.Where((_, idx) => idx <= (UserCount / 3)), membershipDelta.ToAdd));
            Assert.IsTrue(SequencesMatch(_testUsers.Where((_, idx) => idx >= (UserCount / 2)), membershipDelta.ToRemove));

            await _groupRepo.AddUsersToGroup(membershipDelta.ToAdd, _destinationGroup);

            await _groupRepo.RemoveUsersFromGroup(membershipDelta.ToRemove, _destinationGroup);

            await Task.Delay(_waitForGraph);             // Sometimes you have to wait for the graph to catch up.

            destinationGroupMembers = await _groupRepo.GetUsersInGroupTransitively(_destinationGroup.ObjectId);

            Assert.IsTrue(SequencesMatch(initialSourceUsers, destinationGroupMembers));
        }
        public async Task CalculateDeltaForNonInitialGroupSyncExceedingBothThresholds()
        {
            var calculator       = new MembershipDifferenceCalculator <AzureADUser>();
            var senderRecipients = new EmailSenderRecipient();

            var syncjobRepository   = new MockSyncJobRepository();
            var loggingRepository   = new MockLoggingRepository();
            var mailRepository      = new MockMailRepository();
            var dryRun              = new DryRunValue();
            var thresholdConfig     = new ThresholdConfig(5, 3, 3, 10);
            var graphUpdaterService = new MockGraphUpdaterService(mailRepository);

            var deltaCalculator = new DeltaCalculatorService(
                calculator,
                syncjobRepository,
                loggingRepository,
                senderRecipients,
                graphUpdaterService,
                dryRun,
                thresholdConfig,
                _gmmResources,
                _localizationRepository);

            var users = _membership.SourceMembers;

            _membership.SourceMembers = users.Take(2).ToList();
            _job.LastRunTime          = DateTime.UtcNow.AddDays(-1);
            _job.ThresholdViolations  = 2;

            var targetGroupUsers = users.Skip(2).Take(2).ToList();

            syncjobRepository.ExistingSyncJobs.Add((_partitionKey, _rowKey), _job);
            graphUpdaterService.GroupsToUsers.Add(_sources[0].ObjectId, _membership.SourceMembers);
            graphUpdaterService.GroupsToUsers.Add(_targetGroupId, targetGroupUsers);
            graphUpdaterService.Groups.Add(_sources[0].ObjectId, new Microsoft.Graph.Group {
                Id = _sources[0].ObjectId.ToString(), DisplayName = "Source Group"
            });
            graphUpdaterService.Groups.Add(_targetGroupId, new Microsoft.Graph.Group {
                Id = _targetGroupId.ToString(), DisplayName = "Target Group"
            });

            var response = await deltaCalculator.CalculateDifferenceAsync(_membership, targetGroupUsers);

            var emailMessage = mailRepository.SentEmails.Single();

            Assert.AreEqual(SyncStatus.Idle, response.SyncStatus);
            Assert.AreEqual(GraphUpdaterStatus.ThresholdExceeded, response.GraphUpdaterStatus);
            Assert.AreEqual(targetGroupUsers.Count, graphUpdaterService.GroupsToUsers[_targetGroupId].Count);
            Assert.IsTrue(loggingRepository.MessagesLogged.Any(x => x.Message.Contains("is lesser than threshold value")));
            Assert.AreEqual(SyncThresholdBothEmailBody, emailMessage.Content);
            Assert.AreEqual(_targetGroupId.ToString(), emailMessage.AdditionalContentParams[1]);
            Assert.AreEqual(5, emailMessage.AdditionalContentParams.Length);
        }
        public async Task CalculateDeltaForInitialGroupSync()
        {
            var calculator          = new MembershipDifferenceCalculator <AzureADUser>();
            var senderRecipients    = new EmailSenderRecipient();
            var syncjobRepository   = new MockSyncJobRepository();
            var loggingRepository   = new MockLoggingRepository();
            var mailRepository      = new MockMailRepository();
            var graphUpdaterService = new MockGraphUpdaterService(mailRepository);
            var dryRun          = new DryRunValue();
            var thresholdConfig = new ThresholdConfig(5, 3, 3, 10);

            dryRun.DryRunEnabled = false;

            var deltaCalculator = new DeltaCalculatorService(
                calculator,
                syncjobRepository,
                loggingRepository,
                senderRecipients,
                graphUpdaterService,
                dryRun,
                thresholdConfig,
                _gmmResources,
                _localizationRepository);


            var targetGroupUsers = new List <AzureADUser>();

            syncjobRepository.ExistingSyncJobs.Add((_partitionKey, _rowKey), _job);
            graphUpdaterService.GroupsToUsers.Add(_sources[0].ObjectId, _users);
            graphUpdaterService.GroupsToUsers.Add(_targetGroupId, targetGroupUsers);
            graphUpdaterService.Groups.Add(_sources[0].ObjectId, new Microsoft.Graph.Group {
                Id = _sources[0].ObjectId.ToString(), DisplayName = "Source Group"
            });
            graphUpdaterService.Groups.Add(_targetGroupId, new Microsoft.Graph.Group {
                Id = _targetGroupId.ToString(), DisplayName = "Target Group"
            });

            var response = await deltaCalculator.CalculateDifferenceAsync(_membership, targetGroupUsers);

            Assert.AreEqual(SyncStatus.Idle, response.SyncStatus);
            Assert.AreEqual(_users.Count, response.MembersToAdd.Count);
            Assert.IsTrue(response.IsInitialSync);
            Assert.AreEqual(GraphUpdaterStatus.Ok, response.GraphUpdaterStatus);
        }
        public async Task CanGroupSyncIntoOverlappingGroup()
        {
            // the bottom half is in the source
            // the upper two thirds are in the destination
            var initialSourceUsers      = _testUsers.Where((_, idx) => idx < (UserCount / 2)).ToArray();
            var initialDestinationUsers = _testUsers.Where((_, idx) => idx > (UserCount / 3)).ToArray();

            await _groupRepo.AddUsersToGroup(initialSourceUsers, _sourceGroup);

            await _groupRepo.AddUsersToGroup(initialDestinationUsers, _destinationGroup);

            await Task.Delay(_waitForGraph);             // Sometimes you have to wait for the graph to catch up.

            var sourceGroupMembers = await _groupRepo.GetUsersInGroupTransitively(_sourceGroup.ObjectId);

            Assert.IsTrue(SequencesMatch(initialSourceUsers, sourceGroupMembers));

            var destinationGroupMembers = await _groupRepo.GetUsersInGroupTransitively(_destinationGroup.ObjectId);

            Assert.IsTrue(SequencesMatch(initialDestinationUsers, destinationGroupMembers));

            var calc            = new MembershipDifferenceCalculator <AzureADUser>();
            var membershipDelta = calc.CalculateDifference(sourceGroupMembers, destinationGroupMembers);

            // Have to add the bottom third and remove the top half
            Assert.IsTrue(SequencesMatch(_testUsers.Where((_, idx) => idx <= (UserCount / 3)), membershipDelta.ToAdd));
            Assert.IsTrue(SequencesMatch(_testUsers.Where((_, idx) => idx >= (UserCount / 2)), membershipDelta.ToRemove));

            await _groupRepo.AddUsersToGroup(membershipDelta.ToAdd, _destinationGroup);

            await _groupRepo.RemoveUsersFromGroup(membershipDelta.ToRemove, _destinationGroup);

            await Task.Delay(_waitForGraph);             // Sometimes you have to wait for the graph to catch up.

            destinationGroupMembers = await _groupRepo.GetUsersInGroupTransitively(_destinationGroup.ObjectId);

            Assert.IsTrue(SequencesMatch(initialSourceUsers, destinationGroupMembers));
        }
        public async Task DoNotSendNotificationToRequestor(int currentThresoldViolations)
        {
            var calculator       = new MembershipDifferenceCalculator <AzureADUser>();
            var senderRecipients = new EmailSenderRecipient
            {
                SyncDisabledCCAddresses = "*****@*****.**"
            };

            var syncjobRepository   = new MockSyncJobRepository();
            var loggingRepository   = new MockLoggingRepository();
            var mailRepository      = new MockMailRepository();
            var dryRun              = new DryRunValue();
            var thresholdConfig     = new ThresholdConfig(5, 3, 3, 10);
            var graphUpdaterService = new MockGraphUpdaterService(mailRepository);

            var deltaCalculator = new DeltaCalculatorService(
                calculator,
                syncjobRepository,
                loggingRepository,
                senderRecipients,
                graphUpdaterService,
                dryRun,
                thresholdConfig,
                _gmmResources,
                _localizationRepository);

            var targetGroupUsers = new List <AzureADUser>();
            var ownersPage       = new GroupOwnersPage();
            var owners           = new List <User>();

            foreach (var email in _job.Requestor.Split(",", StringSplitOptions.RemoveEmptyEntries))
            {
                owners.Add(new User {
                    Mail = email
                });
            }

            owners.AddRange(GenerateGraphUsers(3));
            owners.ForEach(ownersPage.Add);

            _job.LastRunTime         = DateTime.UtcNow.AddDays(-1);
            _job.ThresholdViolations = currentThresoldViolations;

            syncjobRepository.ExistingSyncJobs.Add((_partitionKey, _rowKey), _job);
            graphUpdaterService.GroupsToUsers.Add(_sources[0].ObjectId, _users);
            graphUpdaterService.GroupsToUsers.Add(_targetGroupId, targetGroupUsers);
            graphUpdaterService.Groups.Add(_sources[0].ObjectId, new Group {
                Id = _sources[0].ObjectId.ToString(), DisplayName = "Source Group"
            });
            graphUpdaterService.Groups.Add(_targetGroupId, new Group {
                Id = _targetGroupId.ToString(), DisplayName = "Target Group", Owners = ownersPage
            });

            var response = await deltaCalculator.CalculateDifferenceAsync(_membership, targetGroupUsers);

            var emailMessage = mailRepository.SentEmails.Count();

            Assert.AreEqual(SyncStatus.Idle, response.SyncStatus);
            Assert.AreEqual(GraphUpdaterStatus.ThresholdExceeded, response.GraphUpdaterStatus);
            Assert.AreEqual(targetGroupUsers.Count, graphUpdaterService.GroupsToUsers[_targetGroupId].Count);
            Assert.IsTrue(loggingRepository.MessagesLogged.Any(x => x.Message.Contains("is greater than threshold value")));
            Assert.AreEqual(0, mailRepository.SentEmails.Count);
        }