public async Task RepoHookWithErrorIsSkipped()
        {
            using (var context = new ShipHubContext()) {
                var user = TestUtil.MakeTestUser(context);
                var repo = TestUtil.MakeTestRepo(context, user.Id);
                var hook = context.Hooks.Add(new Hook()
                {
                    Id           = 1001,
                    Events       = "event1,event2",
                    GitHubId     = null, // Empty GitHubId
                    RepositoryId = repo.Id,
                    Secret       = Guid.NewGuid(),
                    LastError    = DateTimeOffset.UtcNow,
                });
                await context.SaveChangesAsync();

                var repoActor = CreateRepoActor(repo.Id, repo.FullName);
                var changes   = await repoActor.AddOrUpdateWebhooks(null);

                var beforeError = hook.LastError;
                await context.Entry(hook).ReloadAsync();

                Assert.IsTrue(hook.LastError == beforeError, "Recent LastError should be skipped.");
                Assert.IsEmpty(changes.Repositories, "skipped hook should not send changes.");
            }
        }
        public async Task OrgHookWithErrorIsSkipped()
        {
            using (var context = new ShipHubContext()) {
                var user = TestUtil.MakeTestUser(context);
                var org  = TestUtil.MakeTestOrg(context);
                context.OrganizationAccounts.Add(new OrganizationAccount()
                {
                    UserId         = user.Id,
                    OrganizationId = org.Id,
                });
                var hook = context.Hooks.Add(new Hook()
                {
                    Id             = 1001,
                    Events         = "event1,event2",
                    GitHubId       = null, // Empty GitHub Id
                    OrganizationId = org.Id,
                    Secret         = Guid.NewGuid(),
                    LastError      = DateTimeOffset.UtcNow,
                });
                await context.SaveChangesAsync();

                var orgActor = CreateOrgActor(org.Id, org.Login);
                var changes  = await orgActor.AddOrUpdateOrganizationWebhooks(context, null);

                var beforeError = hook.LastError;
                await context.Entry(hook).ReloadAsync();

                Assert.IsTrue(hook.LastError == beforeError, "Recent LastError should be skipped.");
                Assert.IsEmpty(changes.Repositories, "skipped hook should not send changes.");
            }
        }
        public async Task OrgHookSetLastErrorIfGitHubAddRequestFails()
        {
            using (var context = new ShipHubContext()) {
                var user = TestUtil.MakeTestUser(context);
                var org  = TestUtil.MakeTestOrg(context);
                context.OrganizationAccounts.Add(new OrganizationAccount()
                {
                    UserId         = user.Id,
                    OrganizationId = org.Id,
                });
                await context.SaveChangesAsync();

                var mock = new Mock <IGitHubActor>();

                mock
                .Setup(x => x.OrganizationWebhooks(org.Login, null, It.IsAny <RequestPriority>()))
                .ReturnsAsync(new GitHubResponse <IEnumerable <Webhook> >(null)
                {
                    Result = new List <Webhook>(),
                    Status = HttpStatusCode.OK,
                });
                mock
                .Setup(x => x.AddOrganizationWebhook(org.Login, It.IsAny <Webhook>(), It.IsAny <RequestPriority>()))
                .ThrowsAsync(new Exception("some exception!"));

                var orgActor = CreateOrgActor(org.Id, org.Login);
                var changes  = await orgActor.AddOrUpdateOrganizationWebhooks(context, mock.Object);

                Assert.IsEmpty(changes.Repositories, "Failed hook creation should not send notifications.");

                var hook = context.Hooks.SingleOrDefault(x => x.OrganizationId == org.Id);
                Assert.IsNotNull(hook.LastError, "hook should have been marked as errored when we noticed the AddRepoHook failed");
            }
        }
        public async Task RepoHookSetLastErrorIfGitHubAddRequestFails()
        {
            using (var context = new ShipHubContext()) {
                var user = TestUtil.MakeTestUser(context);
                var repo = TestUtil.MakeTestRepo(context, user.Id);
                await context.SaveChangesAsync();

                var mock = new Mock <IGitHubActor>();

                mock
                .Setup(x => x.RepositoryWebhooks(repo.FullName, null, It.IsAny <RequestPriority>()))
                .ReturnsAsync(new GitHubResponse <IEnumerable <Webhook> >(null)
                {
                    Result = new List <Webhook>(),
                    Status = HttpStatusCode.OK,
                });
                mock
                .Setup(x => x.AddRepositoryWebhook(repo.FullName, It.IsAny <Webhook>(), It.IsAny <RequestPriority>()))
                .ThrowsAsync(new Exception("some exception!"));

                var repoActor = CreateRepoActor(repo.Id, repo.FullName);
                var changes   = await repoActor.AddOrUpdateWebhooks(mock.Object);

                Assert.IsEmpty(changes.Repositories, "Failed hook creation should not send notifications.");

                var hook = context.Hooks.SingleOrDefault(x => x.RepositoryId == repo.Id);
                Assert.IsNotNull(hook.LastError, "hook should have been marked as errored when we noticed the AddRepoHook failed");
            }
        }
        private async Task <bool> RepoShipNeedsWebhookHelpHelper(bool hasHook, bool isAdmin)
        {
            using (var context = new ShipHubContext()) {
                var userAccount = TestUtil.MakeTestUser(context);
                var orgAccount  = new AccountTableType()
                {
                    Id    = 4001,
                    Login = "******",
                    Type  = "org",
                };
                var repo = new RepositoryTableType()
                {
                    Id        = 5001,
                    AccountId = 4001,
                    Name      = "unicorns",
                    FullName  = "pureimaginary/unicorns",
                    Private   = true,
                    HasIssues = true,
                };

                await context.BulkUpdateAccounts(DateTimeOffset.UtcNow, new[] { orgAccount });

                var user = context.Users.Single(x => x.Id == userAccount.Id);
                await context.SaveChangesAsync();

                await context.BulkUpdateRepositories(DateTimeOffset.UtcNow, new[] { repo });

                await context.SetUserOrganizations(user.Id, new[] { orgAccount.Id });

                await context.SetAccountLinkedRepositories(
                    userAccount.Id,
                    new[] { (repo.Id, isAdmin) });
        private static async Task <Environment> MakeEnvironment(ShipHubContext context)
        {
            var env = new Environment()
            {
                user1 = TestUtil.MakeTestUser(context, 3001, "alok"),
                user2 = TestUtil.MakeTestUser(context, 3002, "aroon"),
                org   = TestUtil.MakeTestOrg(context, 6001, "pureimaginary")
            };
            await context.SetUserOrganizations(env.user1.Id, new[] { env.org.Id });

            await context.SaveChangesAsync();

            return(env);
        }
        public async Task OrgHookWithOldErrorIsRetried()
        {
            using (var context = new ShipHubContext()) {
                var user = TestUtil.MakeTestUser(context);
                var org  = TestUtil.MakeTestOrg(context);
                var hook = context.Hooks.Add(new Hook()
                {
                    Id             = 1001,
                    Events         = "event1,event2",
                    GitHubId       = null, // Empty GitHub Id
                    OrganizationId = org.Id,
                    Secret         = Guid.NewGuid(),
                    LastError      = DateTimeOffset.UtcNow.Subtract(OrganizationActor.HookErrorDelay),
                });
                await context.SaveChangesAsync();

                await context.SetUserOrganizations(user.Id, new[] { org.Id });

                var mock = new Mock <IGitHubActor>();

                mock
                .Setup(x => x.OrganizationWebhooks(org.Login, null, It.IsAny <RequestPriority>()))
                .ReturnsAsync(new GitHubResponse <IEnumerable <Webhook> >(null)
                {
                    Result = new List <Webhook>()
                    {
                    },
                    Status = HttpStatusCode.OK,
                });
                mock
                .Setup(x => x.AddOrganizationWebhook(org.Login, It.IsAny <Webhook>(), It.IsAny <RequestPriority>()))
                .ReturnsAsync(new GitHubResponse <Webhook>(null)
                {
                    Result = new Webhook()
                    {
                        Id = 9999,
                    },
                    Status = HttpStatusCode.OK,
                });

                var orgActor = CreateOrgActor(org.Id, org.Login);
                var changes  = await orgActor.AddOrUpdateOrganizationWebhooks(context, mock.Object);

                await context.Entry(hook).ReloadAsync();

                Assert.AreEqual(9999, hook.GitHubId);
                Assert.IsNull(hook.LastError);
                Assert.IsTrue(changes.Organizations?.First() == org.Id, "New hook should send notifications.");
            }
        }
        public async Task UsersCanBecomeOrgs()
        {
            using (var context = new ShipHubContext()) {
                var user1 = TestUtil.MakeTestUser(context, 3001, "user1");
                var user2 = TestUtil.MakeTestUser(context, 3002, "user2");
                var user3 = TestUtil.MakeTestUser(context, 3003, "user3");
                var repo1 = TestUtil.MakeTestRepo(context, user1.Id, 2001, "unicorns1");
                var repo2 = TestUtil.MakeTestRepo(context, user1.Id, 2002, "unicorns2");
                var repo3 = TestUtil.MakeTestRepo(context, user2.Id, 2003, "unicorns3");
                var repo4 = TestUtil.MakeTestRepo(context, user3.Id, 2004, "unicorns4");
                var org1  = TestUtil.MakeTestOrg(context, 6001, "org1");
                var org2  = TestUtil.MakeTestOrg(context, 6002, "org2");
                await context.SaveChangesAsync();

                await context.SetAccountLinkedRepositories(user1.Id, new[] {
        public async Task RepoHookWithOldErrorIsRetried()
        {
            using (var context = new ShipHubContext()) {
                var user = TestUtil.MakeTestUser(context);
                var repo = TestUtil.MakeTestRepo(context, user.Id);
                var hook = context.Hooks.Add(new Hook()
                {
                    Id           = 1001,
                    Events       = "event1,event2",
                    GitHubId     = null, // Empty GitHubId
                    RepositoryId = repo.Id,
                    Secret       = Guid.NewGuid(),
                    LastError    = DateTimeOffset.UtcNow.Subtract(RepositoryActor.HookErrorDelay),
                });
                await context.SaveChangesAsync();

                var mock = new Mock <IGitHubActor>();
                mock
                .Setup(x => x.RepositoryWebhooks(repo.FullName, It.IsAny <GitHubCacheDetails>(), It.IsAny <RequestPriority>()))
                .ReturnsAsync(new GitHubResponse <IEnumerable <Webhook> >(null)
                {
                    Result = new List <Webhook>(),
                    Status = HttpStatusCode.OK,
                });
                mock
                .Setup(x => x.AddRepositoryWebhook(repo.FullName, It.IsAny <Webhook>(), It.IsAny <RequestPriority>()))
                .ReturnsAsync(new GitHubResponse <Webhook>(null)
                {
                    Result = new Webhook()
                    {
                        Id = 9999,
                    },
                    Status = HttpStatusCode.OK,
                });

                var repoActor = CreateRepoActor(repo.Id, repo.FullName);
                var changes   = await repoActor.AddOrUpdateWebhooks(mock.Object);

                await context.Entry(hook).ReloadAsync();

                Assert.AreEqual(9999, hook.GitHubId);
                Assert.IsNull(hook.LastError);
                Assert.IsTrue(changes.Repositories.First() == repo.Id, "New hook should send notifications.");
            }
        }
        public async Task ModeIsPaidWithPersonalSubscription()
        {
            using (var context = new ShipHubContext()) {
                var env = await MakeEnvironment(context);

                context.Subscriptions.Add(new Subscription()
                {
                    AccountId = env.user1.Id,
                    State     = SubscriptionState.Subscribed,
                });
                await context.SaveChangesAsync();

                var entry = await GetSubscriptionResponse(env.user1);

                Assert.AreEqual(SubscriptionMode.Paid, entry.Mode,
                                "Mode is paid with a personal subscription.");
            }
        }
        public async Task ModeIsPaidWithOrgSubscriptionEvenIfUserHasTrial()
        {
            using (var context = new ShipHubContext()) {
                var env = await MakeEnvironment(context);

                context.Subscriptions.Add(new Subscription()
                {
                    AccountId = env.user1.Id,
                    State     = SubscriptionState.InTrial,
                });
                context.Subscriptions.Add(new Subscription()
                {
                    AccountId = env.org.Id,
                    State     = SubscriptionState.Subscribed,
                });
                await context.SaveChangesAsync();

                var entry = await GetSubscriptionResponse(env.user1);

                Assert.AreEqual(SubscriptionMode.Paid, entry.Mode,
                                "A paid organization overrides a user's trial");
                Assert.IsNull(entry.TrialEndDate, "should not be set if we're in paid mode");
            }
        }
        public async Task WillEditHookWhenEventListIsExcessiveForOrg()
        {
            using (var context = new ShipHubContext()) {
                var user = TestUtil.MakeTestUser(context);
                var org  = TestUtil.MakeTestOrg(context);
                context.OrganizationAccounts.Add(new OrganizationAccount()
                {
                    UserId         = user.Id,
                    OrganizationId = org.Id,
                });
                var extraEvents       = OrganizationActor.RequiredEvents.Add("extra");
                var extraEventsString = string.Join(",", extraEvents);
                var hook = context.Hooks.Add(new Hook()
                {
                    Id             = 1001,
                    Events         = extraEventsString,
                    GitHubId       = 8001,
                    OrganizationId = org.Id,
                    Secret         = Guid.NewGuid(),
                });
                await context.SaveChangesAsync();

                var mock = new Mock <IGitHubActor>();

                mock
                .Setup(x => x.OrganizationWebhooks(org.Login, null, It.IsAny <RequestPriority>()))
                .ReturnsAsync(new GitHubResponse <IEnumerable <Webhook> >(null)
                {
                    Result = new List <Webhook>()
                    {
                        new Webhook()
                        {
                            Id     = 8001,
                            Active = true,
                            Config = new WebhookConfiguration()
                            {
                                ContentType = "json",
                                InsecureSsl = false,
                                Secret      = "*******",
                                Url         = $"https://{Configuration.ApiHostName}/webhook/repo/1234",
                            },
                            Events = extraEvents,
                            Name   = "web",
                        },
                    },
                    Status = HttpStatusCode.OK,
                });

                mock
                .Setup(x => x.EditOrganizationWebhookEvents(org.Login, (long)hook.GitHubId, It.IsAny <IEnumerable <string> >(), It.IsAny <RequestPriority>()))
                .Returns((string repoName, long hookId, IEnumerable <string> eventList, RequestPriority priority) => {
                    var result = new GitHubResponse <Webhook>(null)
                    {
                        Result = new Webhook()
                        {
                            Id     = 8001,
                            Active = true,
                            Config = new WebhookConfiguration()
                            {
                                ContentType = "json",
                                InsecureSsl = false,
                                Secret      = "*******",
                                Url         = $"https://{Configuration.ApiHostName}/webhook/org/1234",
                            },
                            Events = eventList,
                            Name   = "web",
                        },
                        Status = HttpStatusCode.OK,
                    };
                    return(Task.FromResult(result));
                });

                var orgActor = CreateOrgActor(org.Id, org.Login);
                await orgActor.AddOrUpdateOrganizationWebhooks(context, mock.Object);

                mock.Verify(x => x.EditOrganizationWebhookEvents(org.Login, (long)hook.GitHubId, OrganizationActor.RequiredEvents, It.IsAny <RequestPriority>()));
                context.Entry(hook).Reload();
                Assert.AreEqual(OrganizationActor.RequiredEvents.ToArray(), hook.Events.Split(','));
            }
        }
        public async Task WillRemoveExistingHooksBeforeAddingOneForOrg()
        {
            using (var context = new ShipHubContext()) {
                var user = TestUtil.MakeTestUser(context);
                var org  = TestUtil.MakeTestOrg(context);
                await context.SaveChangesAsync();

                var mock = new Mock <IGitHubActor>();

                mock
                .Setup(x => x.OrganizationWebhooks(org.Login, null, It.IsAny <RequestPriority>()))
                .ReturnsAsync(new GitHubResponse <IEnumerable <Webhook> >(null)
                {
                    Status = HttpStatusCode.OK,
                    Result = new List <Webhook>()
                    {
                        new Webhook()
                        {
                            Id     = 8001,
                            Active = true,
                            Config = new WebhookConfiguration()
                            {
                                ContentType = "json",
                                InsecureSsl = false,
                                Secret      = "*******",
                                Url         = $"https://{Configuration.ApiHostName}/webhook/org/1",
                            },
                            Events = new string[] {
                            },
                            Name   = "web",
                        },
                        new Webhook()
                        {
                            Id     = 8002,
                            Active = true,
                            Config = new WebhookConfiguration()
                            {
                                ContentType = "json",
                                InsecureSsl = false,
                                Secret      = "*******",
                                Url         = $"https://{Configuration.ApiHostName}/webhook/repo/2",
                            },
                            Events = new string[] {
                            },
                            Name   = "web",
                        },
                    },
                });

                var deletedHookIds = new List <long>();

                mock
                .Setup(x => x.DeleteOrganizationWebhook(org.Login, It.IsAny <long>(), It.IsAny <RequestPriority>()))
                .ReturnsAsync(new GitHubResponse <bool>(null)
                {
                    Result = true,
                    Status = HttpStatusCode.OK,
                })
                .Callback((string fullName, long hookId, RequestPriority priority) => {
                    deletedHookIds.Add(hookId);
                });

                mock
                .Setup(x => x.AddOrganizationWebhook(org.Login, It.IsAny <Webhook>(), It.IsAny <RequestPriority>()))
                .ReturnsAsync(new GitHubResponse <Webhook>(null)
                {
                    Result = new Webhook()
                    {
                        Id = 9999,
                    },
                    Status = HttpStatusCode.OK,
                });

                var orgActor = CreateOrgActor(org.Id, org.Login);
                await orgActor.AddOrUpdateOrganizationWebhooks(context, mock.Object);

                var hook = context.Hooks.Single(x => x.OrganizationId == org.Id);

                Assert.AreEqual(new long[] { 8001, 8002 }, deletedHookIds.ToArray());
                Assert.NotNull(hook);
            }
        }
        public async Task WillAddHookWhenNoneExistsForOrg()
        {
            using (var context = new ShipHubContext()) {
                var user = TestUtil.MakeTestUser(context);
                var org  = TestUtil.MakeTestOrg(context);
                await context.SaveChangesAsync();

                await context.SetUserOrganizations(user.Id, new[] { org.Id });

                var orgLogItem           = context.SyncLogs.Single(x => x.OwnerType == "org" && x.OwnerId == org.Id && x.ItemType == "account" && x.ItemId == org.Id);
                var orgLogItemRowVersion = orgLogItem.RowVersion;

                var mock = new Mock <IGitHubActor>();

                mock
                .Setup(x => x.OrganizationWebhooks(org.Login, null, It.IsAny <RequestPriority>()))
                .ReturnsAsync(new GitHubResponse <IEnumerable <Webhook> >(null)
                {
                    Result = new List <Webhook>(),
                    Status = HttpStatusCode.OK,
                });

                Webhook installWebhook = null;

                mock
                .Setup(x => x.AddOrganizationWebhook(org.Login, It.IsAny <Webhook>(), It.IsAny <RequestPriority>()))
                .ReturnsAsync(new GitHubResponse <Webhook>(null)
                {
                    Result = new Webhook()
                    {
                        Id = 9999,
                    },
                    Status = HttpStatusCode.OK,
                })
                .Callback((string login, Webhook webhook, RequestPriority priority) => {
                    installWebhook = webhook;
                });

                var orgActor = CreateOrgActor(org.Id, org.Login);
                var changes  = await orgActor.AddOrUpdateOrganizationWebhooks(context, mock.Object);

                var hook = context.Hooks.Single(x => x.OrganizationId == org.Id);

                Assert.AreEqual(OrganizationActor.RequiredEvents, new HashSet <string>(hook.Events.Split(',')));
                Assert.AreEqual(org.Id, hook.OrganizationId);
                Assert.AreEqual(9999, hook.GitHubId);
                Assert.Null(hook.RepositoryId);
                Assert.Null(hook.LastSeen);
                Assert.NotNull(hook.Secret);

                Assert.AreEqual("web", installWebhook.Name);
                Assert.AreEqual(true, installWebhook.Active);
                Assert.AreEqual(OrganizationActor.RequiredEvents, new HashSet <string>(installWebhook.Events));
                Assert.AreEqual("json", installWebhook.Config.ContentType);
                Assert.AreEqual(false, installWebhook.Config.InsecureSsl);
                Assert.AreEqual(hook.Secret.ToString(), installWebhook.Config.Secret);

                orgLogItem = context.SyncLogs.Single(x => x.OwnerType == "org" && x.OwnerId == org.Id && x.ItemType == "account" && x.ItemId == org.Id);
                Assert.Greater(orgLogItem.RowVersion, orgLogItemRowVersion,
                               "row version should get bumped so the org gets synced");
                Assert.AreEqual(new long[] { org.Id }, changes.Organizations.ToArray());
            }
        }
        public async Task WillEditHookWhenEventListIsNotCompleteForRepo()
        {
            using (var context = new ShipHubContext()) {
                var user = TestUtil.MakeTestUser(context);
                var repo = TestUtil.MakeTestRepo(context, user.Id);
                var hook = context.Hooks.Add(new Hook()
                {
                    Id           = 1001,
                    Events       = "event1,event2",
                    GitHubId     = 8001,
                    RepositoryId = repo.Id,
                    Secret       = Guid.NewGuid(),
                });
                await context.SaveChangesAsync();

                var mock = new Mock <IGitHubActor>();

                mock
                .Setup(x => x.RepositoryWebhooks(repo.FullName, null, It.IsAny <RequestPriority>()))
                .ReturnsAsync(new GitHubResponse <IEnumerable <Webhook> >(null)
                {
                    Result = new List <Webhook>()
                    {
                        new Webhook()
                        {
                            Id     = 8001,
                            Active = true,
                            Config = new WebhookConfiguration()
                            {
                                ContentType = "json",
                                InsecureSsl = false,
                                Secret      = "*******",
                                Url         = $"https://{Configuration.ApiHostName}/webhook/repo/1234",
                            },
                            Events = new string[] {
                                "event1",
                                "event2",
                            },
                            Name = "web",
                        },
                    },
                });

                mock
                .Setup(x => x.EditRepositoryWebhookEvents(repo.FullName, (long)hook.GitHubId, It.IsAny <IEnumerable <string> >(), It.IsAny <RequestPriority>()))
                .Returns((string repoName, long hookId, IEnumerable <string> eventList, RequestPriority priority) => {
                    var result = new GitHubResponse <Webhook>(null)
                    {
                        Result = new Webhook()
                        {
                            Id     = 8001,
                            Active = true,
                            Config = new WebhookConfiguration()
                            {
                                ContentType = "json",
                                InsecureSsl = false,
                                Secret      = "*******",
                                Url         = $"https://{Configuration.ApiHostName}/webhook/repo/1234",
                            },
                            Events = eventList,
                            Name   = "web",
                        },
                        Status = HttpStatusCode.OK,
                    };
                    return(Task.FromResult(result));
                });

                var repoActor = CreateRepoActor(repo.Id, repo.FullName);
                var changes   = await repoActor.AddOrUpdateWebhooks(mock.Object);

                mock.Verify(x => x.EditRepositoryWebhookEvents(repo.FullName, (long)hook.GitHubId, RepositoryActor.RequiredEvents, It.IsAny <RequestPriority>()));
                context.Entry(hook).Reload();
                Assert.IsTrue(RepositoryActor.RequiredEvents.SetEquals(hook.Events.Split(',')));
                Assert.IsFalse(changes.Repositories.Any());
            }
        }
        public async Task WillAddHookWhenNoneExistsForRepo()
        {
            using (var context = new ShipHubContext()) {
                var user = TestUtil.MakeTestUser(context);
                var repo = TestUtil.MakeTestRepo(context, user.Id);
                await context.SaveChangesAsync();

                var repoLogItem           = context.SyncLogs.Single(x => x.OwnerType == "repo" && x.OwnerId == repo.Id && x.ItemType == "repository" && x.ItemId == repo.Id);
                var repoLogItemRowVersion = repoLogItem.RowVersion;

                var mock = new Mock <IGitHubActor>();

                mock
                .Setup(x => x.RepositoryWebhooks(repo.FullName, null, It.IsAny <RequestPriority>()))
                .ReturnsAsync(new GitHubResponse <IEnumerable <Webhook> >(null)
                {
                    Result = new List <Webhook>(),
                    Status = HttpStatusCode.OK,
                });

                string  installRepoName = null;
                Webhook installWebhook  = null;

                mock
                .Setup(x => x.AddRepositoryWebhook(repo.FullName, It.IsAny <Webhook>(), It.IsAny <RequestPriority>()))
                .ReturnsAsync(new GitHubResponse <Webhook>(null)
                {
                    Result = new Webhook()
                    {
                        Id = 9999,
                    },
                    Status = HttpStatusCode.OK,
                })
                .Callback((string fullName, Webhook webhook, RequestPriority priority) => {
                    installRepoName = fullName;
                    installWebhook  = webhook;
                });

                var repoActor = CreateRepoActor(repo.Id, repo.FullName);
                var changes   = await repoActor.AddOrUpdateWebhooks(mock.Object);

                var hook = context.Hooks.Single(x => x.RepositoryId == repo.Id);

                Assert.IsTrue(RepositoryActor.RequiredEvents.SetEquals(hook.Events.Split(',')));
                Assert.AreEqual(repo.Id, hook.RepositoryId);
                Assert.AreEqual(9999, hook.GitHubId);
                Assert.Null(hook.OrganizationId);
                Assert.Null(hook.LastSeen);
                Assert.NotNull(hook.Secret);

                Assert.AreEqual(repo.FullName, installRepoName);
                Assert.AreEqual("web", installWebhook.Name);
                Assert.AreEqual(true, installWebhook.Active);
                Assert.IsTrue(RepositoryActor.RequiredEvents.SetEquals(installWebhook.Events));
                Assert.AreEqual("json", installWebhook.Config.ContentType);
                Assert.AreEqual(false, installWebhook.Config.InsecureSsl);
                Assert.AreEqual(hook.Secret.ToString(), installWebhook.Config.Secret);

                repoLogItem = context.SyncLogs.Single(x => x.OwnerType == "repo" && x.OwnerId == repo.Id && x.ItemType == "repository" && x.ItemId == repo.Id);
                Assert.Greater(repoLogItem.RowVersion, repoLogItemRowVersion,
                               "row version should get bumped so the repo gets synced");
                Assert.AreEqual(new long[] { repo.Id }, changes.Repositories.ToArray());
            }
        }