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 NonExpiredIndexReturnsCache() { 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(); var prlive = Observable.Range(1, 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 => { if (++count == expected) evt.Set(); }, () => { }); evt.WaitOne(); evt.Reset(); Assert.Collection(col, col.Select(x => new Action<IPullRequestModel>(t => Assert.True(x.Title.StartsWith("Cache")))).ToArray()); }
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 OnlyRetrievesOneUserEvenIfCacheOrApiReturnsMoreThanOne() { // This should be impossible, but let's pretend it does happen. var users = new[] { CreateOctokitUser("peppermintpatty"), CreateOctokitUser("peppermintpatty") }; var apiClient = Substitute.For<IApiClient>(); apiClient.GetUser().Returns(users.ToObservable()); apiClient.GetOrganizations().Returns(Observable.Empty<Organization>()); var cache = new InMemoryBlobCache(); var modelService = new ModelService(apiClient, cache, Substitute.For<IAvatarProvider>()); var fetched = await modelService.GetAccounts(); Assert.Equal(1, fetched.Count); Assert.Equal("peppermintpatty", fetched[0].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()); }