예제 #1
0
            public async Task ThrowsArgumentNullExceptionIfKeyIsNull()
            {
                var blobCache = new InMemoryBlobCache();
                var artworkCache = new ArtworkCache(blobCache);

                await Helpers.ThrowsAsync<ArgumentNullException>(() => artworkCache.Retrieve(null, 100, 100));
            }
        public async Task RoundTripIntegrationTest()
        {
            var cache = new InMemoryBlobCache();

            var cachingHandler = new RateLimitedHttpMessageHandler(new HttpClientHandler(), Priority.UserInitiated, cacheResultFunc: async (rq, resp, key, ct) => {
                var data = await resp.Content.ReadAsByteArrayAsync();
                await cache.Insert(key, data);
            });

            var client = new HttpClient(cachingHandler);
            var origData = await client.GetStringAsync("http://httpbin.org/get");

            Assert.True(origData.Contains("origin"));
            Assert.Equal(1, (await cache.GetAllKeys()).Count());

            var offlineHandler = new OfflineHttpMessageHandler(async (rq, key, ct) => {
                return await cache.Get(key);
            });

            client = new HttpClient(offlineHandler);
            var newData = await client.GetStringAsync("http://httpbin.org/get");

            Assert.Equal(origData, newData);

            bool shouldDie = true;
            try {
                await client.GetStringAsync("http://httpbin.org/gzip");
            } catch (Exception ex) {
                shouldDie = false;
                Console.WriteLine(ex);
            }

            Assert.False(shouldDie);
        }
예제 #3
0
        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);
        }
예제 #5
0
        public async Task SetOrCreateInsertsValueIntoBlobCache()
        {
            var cache = new InMemoryBlobCache();
            var settings = new SettingsStorageProxy(cache);

            settings.SetOrCreateProxy(42, "TestNumber");

            Assert.Equal(1, await cache.GetAllKeys().Count());
        }
예제 #6
0
            public async Task FailedRequestThrowsException()
            {
                var fetcher = Substitute.For<IArtworkFetcher>();
                fetcher.RetrieveAsync(Arg.Any<string>(), Arg.Any<string>()).Returns(Observable.Throw<Uri>(new ArtworkFetchException(String.Empty, null)).ToTask());
                var blobCache = new InMemoryBlobCache();
                var fixture = new ArtworkCache(blobCache, fetcher);

                await Helpers.ThrowsAsync<ArtworkCacheException>(() => fixture.FetchOnline("A", "B"));
            }
            public async Task NullSearchTermDefaultsToEmptyString()
            {
                var songs = new[] { new SoundCloudSong { IsStreamable = true, StreamUrl = new Uri("http://blabla.com"), PermaLinkUrl = new Uri("http://blabla") } };
                var cache = new InMemoryBlobCache();
                cache.InsertObject(BlobCacheKeys.GetKeyForSoundCloudCache(string.Empty), songs);
                var finder = new SoundCloudSongFinder(cache);

                var result = await finder.GetSongsAsync();

                Assert.Equal(1, result.Count);
            }
            public async Task NullSearchTermDefaultsToEmptyString()
            {
                var songs = new[] { new YoutubeSong("http://blabla", TimeSpan.Zero) };
                var cache = new InMemoryBlobCache();
                cache.InsertObject(BlobCacheKeys.GetKeyForYoutubeCache(string.Empty), songs);
                var finder = new YoutubeSongFinder(cache);

                var result = await finder.GetSongsAsync();

                Assert.Equal(1, result.Count);
            }
