Beispiel #1
0
        public async Task CanRetrieveAndCacheUserAndAccountsAsync()
        {
            var orgs = new[]
            {
                CreateOctokitOrganization("github"),
                CreateOctokitOrganization("fake")
            };
            var apiClient = Substitute.For <IApiClient>();

            apiClient.GetUser().Returns(Observable.Return(CreateOctokitUser("snoopy")));
            apiClient.GetOrganizations().Returns(orgs.ToObservable());
            var cache        = new InMemoryBlobCache();
            var modelService = CreateTarget(apiClient: apiClient, hostCache: cache);
            await modelService.InsertUser(new AccountCacheItem { Login = "******" });

            var fetched = await modelService.GetAccounts();

            Assert.That(3, Is.EqualTo(fetched.Count));
            Assert.That("snoopy", Is.EqualTo(fetched[0].Login));
            Assert.That("github", Is.EqualTo(fetched[1].Login));
            Assert.That("fake", Is.EqualTo(fetched[2].Login));
            var cachedOrgs = await cache.GetObject <IReadOnlyList <AccountCacheItem> >("snoopy|orgs");

            Assert.That(2, Is.EqualTo(cachedOrgs.Count));
            Assert.That("github", Is.EqualTo(cachedOrgs[0].Login));
            Assert.That("fake", Is.EqualTo(cachedOrgs[1].Login));
            var cachedUser = await cache.GetObject <AccountCacheItem>("user");

            Assert.That("snoopy", Is.EqualTo(cachedUser.Login));
        }
Beispiel #2
0
        public async Task CanRetrieveUserFromCacheAndAccountsFromApi()
        {
            var orgs = new[]
            {
                CreateOctokitOrganization("github"),
                CreateOctokitOrganization("fake")
            };
            var apiClient = Substitute.For <IApiClient>();

            apiClient.GetOrganizations().Returns(orgs.ToObservable());
            var cache        = new InMemoryBlobCache();
            var modelService = new ModelService(apiClient, cache, Substitute.For <IAvatarProvider>());
            await modelService.InsertUser(new AccountCacheItem(CreateOctokitUser("octocat")));

            var fetched = await modelService.GetAccounts();

            Assert.Equal(3, fetched.Count);
            Assert.Equal("octocat", fetched[0].Login);
            Assert.Equal("github", fetched[1].Login);
            Assert.Equal("fake", fetched[2].Login);
            var cachedOrgs = await cache.GetObject <IReadOnlyList <AccountCacheItem> >("octocat|orgs");

            Assert.Equal(2, cachedOrgs.Count);
            Assert.Equal("github", cachedOrgs[0].Login);
            Assert.Equal("fake", cachedOrgs[1].Login);
            var cachedUser = await cache.GetObject <AccountCacheItem>("user");

            Assert.Equal("octocat", cachedUser.Login);
        }
        public async Task CanRetrieveAndCacheLicenses()
        {
            var data = new[]
            {
                new LicenseMetadata("mit", "MIT", new Uri("https://github.com/")),
                new LicenseMetadata("apache", "Apache", new Uri("https://github.com/"))
            };

            var apiClient = Substitute.For <IApiClient>();

            apiClient.GetLicenses().Returns(data.ToObservable());
            var cache        = new InMemoryBlobCache();
            var modelService = new ModelService(apiClient, cache, Substitute.For <IAvatarProvider>());

            var fetched = await modelService.GetLicenses().ToList();

            Assert.Equal(2, fetched.Count);
            for (int i = 0; i < data.Length; i++)
            {
                Assert.Equal(data[i].Name, fetched[i].Name);
            }

            var indexKey = CacheIndex.LicensesPrefix;
            var cached   = await cache.GetObject <CacheIndex>(indexKey);

            Assert.Equal(2, cached.Keys.Count);

            var items = await cache.GetObjects <LicenseCacheItem>(cached.Keys).Take(1);

            for (int i = 0; i < data.Length; i++)
            {
                Assert.Equal(data[i].Name, items[indexKey + "|" + data[i].Key].Name);
            }
        }
Beispiel #4
0
        public async Task CanRetrieveAndCacheLicensesAsync()
        {
            var data = new[]
            {
                new LicenseMetadata("mit", null, "MIT", "foo", "https://github.com/", false),
                new LicenseMetadata("apache", null, "Apache", "foo", "https://github.com/", false)
            };

            var apiClient = Substitute.For <IApiClient>();

            apiClient.GetLicenses().Returns(data.ToObservable());
            var cache        = new InMemoryBlobCache();
            var modelService = CreateTarget(apiClient: apiClient, hostCache: cache);

            var fetched = await modelService.GetLicenses().ToList();

            Assert.That(2, Is.EqualTo(fetched.Count));
            for (int i = 0; i < data.Length; i++)
            {
                Assert.That(data[i].Name, Is.EqualTo(fetched[i].Name));
            }

            var indexKey = CacheIndex.LicensesPrefix;
            var cached   = await cache.GetObject <CacheIndex>(indexKey);

            Assert.That(2, Is.EqualTo(cached.Keys.Count));

            var items = await cache.GetObjects <LicenseCacheItem>(cached.Keys).Take(1);

            for (int i = 0; i < data.Length; i++)
            {
                Assert.That(data[i].Name, Is.EqualTo(items[indexKey + "|" + data[i].Key].Name));
            }
        }
