public async Task Get_by_id_returns_job_when_it_exists() { var fixture = new Fixture().Customize(new DownloaderCustomization()); var job = fixture.Create <DownloadJob>(); var jobs = new DownloadJobsDictionary { [job.Id] = job }; using var client = _factory .WithServices(services => services.ReplaceAllWithSingleton(jobs)) .CreateDefaultClient(); using var response = await client.GetAsync($"{ApiDownloadRoute}/{job.Id}"); response.IsSuccessStatusCode.Should().BeTrue(); var content = await response.Content.ReadFromJsonAsync <DownloadController.GetResponseDto>(); content.Should() .NotBeNull() .And.Be( new DownloadController.GetResponseDto( job.Id, job.Link, job.SaveAsFile.Name, job.Status.ToString(), job.CreatedTicks, job.TotalBytes, job.BytesDownloaded)); }
private HttpClient CreateClient( DownloadJob.JobId newDownloadId, DownloadJobsDictionary downloadJobsDictionary, IFileSystem fileSystem, DelegatingHandler downloadsHttpClient, IHubContext <NotificationsHub, NotificationsHub.IClient> notificationsHubContext) { return (_factory .WithSettings( ("DownloadDirectories:Incomplete", "/incomplete"), ("DownloadDirectories:Completed", "/completed"), /* One millisecond interval to give ProgressNotificationsBackgroundService a chance to iterate * through dictionary while the test is executing - this can potentially cause a flaky test. * See SignalRMessagesShouldBeSent() verification for SendProgress in the Assert block below. */ ("PushNotifications:ProgressIntervalInMilliseconds", "1")) .WithServices(services => services .ReplaceAllWithSingleton(new DownloadManager.DownloadIdGenerator(() => newDownloadId)) .ReplaceAllWithSingleton(new DownloadManager.DateTimeUtcNowTicks(() => 42)) .ReplaceAllWithSingleton(downloadJobsDictionary) .ReplaceAllWithSingleton(fileSystem) .ReplaceAllWithSingleton(notificationsHubContext) .ReplaceHttpMessageHandlerFor <DownloadStarter>(downloadsHttpClient)) .CreateDefaultClient()); }
public async Task Delete_removes_completed_and_failed_jobs() { /* Arrange */ var fixture = new Fixture().Customize(new DownloaderCustomization()); var runningJob = fixture.Create <DownloadJob>(); var downloadingHandlerStub = fixture.Create <Mock <DelegatingHandler> >(); downloadingHandlerStub .Protected() .As <IProtectedDelegatingHandler>() .Setup(h => h.SendAsync(It.IsAny <HttpRequestMessage>(), It.IsAny <CancellationToken>())) .Returns(async() => { const int forever = -1; await Task.Delay(forever); throw new InvalidOperationException("Unreachable code"); }); using var httpClient1 = new HttpClient(downloadingHandlerStub.Object); runningJob.Start(httpClient1); var completedJob = fixture.Create <DownloadJob>(); var completedHandlerStub = fixture.Create <Mock <DelegatingHandler> >(); completedHandlerStub .Protected() .As <IProtectedDelegatingHandler>() .Setup(h => h.SendAsync(It.IsAny <HttpRequestMessage>(), It.IsAny <CancellationToken>())) .ReturnsAsync(new HttpResponseMessage()); using var completedJobHttpClient = new HttpClient(completedHandlerStub.Object); completedJob.Start(completedJobHttpClient); await completedJob.DownloadTask; var failedJob = fixture.Create <DownloadJob>(); var failedHandlerStub = fixture.Create <Mock <DelegatingHandler> >(); failedHandlerStub .Protected() .As <IProtectedDelegatingHandler>() .Setup(h => h.SendAsync(It.IsAny <HttpRequestMessage>(), It.IsAny <CancellationToken>())) .ThrowsAsync(new Exception()); using var failedJobHttpClient = new HttpClient(failedHandlerStub.Object); failedJob.Start(failedJobHttpClient); await failedJob.DownloadTask.AwaitIgnoringExceptions(); var jobs = new DownloadJobsDictionary { [runningJob.Id] = runningJob, [completedJob.Id] = completedJob }; using var client = _factory .WithServices(services => services.ReplaceAllWithSingleton(jobs)) .CreateDefaultClient(); /* Act */ using var response = await client.DeleteAsync(ApiDownloadRoute); /* Assert */ response.IsSuccessStatusCode.Should().BeTrue(); jobs.Should() .NotContainKeys(completedJob.Id, failedJob.Id) .And.ContainKey(runningJob.Id); }
public DownloadManager( DownloadIdGenerator newGuid, DateTimeUtcNowTicks getTicks, DownloadJobsDictionary jobs, DownloadTaskFactory downloadTaskFactory) { _newGuid = newGuid; _getTicks = getTicks; _jobs = jobs; _downloadTaskFactory = downloadTaskFactory; }
public DownloadController( DownloadManager downloadManager, NotificationsManager notificationsManager, DownloadStarter downloadStarter, CompletedDownloadsDirectory completedDownloadsDirectory, DownloadJobsDictionary jobs) { _downloadManager = downloadManager; _notificationsManager = notificationsManager; _downloadStarter = downloadStarter; _completedDownloadsDirectory = completedDownloadsDirectory; _jobs = jobs; }
public async Task Post_sends_failure_notification_if_download_task_fails() { /* Arrange */ var fixture = new Fixture().Customize(new AutoMoqCustomization()); var newDownloadId = fixture.Create <DownloadJob.JobId>(); var downloadJobsDictionary = new DownloadJobsDictionary(); var fileSystemStub = CreateAndSetupFileSystemMock(fixture, newDownloadId); var notificationsHubContextMock = fixture.Create <Mock <IHubContext <NotificationsHub, NotificationsHub.IClient> > >(); var downloadHttpClientStub = fixture.Create <Mock <DelegatingHandler> >(); downloadHttpClientStub .Protected() .As <IProtectedDelegatingHandler>() .Setup(h => h.SendAsync( It.Is <HttpRequestMessage>(r => r.Method == HttpMethod.Get && r.RequestUri !.OriginalString == "https://download.stuff/file.iso"), It.IsAny <CancellationToken>())) .ThrowsAsync(new Exception("Something went wrong!")); using var client = CreateClient( newDownloadId, downloadJobsDictionary, fileSystemStub.Object, downloadHttpClientStub.Object, notificationsHubContextMock.Object); /* Act */ var response = await client.PostAsync( ApiDownloadRoute, JsonContent.Create( new DownloadController.PostRequestDto( "https://download.stuff/file.iso", "saveAsFile.iso"))); /* Assert */ response.IsSuccessStatusCode.Should().BeTrue(); var newDownload = downloadJobsDictionary[newDownloadId]; /* To observe the results we need to simulate completion of the downloading task */ await newDownload.DownloadTask.AwaitIgnoringExceptions(); notificationsHubContextMock.Verify(h => h.Clients.All.SendFailed( It.Is <NotificationsHub.FailedMessage>(m => m.Id == newDownloadId && m.Reason == "Something went wrong!"))); }
public async Task Get_returns_contents_of_DownloadJobsDictionary() { var fixture = new Fixture().Customize(new DownloaderCustomization()); var job1 = fixture.Create <DownloadJob>(); var job2 = fixture.Create <DownloadJob>(); var jobs = new DownloadJobsDictionary { [job1.Id] = job1, [job2.Id] = job2 }; using var client = _factory .WithServices(services => services.ReplaceAllWithSingleton(jobs)) .CreateDefaultClient(); using var response = await client.GetAsync(ApiDownloadRoute); response.IsSuccessStatusCode.Should().BeTrue(); var content = await response.Content.ReadFromJsonAsync <DownloadController.GetResponseDto[]>(); content.Should() .NotBeNullOrEmpty() .And.Contain( new DownloadController.GetResponseDto( job1.Id, job1.Link, job1.SaveAsFile.Name, job1.Status.ToString(), job1.CreatedTicks)) .And.Contain( new DownloadController.GetResponseDto( job2.Id, job2.Link, job2.SaveAsFile.Name, job2.Status.ToString(), job2.CreatedTicks)) .And.Subject.Count().Should().Be(2); }
public async Task Post_starts_download_and_saves_to_file_and_sends_notifications() { /* Arrange */ var fixture = new Fixture().Customize(new AutoMoqCustomization()); var downloadHttpClientStub = CreateAndSetupDownloadHttpClientStub(fixture); var newDownloadId = fixture.Create <DownloadJob.JobId>(); var fileSystemMock = CreateAndSetupFileSystemMock(fixture, newDownloadId); var downloadJobsDictionary = new DownloadJobsDictionary(); var notificationsHubContextMock = fixture.Create <Mock <IHubContext <NotificationsHub, NotificationsHub.IClient> > >(); using var client = CreateClient( newDownloadId, downloadJobsDictionary, fileSystemMock.Object, downloadHttpClientStub.Object, notificationsHubContextMock.Object); /* Act */ var response = await client.PostAsync( ApiDownloadRoute, JsonContent.Create( new DownloadController.PostRequestDto( "https://download.stuff/file.iso", "saveAsFile.iso"))); /* Assert */ response.IsSuccessStatusCode.Should().BeTrue(); await ResponseShouldContainNewDownloadId(); downloadJobsDictionary[newDownloadId].CreatedTicks.Should().Be(42); await DownloadContentsShouldBeSavedToTemporaryFile(); TemporaryFileShouldBeMovedToSaveAsFile(); SignalRMessagesShouldBeSent(); async Task ResponseShouldContainNewDownloadId() { var responseContent = await response.Content.ReadFromJsonAsync <Guid>(); responseContent.Should().Be(newDownloadId.Value); } async Task DownloadContentsShouldBeSavedToTemporaryFile() { var newDownload = downloadJobsDictionary[newDownloadId]; /* To observe the results we need to simulate completion of the downloading task */ await newDownload.DownloadTask; fileSystemMock.Verify(fs => fs.FileStream .Create($"/incomplete/{newDownloadId}", FileMode.CreateNew) .WriteAsync( It.Is <ReadOnlyMemory <byte> >(b => Encoding.Default.GetString(b.ToArray()).Equals("file contents")), It.IsAny <CancellationToken>())); } void TemporaryFileShouldBeMovedToSaveAsFile() { fileSystemMock.Verify(fs => fs.File.Move( $"/incomplete/{newDownloadId}", /* Test that name of the file has incremented index because saveAsFile.iso already exists */ "/completed/saveAsFile(1).iso", false)); } void SignalRMessagesShouldBeSent() { notificationsHubContextMock.Verify(h => h.Clients.All.SendTotalBytes( It.Is <NotificationsHub.TotalBytesMessage>(m => m.Id == newDownloadId && m.TotalBytes == 42))); notificationsHubContextMock.Verify(h => h.Clients.All.SendFinished( It.Is <NotificationsHub.FinishedMessage>(m => m.Id == newDownloadId && m.FileName == "saveAsFile(1).iso"))); notificationsHubContextMock.Verify(h => h.Clients.All.SendProgress( It.Is <IEnumerable <NotificationsHub.ProgressMessage> >(m => m.Any(p => p.Id == newDownloadId)), It.IsAny <CancellationToken>())); } }