예제 #9
0
            public async Task NotFoundRequestReturnsNull()
            {
                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 returned = await fixture.FetchOnline("A", "B");

                Assert.Null(returned);
            }
        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 UsesCachedSongsIfAvailable()
            {
                const string searchTerm = "Foo";
                var songs = new[] { new SoundCloudSong { IsStreamable = true, StreamUrl = new Uri("http://blabla.com"), PermaLinkUrl = new Uri("http://blabla") } };
                var cache = new InMemoryBlobCache();
                cache.InsertObject(BlobCacheKeys.GetKeyForSoundCloudCache(searchTerm), songs);
                var finder = new SoundCloudSongFinder(cache);

                var result = await finder.GetSongsAsync(searchTerm);

                Assert.Equal(1, result.Count);
            }
            public async Task UsesCachedSongsIfAvailable()
            {
                const string searchTerm = "Foo";
                var songs = new[] { new YoutubeSong("http://blabla", TimeSpan.Zero) };
                var cache = new InMemoryBlobCache();
                cache.InsertObject(BlobCacheKeys.GetKeyForYoutubeCache(searchTerm), songs);
                var finder = new YoutubeSongFinder(cache);

                var result = await finder.GetSongsAsync(searchTerm);

                Assert.Equal(1, result.Count);
            }
예제 #13
0
        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);
        }
예제 #14
0
        public async Task ReturnsCollectionOnlyContainingTheNoneOptionWhenLicenseApiNotFound()
        {
            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();

            Assert.Equal(1, fetched.Count);
            Assert.Equal("None", fetched[0].Name);
        }
예제 #15
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));
            }
예제 #16
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));
            }
예제 #17
0
        public static InMemoryBlobCache RegisterComponentsAndReturnInMemoryBlobCache()
        {
            EventListener verboseListener     = new StorageFileEventListener("MyListenerVerbose1");
            EventListener informationListener = new StorageFileEventListener("MyListenerInformation1");

            verboseListener.EnableEvents(KrossEventSource.Log, EventLevel.Error);
            Locator.CurrentMutable.RegisterConstant(KrossEventSource.Log, typeof(KrossEventSource));

            var blobCache = new InMemoryBlobCache();

            Locator.CurrentMutable.RegisterConstant(blobCache, typeof(IBlobCache), "UserAccount");
            Locator.CurrentMutable.Register(() => new KrossLogger(), typeof(ILogger));

            return(blobCache);
        }
예제 #18
0
            public async Task UsesCachedSongsIfAvailable()
            {
                const string searchTerm = "Foo";
                var          songs      = new[] { new SoundCloudSong {
                                                      IsStreamable = true, StreamUrl = new Uri("http://blabla.com"), PermaLinkUrl = new Uri("http://blabla")
                                                  } };
                var cache = new InMemoryBlobCache();

                cache.InsertObject(BlobCacheKeys.GetKeyForSoundCloudCache(searchTerm), songs);
                var finder = new SoundCloudSongFinder(cache);

                var result = await finder.GetSongsAsync(searchTerm);

                Assert.Equal(1, result.Count);
            }
        public async Task AddsImageDirectlyToCache()
        {
            var singlePixel  = Convert.FromBase64String("R0lGODlhAQABAIAAAAAAAAAAACH5BAAAAAAALAAAAAABAAEAAAICTAEAOw==");
            var cache        = new InMemoryBlobCache();
            var cacheFactory = Substitute.For <IBlobCacheFactory>();

            cacheFactory.CreateBlobCache(Args.String).Returns(cache);
            var imageCache = new ImageCache(cacheFactory, Substitute.For <IEnvironment>(), new Lazy <IImageDownloader>(() => Substitute.For <IImageDownloader>()));

            await imageCache.SeedImage(new Uri("https://fake/"), singlePixel, DateTimeOffset.MaxValue);

            var retrieved = await cache.Get("https://fake/");

            Assert.That(singlePixel, Is.EqualTo(retrieved));
        }
        public async Task RemovesImageFromCache()
        {
            var singlePixel = Convert.FromBase64String("R0lGODlhAQABAIAAAAAAAAAAACH5BAAAAAAALAAAAAABAAEAAAICTAEAOw==");
            var cache       = new InMemoryBlobCache();
            await cache.Insert("https://fake/", singlePixel);

            var cacheFactory = Substitute.For <IBlobCacheFactory>();

            cacheFactory.CreateBlobCache(Args.String).Returns(cache);
            var imageCache = new ImageCache(cacheFactory, Substitute.For <IEnvironment>(), new Lazy <IImageDownloader>(() => Substitute.For <IImageDownloader>()));

            await imageCache.Invalidate(new Uri("https://fake/"));

            Assert.ThrowsAsync <KeyNotFoundException>(async() => await cache.Get("https://fake/"));
        }