Beispiel #5
0
        public async Task CanRetrieveAndCacheLicenses()
        {
            var licenses = new[]
            {
                new LicenseMetadata("mit", "MIT", new Uri("https://github.com/")),
                new LicenseMetadata("apache", "Apache", new Uri("https://github.com/"))
            };
            var apiClient = Substitute.For <IApiClient>();

            apiClient.GetLicenses().Returns(licenses.ToObservable());
            var cache        = new InMemoryBlobCache();
            var modelService = new ModelService(apiClient, cache, Substitute.For <IAvatarProvider>());

            var fetched = await modelService.GetLicenses();

            Assert.Equal(3, fetched.Count);
            Assert.Equal("None", fetched[0].Name);
            Assert.Equal("MIT", fetched[1].Name);
            Assert.Equal("Apache", fetched[2].Name);
            var cached = await cache.GetObject <IReadOnlyList <ModelService.LicenseCacheItem> >("licenses");

            Assert.Equal(2, cached.Count);
            Assert.Equal("mit", cached[0].Key);
            Assert.Equal("apache", cached[1].Key);
        }
        public async Task DoesNotLogInWhenRetrievingOauthTokenFails()
        {
            var apiClient = Substitute.For <IApiClient>();

            apiClient.HostAddress.Returns(HostAddress.GitHubDotComHostAddress);
            apiClient.GetOrCreateApplicationAuthenticationCode(
                Args.TwoFactorChallengCallback, Args.String, Args.Boolean)
            .Returns(Observable.Throw <ApplicationAuthorization>(new NotFoundException("", HttpStatusCode.BadGateway)));
            apiClient.GetUser().Returns(Observable.Return(CreateUserAndScopes("jiminy")));
            var hostCache    = new InMemoryBlobCache();
            var modelService = new ModelService(apiClient, hostCache, Substitute.For <IAvatarProvider>());
            var loginCache   = new TestLoginCache();
            var host         = new RepositoryHost(apiClient, modelService, loginCache, Substitute.For <ITwoFactorChallengeHandler>());

            await Assert.ThrowsAsync <NotFoundException>(async() => await host.LogIn("jiminy", "cricket"));

            await Assert.ThrowsAsync <KeyNotFoundException>(
                async() => await hostCache.GetObject <AccountCacheItem>("user"));

            var loginInfo = await loginCache.GetLoginAsync(HostAddress.GitHubDotComHostAddress);

            Assert.Equal("jiminy", loginInfo.UserName);
            Assert.Equal("cricket", loginInfo.Password);
            Assert.False(host.IsLoggedIn);
        }
        public async Task LogsTheUserInSuccessfullyAndCachesRelevantInfo()
        {
            var apiClient = Substitute.For <IApiClient>();

            apiClient.HostAddress.Returns(HostAddress.GitHubDotComHostAddress);
            apiClient.GetOrCreateApplicationAuthenticationCode(
                Args.TwoFactorChallengCallback, Args.String, Args.Boolean)
            .Returns(Observable.Return(new ApplicationAuthorization("S3CR3TS")));
            apiClient.GetUser().Returns(Observable.Return(CreateUserAndScopes("baymax")));
            var hostCache    = new InMemoryBlobCache();
            var modelService = new ModelService(apiClient, hostCache, Substitute.For <IAvatarProvider>());
            var loginCache   = new TestLoginCache();
            var host         = new RepositoryHost(apiClient, modelService, loginCache, Substitute.For <ITwoFactorChallengeHandler>());

            var result = await host.LogIn("baymax", "aPassword");

            Assert.Equal(AuthenticationResult.Success, result);
            var user = await hostCache.GetObject <AccountCacheItem>("user");

            Assert.NotNull(user);
            Assert.Equal("baymax", user.Login);
            var loginInfo = await loginCache.GetLoginAsync(HostAddress.GitHubDotComHostAddress);

            Assert.Equal("baymax", loginInfo.UserName);
            Assert.Equal("S3CR3TS", loginInfo.Password);
            Assert.True(host.IsLoggedIn);
        }
        public async Task LogsTheUserInSuccessfullyAndCachesRelevantInfo()
        {
            var apiClient = Substitute.For<IApiClient>();
            apiClient.HostAddress.Returns(HostAddress.GitHubDotComHostAddress);
            apiClient.GetOrCreateApplicationAuthenticationCode(
                Args.TwoFactorChallengCallback, Args.String, Args.Boolean)
                .Returns(Observable.Return(new ApplicationAuthorization("S3CR3TS")));
            apiClient.GetUser().Returns(Observable.Return(CreateUserAndScopes("baymax")));
            var hostCache = new InMemoryBlobCache();
            var modelService = new ModelService(apiClient, hostCache, Substitute.For<IAvatarProvider>());
            var loginCache = new TestLoginCache();
            var usage = Substitute.For<IUsageTracker>();
            var host = new RepositoryHost(apiClient, modelService, loginCache, Substitute.For<ITwoFactorChallengeHandler>(), usage);

            var result = await host.LogIn("baymax", "aPassword");

            Assert.Equal(AuthenticationResult.Success, result);
            var user = await hostCache.GetObject<AccountCacheItem>("user");
            Assert.NotNull(user);
            Assert.Equal("baymax", user.Login);
            var loginInfo = await loginCache.GetLoginAsync(HostAddress.GitHubDotComHostAddress);
            Assert.Equal("baymax", loginInfo.UserName);
            Assert.Equal("S3CR3TS", loginInfo.Password);
            Assert.True(host.IsLoggedIn);
        }
Beispiel #9
0
        public async Task CanRetrieveAndCacheGitIgnoresAsync()
        {
            var data      = new[] { "dotnet", "peanuts", "bloomcounty" };
            var apiClient = Substitute.For <IApiClient>();

            apiClient.GetGitIgnoreTemplates().Returns(data.ToObservable());
            var cache        = new InMemoryBlobCache();
            var modelService = CreateTarget(apiClient: apiClient, hostCache: cache);

            var fetched = await modelService.GetGitIgnoreTemplates().ToList();

            Assert.That(3, Is.EqualTo(fetched.Count));
            for (int i = 0; i < data.Length; i++)
            {
                Assert.That(data[i], Is.EqualTo(fetched[i].Name));
            }

            var indexKey = CacheIndex.GitIgnoresPrefix;
            var cached   = await cache.GetObject <CacheIndex>(indexKey);

            Assert.That(3, Is.EqualTo(cached.Keys.Count));

            var items = await cache.GetObjects <GitIgnoreCacheItem>(cached.Keys).Take(1);

            for (int i = 0; i < data.Length; i++)
            {
                Assert.That(data[i], Is.EqualTo(items[indexKey + "|" + data[i]].Name));
            }
        }
Beispiel #10
0
        public async Task AddsUserToCacheAsync()
        {
            var cache        = new InMemoryBlobCache();
            var modelService = CreateTarget(hostCache: cache);

            var user = await modelService.InsertUser(new AccountCacheItem(CreateOctokitUser("octocat")));

            var cached = await cache.GetObject <AccountCacheItem>("user");

            Assert.That("octocat", Is.EqualTo(cached.Login));
        }
        public async Task AddsUserToCache()
        {
            var apiClient = Substitute.For<IApiClient>();
            var cache = new InMemoryBlobCache();
            var modelService = new ModelService(apiClient, cache, Substitute.For<IAvatarProvider>());

            var user = await modelService.InsertUser(new AccountCacheItem(CreateOctokitUser("octocat")));

            var cached = await cache.GetObject<AccountCacheItem>("user");
            Assert.Equal("octocat", cached.Login);
        }
