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); }
public async Task UsesUsernameAndPasswordInsteadOfAuthorizationTokenWhenEnterpriseAndAPIReturns404() { var enterpriseHostAddress = HostAddress.Create("https://enterprise.example.com/"); var apiClient = Substitute.For<IApiClient>(); apiClient.HostAddress.Returns(enterpriseHostAddress); // Throw a 404 on the first try with the new scopes apiClient.GetOrCreateApplicationAuthenticationCode(Args.TwoFactorChallengCallback, null, false, true) .Returns(Observable.Throw<ApplicationAuthorization>(new NotFoundException("Not there", HttpStatusCode.NotFound))); // Throw a 404 on the retry with the old scopes: apiClient.GetOrCreateApplicationAuthenticationCode(Args.TwoFactorChallengCallback, null, true, false) .Returns(Observable.Throw<ApplicationAuthorization>(new NotFoundException("Also not there", HttpStatusCode.NotFound))); apiClient.GetUser().Returns(Observable.Return(CreateOctokitUser("Cthulu"))); 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("Cthulu", "aPassword"); Assert.Equal(AuthenticationResult.Success, result); // Only username and password were saved, never an authorization token: var loginInfo = await loginCache.GetLoginAsync(enterpriseHostAddress); Assert.Equal("Cthulu", loginInfo.UserName); Assert.Equal("aPassword", loginInfo.Password); Assert.True(host.IsLoggedIn); }
public IRepositoryHost Create(HostAddress hostAddress) { var apiClient = apiClientFactory.Create(hostAddress); var hostCache = hostCacheFactory.Create(hostAddress); var modelService = new ModelService(apiClient, hostCache, avatarProvider); return new RepositoryHost(apiClient, modelService, loginCache, twoFactorChallengeHandler); }
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 ReturnsCollectionOnlyContainingTheNoneOptionnWhenGitIgnoreEndpointNotFound() { var apiClient = Substitute.For<IApiClient>(); apiClient.GetGitIgnoreTemplates() .Returns(Observable.Throw<string>(new NotFoundException("Not Found", HttpStatusCode.NotFound))); var cache = new InMemoryBlobCache(); var modelService = new ModelService(apiClient, cache, Substitute.For<IAvatarProvider>()); var fetched = await modelService.GetGitIgnoreTemplates(); Assert.Equal(1, fetched.Count); Assert.Equal("None", fetched[0].Name); }
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 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); }
public async Task VaccumsTheCache() { var apiClient = Substitute.For<IApiClient>(); var cache = Substitute.For<IBlobCache>(); cache.InvalidateAll().Returns(Observable.Return(Unit.Default)); var received = false; cache.Vacuum().Returns(x => { received = true; return Observable.Return(Unit.Default); }); var modelService = new ModelService(apiClient, cache, Substitute.For<IAvatarProvider>()); await modelService.InvalidateAll(); Assert.True(received); }
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 WhenLoggedInDoesNotBlowUpOnUnexpectedNetworkProblems() { var apiClient = Substitute.For<IApiClient>(); var modelService = new ModelService(apiClient, new InMemoryBlobCache(), Substitute.For<IAvatarProvider>()); apiClient.GetOrganizations() .Returns(Observable.Throw<Organization>(new NotFoundException("Not Found", HttpStatusCode.NotFound))); var repos = await modelService.GetRepositories(); Assert.Equal(0, repos.Count); }
public async Task InvalidatesTheCache() { 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"))); Assert.Equal(1, (await cache.GetAllObjects<AccountCacheItem>()).Count()); await modelService.InvalidateAll(); Assert.Equal(0, (await cache.GetAllObjects<AccountCacheItem>()).Count()); }
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 WhenNotLoggedInReturnsEmptyCollection() { var apiClient = Substitute.For<IApiClient>(); var modelService = new ModelService(apiClient, new InMemoryBlobCache(), Substitute.For<IAvatarProvider>()); var repos = await modelService.GetRepositories(); Assert.Equal(0, repos.Count); }
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 SupportsGistIsFalseWhenGistScopeIsNotPresent() { var apiClient = Substitute.For<IApiClient>(); apiClient.HostAddress.Returns(HostAddress.GitHubDotComHostAddress); apiClient.GetUser().Returns(Observable.Return(CreateUserAndScopes("baymax", new[] { "foo" }))); 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); Assert.False(host.SupportsGist); }
public async Task ReturnsEmptyIfCacheReadFails() { var apiClient = Substitute.For<IApiClient>(); var cache = Substitute.For<IBlobCache>(); cache.Get(Args.String) .Returns(Observable.Throw<byte[]>(new InvalidOperationException("Unknown"))); var modelService = new ModelService(apiClient, cache, Substitute.For<IAvatarProvider>()); var fetched = await modelService.GetLicenses().ToList(); Assert.Equal(0, fetched.Count); }
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 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); }
public async Task RetriesUsingOldScopeWhenAuthenticationFailsAndIsEnterprise() { var enterpriseHostAddress = HostAddress.Create("https://enterprise.example.com/"); var apiClient = Substitute.For<IApiClient>(); apiClient.HostAddress.Returns(enterpriseHostAddress); apiClient.GetOrCreateApplicationAuthenticationCode(Args.TwoFactorChallengCallback, null, false, true) .Returns(Observable.Throw<ApplicationAuthorization>(new ApiException("Bad scopes", (HttpStatusCode)422))); apiClient.GetOrCreateApplicationAuthenticationCode(Args.TwoFactorChallengCallback, null, true, false) .Returns(Observable.Return(new ApplicationAuthorization("T0k3n"))); 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 host.LogIn("jiminy", "aPassowrd"); Assert.True(host.IsLoggedIn); var loginInfo = await loginCache.GetLoginAsync(enterpriseHostAddress); Assert.Equal("jiminy", loginInfo.UserName); Assert.Equal("T0k3n", loginInfo.Password); }
public async Task DoesNotFallBackToOldScopesWhenGitHubAndTwoFactorAuthFailsAndErasesLogin() { var apiClient = Substitute.For<IApiClient>(); apiClient.HostAddress.Returns(HostAddress.GitHubDotComHostAddress); bool received1 = false, received2 = false; apiClient.GetOrCreateApplicationAuthenticationCode(Args.TwoFactorChallengCallback, null, false, true) .Returns(_ => { received1 = true; return Observable.Throw<ApplicationAuthorization>(new TwoFactorChallengeFailedException()); }); apiClient.GetOrCreateApplicationAuthenticationCode(Args.TwoFactorChallengCallback, Args.String, true, Args.Boolean) .Returns(_ => { received2 = true; return Observable.Throw<ApplicationAuthorization>(new TwoFactorChallengeFailedException()); }); 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 host.LogIn("aUsername", "aPassowrd"); Assert.True(received1); Assert.False(received2); Assert.False(host.IsLoggedIn); var loginInfo = await loginCache.GetLoginAsync(HostAddress.GitHubDotComHostAddress); Assert.Equal("", loginInfo.UserName); Assert.Equal("", loginInfo.Password); }
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 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 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 SupportsGistIsTrueWhenScopesAreNull() { // TODO: Check assumptions here. From my conversation with @shana it seems that the first login // will be done with basic auth and from then on a token will be used. So if it's the first login, // it's from this version and so gists will be supported. However I've been unable to repro this // behavior. var apiClient = Substitute.For<IApiClient>(); apiClient.HostAddress.Returns(HostAddress.GitHubDotComHostAddress); 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); Assert.True(host.SupportsGist); }
public async Task ReturnsCollectionOnlyContainingTheNoneOptionIfCacheReadFails() { var apiClient = Substitute.For<IApiClient>(); apiClient.GetGitIgnoreTemplates() .Returns(Observable.Throw<string>(new NotFoundException("Not Found", HttpStatusCode.NotFound))); var cache = Substitute.For<IBlobCache>(); cache.Get(Args.String) .Returns(Observable.Throw<byte[]>(new InvalidOperationException("Unknown"))); var modelService = new ModelService(apiClient, cache, Substitute.For<IAvatarProvider>()); var fetched = await modelService.GetGitIgnoreTemplates(); Assert.Equal(1, fetched.Count); Assert.Equal("None", fetched[0].Name); }
public async Task ReturnsEmptyIfLicenseApiNotFound() { var apiClient = Substitute.For<IApiClient>(); apiClient.GetLicenses() .Returns(Observable.Throw<LicenseMetadata>(new NotFoundException("Not Found", HttpStatusCode.NotFound))); var cache = new InMemoryBlobCache(); var modelService = new ModelService(apiClient, cache, Substitute.For<IAvatarProvider>()); var fetched = await modelService.GetLicenses().ToList(); Assert.Equal(0, fetched.Count); }