예제 #21
0
            public async Task PullsSearchesFromCache()
            {
                string artist = "A";
                string album = "B";
                string key = BlobCacheKeys.GetKeyForOnlineArtwork(artist, album);
                var fetcher = Substitute.For<IArtworkFetcher>();
                var blobCache = new InMemoryBlobCache();
                await blobCache.InsertObject(key, "TestArtworkKey");
                var fixture = new ArtworkCache(blobCache, fetcher);

                string returned = await fixture.FetchOnline(artist, album);

                Assert.Equal("TestArtworkKey", returned);
                fetcher.DidNotReceiveWithAnyArgs().RetrieveAsync(null, null);
            }
예제 #22
0
            public async Task PullsSearchesFromCache()
            {
                string artist    = "A";
                string album     = "B";
                string key       = BlobCacheKeys.GetKeyForOnlineArtwork(artist, album);
                var    fetcher   = Substitute.For <IArtworkFetcher>();
                var    blobCache = new InMemoryBlobCache();
                await blobCache.InsertObject(key, "TestArtworkKey");

                var fixture = new ArtworkCache(blobCache, fetcher);

                string returned = await fixture.FetchOnline(artist, album);

                Assert.Equal("TestArtworkKey", returned);
                fetcher.DidNotReceiveWithAnyArgs().RetrieveAsync(null, null);
            }
        public void ThrowsKeyNotFoundExceptionWhenItemNotInCacheAndImageFetchReturnsEmpty()
        {
            var imageUri     = new Uri("https://example.com/poop.gif");
            var cache        = new InMemoryBlobCache();
            var cacheFactory = Substitute.For <IBlobCacheFactory>();

            cacheFactory.CreateBlobCache(Args.String).Returns(cache);
            var imageDownloader = Substitute.For <IImageDownloader>();

            imageDownloader.DownloadImageBytes(imageUri).Returns(Observable.Empty <byte[]>());

            var imageCache = new ImageCache(cacheFactory, Substitute.For <IEnvironment>(), new Lazy <IImageDownloader>(() => imageDownloader));

            Assert.ThrowsAsync <KeyNotFoundException>(async() => await
                                                      imageCache.GetImage(imageUri).FirstAsync());
        }
예제 #24
0
        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 host         = new RepositoryHost(apiClient, modelService, loginCache, Substitute.For <ITwoFactorChallengeHandler>());

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

            Assert.Equal(AuthenticationResult.Success, result);
            Assert.False(host.SupportsGist);
        }
예제 #25
0
            public void CanStoreMulipleArtworksIfArtworksExistsInCache()
            {
                var    blobCache = new InMemoryBlobCache();
                var    data      = new byte[] { 0, 1 };
                string key       = BlobCacheKeys.GetKeyForArtwork(data);

                blobCache.Insert(key, data);
                var fixture = new ArtworkCache(blobCache);

                Task firstTask  = fixture.Store(key, data);
                Task secondTask = fixture.Store(key, data);
                Task thrirdTask = fixture.Store(key, data);

                Assert.True(firstTask.IsCompleted);
                Assert.True(secondTask.IsCompleted);
                Assert.True(thrirdTask.IsCompleted);
            }
        public async Task GetsAvatarFromCache()
        {
            var expectedImage = AvatarProvider.CreateBitmapImage("pack://*****:*****@test.com&s=140");
            var blobCache = new InMemoryBlobCache();
            var sharedCache = Substitute.For<ISharedCache>();
            sharedCache.LocalMachine.Returns(blobCache);
            var imageCache = new TestImageCache();
            await imageCache.SeedImage(avatarUrl, expectedImage, DateTimeOffset.MaxValue);
            var avatarProvider = new AvatarProvider(sharedCache, imageCache);
            var avatarContainer = Substitute.For<IAvatarContainer>();
            avatarContainer.AvatarUrl.Returns("https://avatars.githubusercontent.com/u/[email protected]&s=140");

            var retrieved = await avatarProvider.GetAvatar(avatarContainer);

            AssertSameImage(expectedImage, retrieved);
        }