Beispiel #12
0
        public async Task AddsUserToCache()
        {
            var apiClient    = Substitute.For <IApiClient>();
            var cache        = new InMemoryBlobCache();
            var modelService = new ModelService(apiClient, cache, Substitute.For <IAvatarProvider>());

            var user = await modelService.InsertUser(new AccountCacheItem(CreateOctokitUser("octocat")));

            var cached = await cache.GetObject <AccountCacheItem>("user");

            Assert.Equal("octocat", cached.Login);
        }
            public async Task NotFoundRequestIsMarked()
            {
                var fetcher = Substitute.For<IArtworkFetcher>();
                fetcher.RetrieveAsync(Arg.Any<string>(), Arg.Any<string>()).Returns(Task.FromResult<Uri>(null));
                var blobCache = new InMemoryBlobCache();
                var fixture = new ArtworkCache(blobCache, fetcher);
                string artist = "A";
                string album = "B";
                string lookupKey = BlobCacheKeys.GetKeyForOnlineArtwork(artist, album);

                await fixture.FetchOnline(artist, album);

                Assert.Equal("FAILED", await blobCache.GetObject<string>(lookupKey));
            }
Beispiel #14
0
            public async Task NotFoundRequestIsMarked()
            {
                var fetcher = Substitute.For <IArtworkFetcher>();

                fetcher.RetrieveAsync(Arg.Any <string>(), Arg.Any <string>()).Returns(Task.FromResult <Uri>(null));
                var    blobCache = new InMemoryBlobCache();
                var    fixture   = new ArtworkCache(blobCache, fetcher);
                string artist    = "A";
                string album     = "B";
                string lookupKey = BlobCacheKeys.GetKeyForOnlineArtwork(artist, album);

                await fixture.FetchOnline(artist, album);

                Assert.Equal("FAILED", await blobCache.GetObject <string>(lookupKey));
            }
        public async Task DoesNotLogInWhenRetrievingOauthTokenFails()
        {
            var apiClient = Substitute.For <IApiClient>();

            apiClient.HostAddress.Returns(HostAddress.GitHubDotComHostAddress);
            var hostCache    = new InMemoryBlobCache();
            var modelService = new ModelService(apiClient, hostCache, Substitute.For <IAvatarProvider>());
            var loginManager = Substitute.For <ILoginManager>();

            loginManager.Login(HostAddress.GitHubDotComHostAddress, Arg.Any <IGitHubClient>(), "jiminy", "cricket")
            .Returns <User>(_ => { throw new NotFoundException("", HttpStatusCode.BadGateway); });
            var loginCache = new TestLoginCache();
            var usage      = Substitute.For <IUsageTracker>();
            var host       = new RepositoryHost(apiClient, modelService, loginManager, loginCache, usage);

            await Assert.ThrowsAsync <NotFoundException>(async() => await host.LogIn("jiminy", "cricket"));

            await Assert.ThrowsAsync <KeyNotFoundException>(
                async() => await hostCache.GetObject <AccountCacheItem>("user"));
        }
        public async Task CanRetrieveAndCacheGitIgnores()
        {
            var templates = new[] { "dotnet", "peanuts", "bloomcounty" };
            var apiClient = Substitute.For<IApiClient>();
            apiClient.GetGitIgnoreTemplates().Returns(templates.ToObservable());
            var cache = new InMemoryBlobCache();
            var modelService = new ModelService(apiClient, cache, Substitute.For<IAvatarProvider>());

            var fetched = await modelService.GetGitIgnoreTemplates();

            Assert.Equal(4, fetched.Count);
            Assert.Equal("None", fetched[0].Name);
            Assert.Equal("dotnet", fetched[1].Name);
            Assert.Equal("peanuts", fetched[2].Name);
            Assert.Equal("bloomcounty", fetched[3].Name);
            var cached = await cache.GetObject<IReadOnlyList<string>>("gitignores");
            Assert.Equal(3, cached.Count);
            Assert.Equal("dotnet", cached[0]);
            Assert.Equal("peanuts", cached[1]);
            Assert.Equal("bloomcounty", cached[2]);
        }
        public async Task CanRetrieveAndCacheGitIgnores()
        {
            var data = new[] { "dotnet", "peanuts", "bloomcounty" };
            var apiClient = Substitute.For<IApiClient>();
            apiClient.GetGitIgnoreTemplates().Returns(data.ToObservable());
            var cache = new InMemoryBlobCache();
            var modelService = new ModelService(apiClient, cache, Substitute.For<IAvatarProvider>());

            var fetched = await modelService.GetGitIgnoreTemplates().ToList();

            Assert.Equal(3, fetched.Count);
            for (int i = 0; i < data.Length; i++)
                Assert.Equal(data[i], fetched[i].Name);

            var indexKey = CacheIndex.GitIgnoresPrefix;
            var cached = await cache.GetObject<CacheIndex>(indexKey);
            Assert.Equal(3, cached.Keys.Count);

            var items = await cache.GetObjects<GitIgnoreCacheItem>(cached.Keys).Take(1);
            for (int i = 0; i < data.Length; i++)
                Assert.Equal(data[i], items[indexKey + "|" + data[i]].Name);
        }
        public async Task LogsTheUserInSuccessfullyAndCachesRelevantInfo()
        {
            var apiClient = Substitute.For <IApiClient>();

            apiClient.HostAddress.Returns(HostAddress.GitHubDotComHostAddress);
            var hostCache    = new InMemoryBlobCache();
            var modelService = new ModelService(apiClient, hostCache, Substitute.For <IAvatarProvider>());
            var loginManager = Substitute.For <ILoginManager>();

            loginManager.LoginFromCache(HostAddress.GitHubDotComHostAddress, Arg.Any <IGitHubClient>()).Returns(CreateOctokitUser("baymax"));
            var loginCache = new TestLoginCache();
            var usage      = Substitute.For <IUsageTracker>();
            var host       = new RepositoryHost(apiClient, modelService, loginManager, loginCache, usage);

            var result = await host.LogInFromCache();

            Assert.Equal(AuthenticationResult.Success, result);
            var user = await hostCache.GetObject <AccountCacheItem>("user");

            Assert.NotNull(user);
            Assert.Equal("baymax", user.Login);
        }
        public async Task DoesNotLogInWhenRetrievingOauthTokenFails()
        {
            var apiClient = Substitute.For<IApiClient>();
            apiClient.HostAddress.Returns(HostAddress.GitHubDotComHostAddress);
            apiClient.GetOrCreateApplicationAuthenticationCode(
                Args.TwoFactorChallengCallback, Args.String, Args.Boolean)
                .Returns(Observable.Throw<ApplicationAuthorization>(new NotFoundException("", HttpStatusCode.BadGateway)));
            apiClient.GetUser().Returns(Observable.Return(CreateOctokitUser("jiminy")));
            var hostCache = new InMemoryBlobCache();
            var modelService = new ModelService(apiClient, hostCache, Substitute.For<IAvatarProvider>());
            var loginCache = new TestLoginCache();
            var host = new RepositoryHost(apiClient, modelService, loginCache, Substitute.For<ITwoFactorChallengeHandler>());

            await Assert.ThrowsAsync<NotFoundException>(async () => await host.LogIn("jiminy", "cricket"));

            await Assert.ThrowsAsync<KeyNotFoundException>(
                async () => await hostCache.GetObject<AccountCacheItem>("user"));
            var loginInfo = await loginCache.GetLoginAsync(HostAddress.GitHubDotComHostAddress);
            Assert.Equal("jiminy", loginInfo.UserName);
            Assert.Equal("cricket", loginInfo.Password);
            Assert.False(host.IsLoggedIn);
        }