예제 #27
0
        public async Task RetrievesImageFromCacheAndDoesNotFetchIt()
        {
            var singlePixel = Convert.FromBase64String("R0lGODlhAQABAIAAAAAAAAAAACH5BAAAAAAALAAAAAABAAEAAAICTAEAOw==");
            var cache = new InMemoryBlobCache();
            await cache.Insert("https://fake/", singlePixel);
            var cacheFactory = Substitute.For<IBlobCacheFactory>();
            cacheFactory.CreateBlobCache(Args.String).Returns(cache);
            var imageDownloader = Substitute.For<IImageDownloader>();
            imageDownloader.DownloadImageBytes(Args.Uri).Returns(_ => { throw new InvalidOperationException(); });
            var imageCache = new ImageCache(cacheFactory, Substitute.For<IEnvironment>(), new Lazy<IImageDownloader>(() => imageDownloader));

            var retrieved = await imageCache.GetImage(new Uri("https://fake/")).FirstAsync();

            Assert.NotNull(retrieved);
            Assert.Equal(32, retrieved.PixelWidth);
            Assert.Equal(32, retrieved.PixelHeight);
        }
예제 #28
0
        public async Task IncrementsLoginCount()
        {
            var apiClient = Substitute.For <IApiClient>();

            apiClient.HostAddress.Returns(HostAddress.GitHubDotComHostAddress);
            var hostCache    = new InMemoryBlobCache();
            var modelService = Substitute.For <IModelService>();
            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();

            await usage.Received().IncrementLoginCount();
        }
예제 #29
0
        public async Task WhenLoadingFromCacheFailsInvalidatesCacheEntry()
        {
            var cache = new InMemoryBlobCache();
            await cache.Insert("https://fake/", new byte[] { 0, 0, 0 });
            var cacheFactory = Substitute.For<IBlobCacheFactory>();
            cacheFactory.CreateBlobCache(Args.String).Returns(cache);
            var imageDownloader = Substitute.For<IImageDownloader>();
            imageDownloader.DownloadImageBytes(Args.Uri).Returns(_ => { throw new InvalidOperationException(); });
            var imageCache = new ImageCache(cacheFactory, Substitute.For<IEnvironment>(), new Lazy<IImageDownloader>(() => imageDownloader));

            var retrieved = await imageCache
                .GetImage(new Uri("https://fake/"))
                .Catch(Observable.Return<BitmapSource>(null))
                .FirstAsync();

            Assert.Null(retrieved);
            await Assert.ThrowsAsync<KeyNotFoundException>(async () => await cache.Get("https://fake/"));
        }
예제 #30
0
 protected SharedCache(IBlobCache userAccountCache, IBlobCache localMachineCache, ISecureBlobCache secureCache)
 {
     if (secureCache == null)
     {
         try
         {
             BlobCache.Secure = new CredentialCache();
             secureCache = BlobCache.Secure;
         }
         catch (Exception e)
         {
             log.Error("Failed to set up secure cache.", e);
             secureCache = new InMemoryBlobCache();
         }
     }
     UserAccount = userAccountCache ?? GetBlobCacheWithFallback(() => BlobCache.UserAccount, "UserAccount");
     LocalMachine = localMachineCache ?? GetBlobCacheWithFallback(() => BlobCache.LocalMachine, "LocalMachine");
     Secure = secureCache;
 }
예제 #31
0
 protected SharedCache(IBlobCache userAccountCache, IBlobCache localMachineCache, ISecureBlobCache secureCache)
 {
     if (secureCache == null)
     {
         try
         {
             BlobCache.Secure = new CredentialCache();
             secureCache      = BlobCache.Secure;
         }
         catch (Exception e)
         {
             log.Error(e, "Failed to set up secure cache");
             secureCache = new InMemoryBlobCache();
         }
     }
     UserAccount  = userAccountCache ?? GetBlobCacheWithFallback(() => BlobCache.UserAccount, "UserAccount");
     LocalMachine = localMachineCache ?? GetBlobCacheWithFallback(() => BlobCache.LocalMachine, "LocalMachine");
     Secure       = secureCache;
 }
예제 #32
0
        public async Task InitializeAsyncLoadsValuesIntoCache()
        {
            var testCache = new InMemoryBlobCache();
            await testCache.InsertObject("Storage:DummyNumber", 16);
            await testCache.InsertObject("Storage:DummyText", "Random");

            var cache = Substitute.For<IBlobCache>();
            cache.Get(Arg.Any<string>()).Returns(x => testCache.Get(x.Arg<string>()));
            var settings = new DummySettingsStorage("Storage", cache);

            await settings.InitializeAsync();

            int number = settings.DummyNumber;
            string text = settings.DummyText;

            Assert.Equal(16, number);
            Assert.Equal("Random", text);
            cache.ReceivedWithAnyArgs(2).Get(Arg.Any<string>());
        }
예제 #33
0
        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 RetrievesGitHubAvatar()
        {
            var expectedImage = AvatarProvider.CreateBitmapImage("pack://*****:*****@test.com&s=140");
            var blobCache     = new InMemoryBlobCache();
            var sharedCache   = Substitute.For <ISharedCache>();

            sharedCache.LocalMachine.Returns(blobCache);
            var imageCache = new TestImageCache();
            await imageCache.SeedImage(avatarUrl, expectedImage, DateTimeOffset.MaxValue);

            var avatarProvider  = new AvatarProvider(sharedCache, imageCache);
            var avatarContainer = Substitute.For <IAvatarContainer>();

            avatarContainer.AvatarUrl.Returns("https://avatars.githubusercontent.com/u/[email protected]&s=140");

            var retrieved = await avatarProvider.GetAvatar(avatarContainer);

            AssertSameImage(expectedImage, retrieved);
        }
        public async Task RetrievesImageFromCacheAndDoesNotFetchIt()
        {
            var singlePixel = Convert.FromBase64String("R0lGODlhAQABAIAAAAAAAAAAACH5BAAAAAAALAAAAAABAAEAAAICTAEAOw==");
            var cache       = new InMemoryBlobCache();
            await cache.Insert("https://fake/", singlePixel);

            var cacheFactory = Substitute.For <IBlobCacheFactory>();

            cacheFactory.CreateBlobCache(Args.String).Returns(cache);
            var imageDownloader = Substitute.For <IImageDownloader>();

            imageDownloader.DownloadImageBytes(Args.Uri).Returns(_ => { throw new InvalidOperationException(); });
            var imageCache = new ImageCache(cacheFactory, Substitute.For <IEnvironment>(), new Lazy <IImageDownloader>(() => imageDownloader));

            var retrieved = await imageCache.GetImage(new Uri("https://fake/")).FirstAsync();

            Assert.NotNull(retrieved);
            Assert.That(32, Is.EqualTo(retrieved.PixelWidth));
            Assert.That(32, Is.EqualTo(retrieved.PixelHeight));
        }
        public async Task CanBeAccessedFromMultipleThreads()
        {
            var blobCache = new InMemoryBlobCache();
            var sharedCache = Substitute.For<ISharedCache>();
            sharedCache.LocalMachine.Returns(blobCache);
            var imageCache = new TestImageCache();
            var avatarProvider = new AvatarProvider(sharedCache, imageCache);
            var expected = avatarProvider.DefaultUserBitmapImage.ToString();
            int mainThreadId = Thread.CurrentThread.ManagedThreadId;
            int otherThreadId = mainThreadId;

            var actual = await Task.Run(() =>
            {
                otherThreadId = Thread.CurrentThread.ManagedThreadId;
                return avatarProvider.DefaultUserBitmapImage.ToString();
            });

            Assert.Equal(expected, actual);
            Assert.NotEqual(mainThreadId, otherThreadId);
        }
예제 #37
0
        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 host         = new RepositoryHost(apiClient, modelService, loginCache, Substitute.For <ITwoFactorChallengeHandler>());

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

            Assert.Equal(AuthenticationResult.Success, result);
            Assert.True(host.SupportsGist);
        }