Beispiel #20
0
        public async Task CanRetrieveAndCacheGitIgnores()
        {
            var templates = new[] { "dotnet", "peanuts", "bloomcounty" };
            var apiClient = Substitute.For <IApiClient>();

            apiClient.GetGitIgnoreTemplates().Returns(templates.ToObservable());
            var cache        = new InMemoryBlobCache();
            var modelService = new ModelService(apiClient, cache, Substitute.For <IAvatarProvider>());

            var fetched = await modelService.GetGitIgnoreTemplates();

            Assert.Equal(4, fetched.Count);
            Assert.Equal("None", fetched[0].Name);
            Assert.Equal("dotnet", fetched[1].Name);
            Assert.Equal("peanuts", fetched[2].Name);
            Assert.Equal("bloomcounty", fetched[3].Name);
            var cached = await cache.GetObject <IReadOnlyList <string> >("gitignores");

            Assert.Equal(3, cached.Count);
            Assert.Equal("dotnet", cached[0]);
            Assert.Equal("peanuts", cached[1]);
            Assert.Equal("bloomcounty", cached[2]);
        }
Beispiel #21
0
        public async Task ExpiredIndexClearsItemsAsync()
        {
            var expected = 5;

            var username = "******";
            var reponame = "repo";

            var cache        = new InMemoryBlobCache();
            var apiClient    = Substitute.For <IApiClient>();
            var modelService = CreateTarget(apiClient: apiClient, hostCache: cache);
            var user         = CreateOctokitUser(username);

            apiClient.GetUser().Returns(Observable.Return(user));
            apiClient.GetOrganizations().Returns(Observable.Empty <Organization>());
            var act = modelService.GetAccounts().ToEnumerable().First().First();

            var repo = Substitute.For <LocalRepositoryModel>();

            repo.Name.Returns(reponame);
            repo.Owner.Returns(user.Login);
            repo.CloneUrl.Returns(new UriString("https://github.com/" + username + "/" + reponame));

            var indexKey = string.Format(CultureInfo.InvariantCulture, "{0}|{1}:{2}", CacheIndex.PRPrefix, user.Login, repo.Name);

            var prcache = Enumerable.Range(1, expected)
                          .Select(id => CreatePullRequest(user, id, ItemState.Open, "Cache " + id, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow));

            // seed the cache
            prcache
            .Select(item => new ModelService.PullRequestCacheItem(item))
            .Select(item => item.Save <ModelService.PullRequestCacheItem>(cache, indexKey).ToEnumerable().First())
            .SelectMany(item => CacheIndex.AddAndSaveToIndex(cache, indexKey, item).ToEnumerable())
            .ToList();

            // expire the index
            var indexobj = await cache.GetObject <CacheIndex>(indexKey);

            indexobj.UpdatedAt = DateTimeOffset.UtcNow - TimeSpan.FromMinutes(6);
            await cache.InsertObject(indexKey, indexobj);

            var prlive = Observable.Range(5, expected)
                         .Select(id => CreatePullRequest(user, id, ItemState.Open, "Live " + id, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow, 0))
                         .DelaySubscription(TimeSpan.FromMilliseconds(10));

            apiClient.GetPullRequestsForRepository(user.Login, repo.Name).Returns(prlive);

            await modelService.InsertUser(new AccountCacheItem(user));

            ITrackingCollection <IPullRequestModel> col = new TrackingCollection <IPullRequestModel>();

            modelService.GetPullRequests(repo, col);
            col.ProcessingDelay = TimeSpan.Zero;

            var count = 0;
            var done  = new ReplaySubject <Unit>();

            done.OnNext(Unit.Default);
            done.Subscribe();

            col.Subscribe(t =>
            {
                // we get all the items from the cache (items 1-5), all the items from the live (items 5-9),
                // and 4 deletions (items 1-4) because the cache expired the items that were not
                // a part of the live data
                if (++count == 14)
                {
                    done.OnCompleted();
                }
            }, () => { });

            await done;

            Assert.That(5, Is.EqualTo(col.Count));

            /**Assert.Collection(col,
             *  t => { Assert.StartsWith("Live", t.Title); Assert.Equal(5, t.Number); },
             *  t => { Assert.StartsWith("Live", t.Title); Assert.Equal(6, t.Number); },
             *  t => { Assert.StartsWith("Live", t.Title); Assert.Equal(7, t.Number); },
             *  t => { Assert.StartsWith("Live", t.Title); Assert.Equal(8, t.Number); },
             *  t => { Assert.StartsWith("Live", t.Title); Assert.Equal(9, t.Number); }
             * );*/
        }
        public async Task ExpiredIndexClearsItems()
        {
            var expected = 5;

            var username = "******";
            var reponame = "repo";

            var cache = new InMemoryBlobCache();
            var apiClient = Substitute.For<IApiClient>();
            var modelService = new ModelService(apiClient, cache, Substitute.For<IAvatarProvider>());
            var user = CreateOctokitUser(username);
            apiClient.GetUser().Returns(Observable.Return(user));
            apiClient.GetOrganizations().Returns(Observable.Empty<Organization>());
            var act = modelService.GetAccounts().ToEnumerable().First().First();

            var repo = Substitute.For<ISimpleRepositoryModel>();
            repo.Name.Returns(reponame);
            repo.CloneUrl.Returns(new UriString("https://github.com/" + username + "/" + reponame));

            var indexKey = string.Format(CultureInfo.InvariantCulture, "{0}|{1}|pr", user.Login, repo.Name);

            var prcache = Enumerable.Range(1, expected)
                .Select(id => CreatePullRequest(user, id, ItemState.Open, "Cache " + id, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow, 0));

            // seed the cache
            prcache
                .Select(item => new ModelService.PullRequestCacheItem(item))
                .Select(item => item.Save<ModelService.PullRequestCacheItem>(cache, indexKey).ToEnumerable().First())
                .SelectMany(item => CacheIndex.AddAndSaveToIndex(cache, indexKey, item).ToEnumerable())
                .ToList();

            // expire the index
            var indexobj = await cache.GetObject<CacheIndex>(indexKey);
            indexobj.UpdatedAt = DateTimeOffset.UtcNow - TimeSpan.FromMinutes(6);
            await cache.InsertObject(indexKey, indexobj);

            var prlive = Observable.Range(5, expected)
                .Select(id => CreatePullRequest(user, id, ItemState.Open, "Live " + id, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow, 0))
                .DelaySubscription(TimeSpan.FromMilliseconds(10));

            apiClient.GetPullRequestsForRepository(user.Login, repo.Name).Returns(prlive);

            await modelService.InsertUser(new AccountCacheItem(user));
            var col = modelService.GetPullRequests(repo);
            col.ProcessingDelay = TimeSpan.Zero;

            var count = 0;
            var evt = new ManualResetEvent(false);
            col.Subscribe(t =>
            {
                // we get all the items from the cache (items 1-5), all the items from the live (items 5-9),
                // and 4 deletions (items 1-4) because the cache expired the items that were not
                // a part of the live data
                if (++count == 14)
                    evt.Set();
            }, () => { });


            evt.WaitOne();
            evt.Reset();

            Assert.Equal(5, col.Count);
            Assert.Collection(col, 
                t => { Assert.True(t.Title.StartsWith("Live")); Assert.Equal(5, t.Number); },
                t => { Assert.True(t.Title.StartsWith("Live")); Assert.Equal(6, t.Number); },
                t => { Assert.True(t.Title.StartsWith("Live")); Assert.Equal(7, t.Number); },
                t => { Assert.True(t.Title.StartsWith("Live")); Assert.Equal(8, t.Number); },
                t => { Assert.True(t.Title.StartsWith("Live")); Assert.Equal(9, t.Number); }
            );
        }
        public async Task CanRetrieveAndCacheRepositoriesForUserAndOrganizations()
        {
            var orgs = new[]
            {
                CreateOctokitOrganization("github"),
                CreateOctokitOrganization("octokit")
            };
            var ownedRepos = new[]
            {
                CreateRepository("haacked", "seegit"),
                CreateRepository("haacked", "codehaacks")
            };
            var memberRepos = new[]
            {
                CreateRepository("mojombo", "semver"),
                CreateRepository("ninject", "ninject"),
                CreateRepository("jabbr", "jabbr"),
                CreateRepository("fody", "nullguard")
            };
            var githubRepos = new[]
            {
                CreateRepository("github", "visualstudio")
            };
            var octokitRepos = new[]
            {
                CreateRepository("octokit", "octokit.net"),
                CreateRepository("octokit", "octokit.rb"),
                CreateRepository("octokit", "octokit.objc")
            };
            var apiClient = Substitute.For<IApiClient>();
            apiClient.GetOrganizations().Returns(orgs.ToObservable());
            apiClient.GetUserRepositories(RepositoryType.Owner).Returns(ownedRepos.ToObservable());
            apiClient.GetUserRepositories(RepositoryType.Member).Returns(memberRepos.ToObservable());
            apiClient.GetRepositoriesForOrganization("github").Returns(githubRepos.ToObservable());
            apiClient.GetRepositoriesForOrganization("octokit").Returns(octokitRepos.ToObservable());
            var cache = new InMemoryBlobCache();
            var modelService = new ModelService(apiClient, cache, Substitute.For<IAvatarProvider>());
            await modelService.InsertUser(new AccountCacheItem { Login = "******" });

            var fetched = await modelService.GetRepositories().ToList();

            Assert.Equal(4, fetched.Count);
            Assert.Equal(2, fetched[0].Count);
            Assert.Equal(4, fetched[1].Count);
            Assert.Equal(1, fetched[2].Count);
            Assert.Equal(3, fetched[3].Count);
            Assert.Equal("seegit", fetched[0][0].Name);
            Assert.Equal("codehaacks", fetched[0][1].Name);
            Assert.Equal("semver", fetched[1][0].Name);
            Assert.Equal("ninject", fetched[1][1].Name);
            Assert.Equal("jabbr", fetched[1][2].Name);
            Assert.Equal("nullguard", fetched[1][3].Name);
            Assert.Equal("visualstudio", fetched[2][0].Name);
            Assert.Equal("octokit.net", fetched[3][0].Name);
            Assert.Equal("octokit.rb", fetched[3][1].Name);
            Assert.Equal("octokit.objc", fetched[3][2].Name);
            var cachedOwnerRepositories = await cache.GetObject<IReadOnlyList<ModelService.RepositoryCacheItem>>("opus|Owner:repos");
            Assert.Equal(2, cachedOwnerRepositories.Count);
            Assert.Equal("seegit", cachedOwnerRepositories[0].Name);
            Assert.Equal("haacked", cachedOwnerRepositories[0].Owner.Login);
            Assert.Equal("codehaacks", cachedOwnerRepositories[1].Name);
            Assert.Equal("haacked", cachedOwnerRepositories[1].Owner.Login);
            var cachedMemberRepositories = await cache.GetObject<IReadOnlyList<ModelService.RepositoryCacheItem>>("opus|Member:repos");
            Assert.Equal(4, cachedMemberRepositories.Count);
            Assert.Equal("semver", cachedMemberRepositories[0].Name);
            Assert.Equal("mojombo", cachedMemberRepositories[0].Owner.Login);
            Assert.Equal("ninject", cachedMemberRepositories[1].Name);
            Assert.Equal("ninject", cachedMemberRepositories[1].Owner.Login);
            Assert.Equal("jabbr", cachedMemberRepositories[2].Name);
            Assert.Equal("jabbr", cachedMemberRepositories[2].Owner.Login);
            Assert.Equal("nullguard", cachedMemberRepositories[3].Name);
            Assert.Equal("fody", cachedMemberRepositories[3].Owner.Login);
            var cachedGitHubRepositories = await cache.GetObject<IReadOnlyList<ModelService.RepositoryCacheItem>>("opus|github|repos");
            Assert.Equal(1, cachedGitHubRepositories.Count);
            Assert.Equal("seegit", cachedOwnerRepositories[0].Name);
            Assert.Equal("haacked", cachedOwnerRepositories[0].Owner.Login);
            Assert.Equal("codehaacks", cachedOwnerRepositories[1].Name);
            Assert.Equal("haacked", cachedOwnerRepositories[1].Owner.Login);
            var cachedOctokitRepositories = await cache.GetObject<IReadOnlyList<ModelService.RepositoryCacheItem>>("opus|octokit|repos");
            Assert.Equal("octokit.net", cachedOctokitRepositories[0].Name);
            Assert.Equal("octokit", cachedOctokitRepositories[0].Owner.Login);
            Assert.Equal("octokit.rb", cachedOctokitRepositories[1].Name);
            Assert.Equal("octokit", cachedOctokitRepositories[1].Owner.Login);
            Assert.Equal("octokit.objc", cachedOctokitRepositories[2].Name);
            Assert.Equal("octokit", cachedOctokitRepositories[2].Owner.Login);
        }
        public async Task CanRetrieveUserFromCacheAndAccountsFromApi()
        {
            var orgs = new[]
            {
                CreateOctokitOrganization("github"),
                CreateOctokitOrganization("fake")
            };
            var apiClient = Substitute.For<IApiClient>();
            apiClient.GetOrganizations().Returns(orgs.ToObservable());
            var cache = new InMemoryBlobCache();
            var modelService = new ModelService(apiClient, cache, Substitute.For<IAvatarProvider>());
            await modelService.InsertUser(new AccountCacheItem(CreateOctokitUser("octocat")));

            var fetched = await modelService.GetAccounts();

            Assert.Equal(3, fetched.Count);
            Assert.Equal("octocat", fetched[0].Login);
            Assert.Equal("github", fetched[1].Login);
            Assert.Equal("fake", fetched[2].Login);
            var cachedOrgs = await cache.GetObject<IReadOnlyList<AccountCacheItem>>("octocat|orgs");
            Assert.Equal(2, cachedOrgs.Count);
            Assert.Equal("github", cachedOrgs[0].Login);
            Assert.Equal("fake", cachedOrgs[1].Login);
            var cachedUser = await cache.GetObject<AccountCacheItem>("user");
            Assert.Equal("octocat", cachedUser.Login);
        }
        public async Task CanRetrieveAndCacheLicenses()
        {
            var licenses = new[]
            {
                new LicenseMetadata("mit", "MIT", new Uri("https://github.com/")),
                new LicenseMetadata("apache", "Apache", new Uri("https://github.com/"))
            };
            var apiClient = Substitute.For<IApiClient>();
            apiClient.GetLicenses().Returns(licenses.ToObservable());
            var cache = new InMemoryBlobCache();
            var modelService = new ModelService(apiClient, cache, Substitute.For<IAvatarProvider>());

            var fetched = await modelService.GetLicenses();

            Assert.Equal(3, fetched.Count);
            Assert.Equal("None", fetched[0].Name);
            Assert.Equal("MIT", fetched[1].Name);
            Assert.Equal("Apache", fetched[2].Name);
            var cached = await cache.GetObject<IReadOnlyList<ModelService.LicenseCacheItem>>("licenses");
            Assert.Equal(2, cached.Count);
            Assert.Equal("mit", cached[0].Key);
            Assert.Equal("apache", cached[1].Key);
        }
Beispiel #26
0
        public async Task ExpiredIndexClearsItems()
        {
            var expected = 5;

            var username = "******";
            var reponame = "repo";

            var cache        = new InMemoryBlobCache();
            var apiClient    = Substitute.For <IApiClient>();
            var modelService = new ModelService(apiClient, cache, Substitute.For <IAvatarProvider>());
            var user         = CreateOctokitUser(username);

            apiClient.GetUser().Returns(Observable.Return(new UserAndScopes(user, null)));
            apiClient.GetOrganizations().Returns(Observable.Empty <Organization>());
            var act = modelService.GetAccounts().ToEnumerable().First().First();

            var repo = Substitute.For <ISimpleRepositoryModel>();

            repo.Name.Returns(reponame);
            repo.CloneUrl.Returns(new UriString("https://github.com/" + username + "/" + reponame));

            var indexKey = string.Format(CultureInfo.InvariantCulture, "{0}|{1}|pr", user.Login, repo.Name);

            var prcache = Enumerable.Range(1, expected)
                          .Select(id => CreatePullRequest(user, id, ItemState.Open, "Cache " + id, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow, 0));

            // seed the cache
            prcache
            .Select(item => new ModelService.PullRequestCacheItem(item))
            .Select(item => item.Save <ModelService.PullRequestCacheItem>(cache, indexKey).ToEnumerable().First())
            .SelectMany(item => CacheIndex.AddAndSaveToIndex(cache, indexKey, item).ToEnumerable())
            .ToList();

            // expire the index
            var indexobj = await cache.GetObject <CacheIndex>(indexKey);

            indexobj.UpdatedAt = DateTimeOffset.UtcNow - TimeSpan.FromMinutes(6);
            await cache.InsertObject(indexKey, indexobj);

            var prlive = Observable.Range(5, expected)
                         .Select(id => CreatePullRequest(user, id, ItemState.Open, "Live " + id, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow, 0))
                         .DelaySubscription(TimeSpan.FromMilliseconds(10));

            apiClient.GetPullRequestsForRepository(user.Login, repo.Name).Returns(prlive);

            await modelService.InsertUser(new AccountCacheItem(user));

            var col = modelService.GetPullRequests(repo);

            col.ProcessingDelay = TimeSpan.Zero;

            var count = 0;
            var evt   = new ManualResetEvent(false);

            col.Subscribe(t =>
            {
                // we get all the items from the cache (items 1-5), all the items from the live (items 5-9),
                // and 4 deletions (items 1-4) because the cache expired the items that were not
                // a part of the live data
                if (++count == 14)
                {
                    evt.Set();
                }
            }, () => { });


            evt.WaitOne();
            evt.Reset();

            Assert.Equal(5, col.Count);
            Assert.Collection(col,
                              t => { Assert.True(t.Title.StartsWith("Live")); Assert.Equal(5, t.Number); },
                              t => { Assert.True(t.Title.StartsWith("Live")); Assert.Equal(6, t.Number); },
                              t => { Assert.True(t.Title.StartsWith("Live")); Assert.Equal(7, t.Number); },
                              t => { Assert.True(t.Title.StartsWith("Live")); Assert.Equal(8, t.Number); },
                              t => { Assert.True(t.Title.StartsWith("Live")); Assert.Equal(9, t.Number); }
                              );
        }
        public async Task CanRetrieveAndCacheLicenses()
        {
            var data = new[]
            {
                new LicenseMetadata("mit", "MIT", new Uri("https://github.com/")),
                new LicenseMetadata("apache", "Apache", new Uri("https://github.com/"))
            };

            var apiClient = Substitute.For<IApiClient>();
            apiClient.GetLicenses().Returns(data.ToObservable());
            var cache = new InMemoryBlobCache();
            var modelService = new ModelService(apiClient, cache, Substitute.For<IAvatarProvider>());

            var fetched = await modelService.GetLicenses().ToList();

            Assert.Equal(2, fetched.Count);
            for (int i = 0; i < data.Length; i++)
                Assert.Equal(data[i].Name, fetched[i].Name);

            var indexKey = CacheIndex.LicensesPrefix;
            var cached = await cache.GetObject<CacheIndex>(indexKey);
            Assert.Equal(2, cached.Keys.Count);

            var items = await cache.GetObjects<LicenseCacheItem>(cached.Keys).Take(1);
            for (int i = 0; i < data.Length; i++)
                Assert.Equal(data[i].Name, items[indexKey + "|" + data[i].Key].Name);
        }