예제 #38
0
        public async Task OnlyRetrievesOneUserEvenIfCacheOrApiReturnsMoreThanOne()
        {
            // This should be impossible, but let's pretend it does happen.
            var users = new[]
            {
                CreateUserAndScopes("peppermintpatty"),
                CreateUserAndScopes("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 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 WhenLoadingFromCacheFailsInvalidatesCacheEntry()
        {
            var cache = new InMemoryBlobCache();
            await cache.Insert("https://fake/", new byte[] { 0, 0, 0 });

            var cacheFactory = Substitute.For <IBlobCacheFactory>();

            cacheFactory.CreateBlobCache(Args.String).Returns(cache);
            var imageDownloader = Substitute.For <IImageDownloader>();

            imageDownloader.DownloadImageBytes(Args.Uri).Returns(_ => { throw new InvalidOperationException(); });
            var imageCache = new ImageCache(cacheFactory, Substitute.For <IEnvironment>(), new Lazy <IImageDownloader>(() => imageDownloader));

            var retrieved = await imageCache
                            .GetImage(new Uri("https://fake/"))
                            .Catch(Observable.Return <BitmapSource>(null))
                            .FirstAsync();

            Assert.That(retrieved, Is.Null);
            Assert.ThrowsAsync <KeyNotFoundException>(async() => await cache.Get("https://fake/"));
        }
        public async Task CanBeAccessedFromMultipleThreads()
        {
            var blobCache   = new InMemoryBlobCache();
            var sharedCache = Substitute.For <ISharedCache>();

            sharedCache.LocalMachine.Returns(blobCache);
            var imageCache     = new TestImageCache();
            var avatarProvider = new AvatarProvider(sharedCache, imageCache);
            var expected       = avatarProvider.DefaultUserBitmapImage.ToString();
            int mainThreadId   = Thread.CurrentThread.ManagedThreadId;
            int otherThreadId  = mainThreadId;

            var actual = await Task.Run(() =>
            {
                otherThreadId = Thread.CurrentThread.ManagedThreadId;
                return(avatarProvider.DefaultUserBitmapImage.ToString());
            });

            Assert.Equal(expected, actual);
            Assert.NotEqual(mainThreadId, otherThreadId);
        }
예제 #42
0
        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(CreateUserAndScopes("jiminy")));
            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);

            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);
        }
예제 #43
0
        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);
        }
예제 #45
0
        public async Task IncrementsLoginCount()
        {
            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(CreateOctokitUser("baymax")));
            var hostCache    = new InMemoryBlobCache();
            var modelService = Substitute.For <IModelService>();
            var loginManager = Substitute.For <ILoginManager>();

            loginManager.Login(HostAddress.GitHubDotComHostAddress, Arg.Any <IGitHubClient>(), "baymax", "aPassword").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.LogIn("baymax", "aPassword");

            await usage.Received().IncrementLoginCount();
        }
예제 #46
0
        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);
        }
예제 #47
0
        public async Task IncrementsLoginCount()
        {
            var apiClient = Substitute.For <IApiClient>();

            apiClient.HostAddress.Returns(HostAddress.GitHubDotComHostAddress);
            var hostCache    = new InMemoryBlobCache();
            var modelService = Substitute.For <IModelService>();
            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();

            var model = new UsageModel();

            await usage.Received().IncrementCounter(
                Arg.Is <Expression <Func <UsageModel, int> > >(x =>
                                                               ((MemberExpression)x.Body).Member.Name == nameof(model.NumberOfLogins)));
        }
예제 #48
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]);
        }
예제 #49
0
            public async Task DistinctsFetchedSongsByGuid()
            {
                var songs = Helpers.SetupSongs(2);

                songs[0].Artist = "A";
                songs[1].Artist = "B";

                var songFetcher = Substitute.For <ISongFetcher <NetworkSong> >();

                songFetcher.GetSongsAsync().Returns(Observable.Return(songs).Concat(Observable.Return(songs)));

                var cache = new InMemoryBlobCache();

                var vm = new RemoteArtistsViewModel(songFetcher, cache);

                vm.Activator.Activate();

                var loadResults = vm.LoadCommand.CreateCollection();

                await vm.LoadCommand.ExecuteAsync();

                Assert.Equal(1, loadResults.Count);
            }