Beispiel #28
0
        public async Task CanRetrieveAndCacheRepositoriesForUserAndOrganizations()
        {
            var orgs = new[]
            {
                CreateOctokitOrganization("github"),
                CreateOctokitOrganization("octokit")
            };
            var ownedRepos = new[]
            {
                CreateRepository("haacked", "seegit"),
                CreateRepository("haacked", "codehaacks")
            };
            var memberRepos = new[]
            {
                CreateRepository("mojombo", "semver"),
                CreateRepository("ninject", "ninject"),
                CreateRepository("jabbr", "jabbr"),
                CreateRepository("fody", "nullguard")
            };
            var githubRepos = new[]
            {
                CreateRepository("github", "visualstudio")
            };
            var octokitRepos = new[]
            {
                CreateRepository("octokit", "octokit.net"),
                CreateRepository("octokit", "octokit.rb"),
                CreateRepository("octokit", "octokit.objc")
            };
            var apiClient = Substitute.For <IApiClient>();

            apiClient.GetOrganizations().Returns(orgs.ToObservable());
            apiClient.GetUserRepositories(RepositoryType.Owner).Returns(ownedRepos.ToObservable());
            apiClient.GetUserRepositories(RepositoryType.Member).Returns(memberRepos.ToObservable());
            apiClient.GetRepositoriesForOrganization("github").Returns(githubRepos.ToObservable());
            apiClient.GetRepositoriesForOrganization("octokit").Returns(octokitRepos.ToObservable());
            var cache        = new InMemoryBlobCache();
            var modelService = new ModelService(apiClient, cache, Substitute.For <IAvatarProvider>());
            await modelService.InsertUser(new AccountCacheItem { Login = "******" });

            var fetched = await modelService.GetRepositories().ToList();

            Assert.Equal(4, fetched.Count);
            Assert.Equal(2, fetched[0].Count);
            Assert.Equal(4, fetched[1].Count);
            Assert.Equal(1, fetched[2].Count);
            Assert.Equal(3, fetched[3].Count);
            Assert.Equal("seegit", fetched[0][0].Name);
            Assert.Equal("codehaacks", fetched[0][1].Name);
            Assert.Equal("semver", fetched[1][0].Name);
            Assert.Equal("ninject", fetched[1][1].Name);
            Assert.Equal("jabbr", fetched[1][2].Name);
            Assert.Equal("nullguard", fetched[1][3].Name);
            Assert.Equal("visualstudio", fetched[2][0].Name);
            Assert.Equal("octokit.net", fetched[3][0].Name);
            Assert.Equal("octokit.rb", fetched[3][1].Name);
            Assert.Equal("octokit.objc", fetched[3][2].Name);
            var cachedOwnerRepositories = await cache.GetObject <IReadOnlyList <ModelService.RepositoryCacheItem> >("opus|Owner:repos");

            Assert.Equal(2, cachedOwnerRepositories.Count);
            Assert.Equal("seegit", cachedOwnerRepositories[0].Name);
            Assert.Equal("haacked", cachedOwnerRepositories[0].Owner.Login);
            Assert.Equal("codehaacks", cachedOwnerRepositories[1].Name);
            Assert.Equal("haacked", cachedOwnerRepositories[1].Owner.Login);
            var cachedMemberRepositories = await cache.GetObject <IReadOnlyList <ModelService.RepositoryCacheItem> >("opus|Member:repos");

            Assert.Equal(4, cachedMemberRepositories.Count);
            Assert.Equal("semver", cachedMemberRepositories[0].Name);
            Assert.Equal("mojombo", cachedMemberRepositories[0].Owner.Login);
            Assert.Equal("ninject", cachedMemberRepositories[1].Name);
            Assert.Equal("ninject", cachedMemberRepositories[1].Owner.Login);
            Assert.Equal("jabbr", cachedMemberRepositories[2].Name);
            Assert.Equal("jabbr", cachedMemberRepositories[2].Owner.Login);
            Assert.Equal("nullguard", cachedMemberRepositories[3].Name);
            Assert.Equal("fody", cachedMemberRepositories[3].Owner.Login);
            var cachedGitHubRepositories = await cache.GetObject <IReadOnlyList <ModelService.RepositoryCacheItem> >("opus|github|repos");

            Assert.Equal(1, cachedGitHubRepositories.Count);
            Assert.Equal("seegit", cachedOwnerRepositories[0].Name);
            Assert.Equal("haacked", cachedOwnerRepositories[0].Owner.Login);
            Assert.Equal("codehaacks", cachedOwnerRepositories[1].Name);
            Assert.Equal("haacked", cachedOwnerRepositories[1].Owner.Login);
            var cachedOctokitRepositories = await cache.GetObject <IReadOnlyList <ModelService.RepositoryCacheItem> >("opus|octokit|repos");

            Assert.Equal("octokit.net", cachedOctokitRepositories[0].Name);
            Assert.Equal("octokit", cachedOctokitRepositories[0].Owner.Login);
            Assert.Equal("octokit.rb", cachedOctokitRepositories[1].Name);
            Assert.Equal("octokit", cachedOctokitRepositories[1].Owner.Login);
            Assert.Equal("octokit.objc", cachedOctokitRepositories[2].Name);
            Assert.Equal("octokit", cachedOctokitRepositories[2].Owner.Login);
        }
        public async Task ExpiredIndexReturnsLive()
        {
            var expected = 5;

            var username = "******";
            var reponame = "repo";

            var cache        = new InMemoryBlobCache();
            var apiClient    = Substitute.For <IApiClient>();
            var modelService = new ModelService(apiClient, cache, Substitute.For <IAvatarProvider>());
            var user         = CreateOctokitUser(username);

            apiClient.GetUser().Returns(Observable.Return(new UserAndScopes(user, null)));
            apiClient.GetOrganizations().Returns(Observable.Empty <Organization>());
            var act = modelService.GetAccounts().ToEnumerable().First().First();

            var repo = Substitute.For <ILocalRepositoryModel>();

            repo.Name.Returns(reponame);
            repo.CloneUrl.Returns(new UriString("https://github.com/" + username + "/" + reponame));

            var indexKey = string.Format(CultureInfo.InvariantCulture, "{0}|{1}:{2}", CacheIndex.PRPrefix, user.Login, repo.Name);

            var prcache = Enumerable.Range(1, expected)
                          .Select(id => CreatePullRequest(user, id, ItemState.Open, "Cache " + id, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow));

            // seed the cache
            prcache
            .Select(item => new ModelService.PullRequestCacheItem(item))
            .Select(item => item.Save <ModelService.PullRequestCacheItem>(cache, indexKey).ToEnumerable().First())
            .SelectMany(item => CacheIndex.AddAndSaveToIndex(cache, indexKey, item).ToEnumerable())
            .ToList();

            // expire the index
            var indexobj = await cache.GetObject <CacheIndex>(indexKey);

            indexobj.UpdatedAt = DateTimeOffset.UtcNow - TimeSpan.FromMinutes(6);
            await cache.InsertObject(indexKey, indexobj);

            var prlive = Observable.Range(1, expected)
                         .Select(id => CreatePullRequest(user, id, ItemState.Open, "Live " + id, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow))
                         .DelaySubscription(TimeSpan.FromMilliseconds(10));

            apiClient.GetPullRequestsForRepository(user.Login, repo.Name).Returns(prlive);

            await modelService.InsertUser(new AccountCacheItem(user));

            ITrackingCollection <IPullRequestModel> col = new TrackingCollection <IPullRequestModel>();

            modelService.GetPullRequests(repo, col);
            col.ProcessingDelay = TimeSpan.Zero;

            var count = 0;
            var done  = new ReplaySubject <Unit>();

            done.OnNext(Unit.Default);
            done.Subscribe();

            col.Subscribe(t =>
            {
                if (++count == expected * 2)
                {
                    done.OnCompleted();
                }
            }, () => { });

            await done;

            Assert.Collection(col, col.Select(x => new Action <IPullRequestModel>(t => Assert.True(x.Title.StartsWith("Live")))).ToArray());
        }
        public async Task ExpiredIndexReturnsLive()
        {
            var expected = 5;

            var username = "******";
            var reponame = "repo";

            var cache = new InMemoryBlobCache();
            var apiClient = Substitute.For<IApiClient>();
            var modelService = new ModelService(apiClient, cache, Substitute.For<IAvatarProvider>());
            var user = CreateOctokitUser(username);
            apiClient.GetUser().Returns(Observable.Return(new UserAndScopes(user, null)));
            apiClient.GetOrganizations().Returns(Observable.Empty<Organization>());
            var act = modelService.GetAccounts().ToEnumerable().First().First();

            var repo = Substitute.For<ILocalRepositoryModel>();
            repo.Name.Returns(reponame);
            repo.CloneUrl.Returns(new UriString("https://github.com/" + username + "/" + reponame));

            var indexKey = string.Format(CultureInfo.InvariantCulture, "{0}|{1}:{2}", CacheIndex.PRPrefix, user.Login, repo.Name);

            var prcache = Enumerable.Range(1, expected)
                .Select(id => CreatePullRequest(user, id, ItemState.Open, "Cache " + id, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow));

            // seed the cache
            prcache
                .Select(item => new ModelService.PullRequestCacheItem(item))
                .Select(item => item.Save<ModelService.PullRequestCacheItem>(cache, indexKey).ToEnumerable().First())
                .SelectMany(item => CacheIndex.AddAndSaveToIndex(cache, indexKey, item).ToEnumerable())
                .ToList();

            // expire the index
            var indexobj = await cache.GetObject<CacheIndex>(indexKey);
            indexobj.UpdatedAt = DateTimeOffset.UtcNow - TimeSpan.FromMinutes(6);
            await cache.InsertObject(indexKey, indexobj);

            var prlive = Observable.Range(1, expected)
                .Select(id => CreatePullRequest(user, id, ItemState.Open, "Live " + id, DateTimeOffset.UtcNow, DateTimeOffset.UtcNow))
                .DelaySubscription(TimeSpan.FromMilliseconds(10));

            apiClient.GetPullRequestsForRepository(user.Login, repo.Name).Returns(prlive);

            await modelService.InsertUser(new AccountCacheItem(user));

            ITrackingCollection<IPullRequestModel> col = new TrackingCollection<IPullRequestModel>();
            modelService.GetPullRequests(repo, col);
            col.ProcessingDelay = TimeSpan.Zero;

            var count = 0;
            var done = new ReplaySubject<Unit>();
            done.OnNext(Unit.Default);
            done.Subscribe();

            col.Subscribe(t =>
            {
                if (++count == expected * 2)
                {
                    done.OnCompleted();
                }
            }, () => { });

            await done;

            Assert.Collection(col, col.Select(x => new Action<IPullRequestModel>(t => Assert.True(x.Title.StartsWith("Live")))).ToArray());
        }