예제 #50
0
        public async Task InitializeAsyncLoadsValuesIntoCache()
        {
            var testCache = new InMemoryBlobCache();
            await testCache.InsertObject("Storage:DummyNumber", 16);

            await testCache.InsertObject("Storage:DummyText", "Random");

            var cache = Substitute.For <IBlobCache>();

            cache.Get(Arg.Any <string>()).Returns(x => testCache.Get(x.Arg <string>()));
            var settings = new DummySettingsStorage("Storage", cache);

            await settings.InitializeAsync();

            int    number = settings.DummyNumber;
            string text   = settings.DummyText;

            Assert.Equal(16, number);
            Assert.Equal("Random", text);
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
            cache.ReceivedWithAnyArgs(2).Get(Arg.Any <string>());
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
        }
예제 #51
0
        public async Task RoundTripIntegrationTest()
        {
            var cache = new InMemoryBlobCache();

            var cachingHandler = new RateLimitedHttpMessageHandler(new HttpClientHandler(), Priority.UserInitiated, cacheResultFunc: async(rq, resp, key, ct) =>
            {
                var data = await resp.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
                await cache.Insert(key, data);
            });

            var client   = new HttpClient(cachingHandler);
            var origData = await client.GetStringAsync(new Uri("http://httpbin.org/get")).ConfigureAwait(false);

            Assert.True(origData.Contains("origin"));
            Assert.Equal(1, (await cache.GetAllKeys()).Count());

            var offlineHandler = new OfflineHttpMessageHandler(async(rq, key, ct) => await cache.Get(key));

            client = new HttpClient(offlineHandler);
            var newData = await client.GetStringAsync(new Uri("http://httpbin.org/get")).ConfigureAwait(false);

            Assert.Equal(origData, newData);

            bool shouldDie = true;

            try
            {
                await client.GetStringAsync(new Uri("http://httpbin.org/gzip")).ConfigureAwait(false);
            }
            catch (Exception ex)
            {
                shouldDie = false;
                Console.WriteLine(ex);
            }

            Assert.False(shouldDie);
        }
    public async Task CachingTest()
    {
        var testScheduler = new TestScheduler();
        var cache         = new InMemoryBlobCache(testScheduler);
        var cacheTimeout  = TimeSpan.FromSeconds(10);
        var someApi       = new Mock <ISomeApi>();

        someApi.Setup(s => s.GetSomeStrings())
        .Returns(Task.FromResult("helloworld")).Verifiable();

        var apiWrapper = new SomeApiWrapper(someApi.Object, cache, cacheTimeout);
        var string1    = await apiWrapper.GetSomeStrings();

        someApi.Verify(s => s.GetSomeStrings(), Times.Once());
        StringAssert.AreEqualIgnoringCase("helloworld", string1);
        testScheduler.AdvanceToMs(5000);

        var observer = testScheduler.CreateObserver <string>();

        apiWrapper.GetSomeStrings().Subscribe(observer);
        testScheduler.AdvanceByMs(cacheTimeout.TotalMilliseconds);
        someApi.Verify(s => s.GetSomeStrings(), Times.Once());
        StringAssert.AreEqualIgnoringCase("helloworld", observer.Messages[0].Value.Value);
    }
예제 #53
0
        public void CanCreateUserSettings()
        {
            Logger.Debug("CanCreateUserSettings");
            var autoResetEvent = new AutoResetEvent(false);

            var inMemoryBlobCache = new InMemoryBlobCache();

            _autoSub.Provide <IBlobCache>(inMemoryBlobCache);

            var dataCache = _autoSub.Resolve <DataCache>();

            UserSettings userSettings = null;

            dataCache.GetUserSettings()
            .Subscribe(settings =>
            {
                userSettings = settings;
                autoResetEvent.Set();
            });

            autoResetEvent.WaitOne();

            userSettings.Should().NotBeNull();
        }
예제 #54
0
        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(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 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 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 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 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 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);
        }