/* * Copyright (c) 2023 Proton AG * * This file is part of ProtonVPN. * * ProtonVPN is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * ProtonVPN is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with ProtonVPN. If not, see . */ using System; using System.Diagnostics.Contracts; using System.IO; using System.Net.Http; using System.Net.Sockets; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using NSubstitute; using ProtonVPN.Common.Legacy.OS.Net.Http; using ProtonVPN.Tests.Common; using ProtonVPN.Update.Files.Downloadable; using ProtonVPN.Update.Files.Validatable; namespace ProtonVPN.Update.Tests.Files.Downloadable { [TestClass] public class DownloadableFileTest { private const string APP_FILENAME = "ProtonVPN.exe"; private const string INSTALLER_FILENAME = "ProtonVPN_win_v1.5.1.exe"; private const string INSTALLER_DOWNLOAD_URL = "https://protonvpn.com/download/" + INSTALLER_FILENAME; private IHttpClient _httpClient; #region Initialization [TestInitialize] public void TestInitialize() { _httpClient = Substitute.For(); } private IDownloadableFile DownloadableFile(string uri, Task httpResponse) { _httpClient.GetAsync(uri).Returns(httpResponse); return DownloadableFile(); } private IDownloadableFile DownloadableFile(string uri, IHttpResponseMessage httpResponse) { _httpClient.GetAsync(uri).Returns(httpResponse); return DownloadableFile(); } private IDownloadableFile DownloadableFile() { return new DownloadableFile(_httpClient); } #endregion [TestMethod] public async Task Download_ShouldDownload_FromFileUri() { string filename = Path.Combine(DownloadsPath(), APP_FILENAME); IDownloadableFile downloadable = DownloadableFile(INSTALLER_DOWNLOAD_URL, HttpResponseFromFile(INSTALLER_FILENAME)); await downloadable.Download(INSTALLER_DOWNLOAD_URL, filename); File.Delete(filename); await _httpClient.Received().GetAsync(INSTALLER_DOWNLOAD_URL); } [TestMethod] public async Task Download_ShouldDownloadFile_ToFilename() { string filename = Path.Combine(DownloadsPath(), APP_FILENAME); File.Exists(filename).Should().BeFalse(); IDownloadableFile downloadable = DownloadableFile(INSTALLER_DOWNLOAD_URL, HttpResponseFromFile(INSTALLER_FILENAME)); await downloadable.Download(INSTALLER_DOWNLOAD_URL, filename); string checkSum = await new FileCheckSum(filename).Value(); File.Delete(filename); checkSum.Should().Be("c011146ae24f5a49ef86ff6199ec0bd42223b408e1dce3ffef9a2ef4b9c1806b1c823ce427d7473378b7d8c427ba6cb3701320740523ad79fc9ec8cfeb907875"); } [TestMethod] public void Download_ShouldThrow_WhenHttpResponse_IsNotSuccess() { string filename = Path.Combine(DownloadsPath(), APP_FILENAME); IHttpResponseMessage httpResponse = Substitute.For(); httpResponse.IsSuccessStatusCode.Returns(false); IDownloadableFile downloadable = DownloadableFile(INSTALLER_DOWNLOAD_URL, httpResponse); Func action = () => downloadable.Download(INSTALLER_DOWNLOAD_URL, filename); File.Delete(filename); action.Should().ThrowAsync(); } [TestMethod] public void Download_ShouldThrow_WhenHttpRequest_Throws() { Exception[] exceptions = { new HttpRequestException(), new OperationCanceledException(), new SocketException() }; foreach (Exception exception in exceptions) { Download_ShouldThrow_WhenHttpRequest_Throws(exception); Download_ShouldThrow_WhenHttpResponse_Throws(exception); } } private void Download_ShouldThrow_WhenHttpRequest_Throws(TE exception) where TE: Exception { string filename = Path.Combine(DownloadsPath(), APP_FILENAME); IDownloadableFile downloadable = DownloadableFile(INSTALLER_DOWNLOAD_URL, FailedHttpRequest(exception)); Func action = () => downloadable.Download(INSTALLER_DOWNLOAD_URL, filename); File.Delete(filename); action.Should().ThrowAsync(); } private void Download_ShouldThrow_WhenHttpResponse_Throws(TE exception) where TE : Exception { string filename = Path.Combine(DownloadsPath(), APP_FILENAME); IDownloadableFile downloadable = DownloadableFile(INSTALLER_DOWNLOAD_URL, FailedHttpResponse(exception)); Func action = () => downloadable.Download(INSTALLER_DOWNLOAD_URL, filename); File.Delete(filename); action.Should().ThrowAsync(); } [TestMethod] public void Download_ShouldThrow_WhenHttpRequest_Cancelled() { string filename = Path.Combine(DownloadsPath(), APP_FILENAME); IDownloadableFile downloadable = DownloadableFile(INSTALLER_DOWNLOAD_URL, CancelledHttpRequest()); Func action = () => downloadable.Download(INSTALLER_DOWNLOAD_URL, filename); File.Delete(filename); action.Should().ThrowAsync(); } [TestMethod] public void Download_ShouldThrow_WhenHttpResponse_Cancelled() { string filename = Path.Combine(DownloadsPath(), APP_FILENAME); IDownloadableFile downloadable = DownloadableFile(INSTALLER_DOWNLOAD_URL, CancelledHttpResponse()); Func action = () => downloadable.Download(INSTALLER_DOWNLOAD_URL, filename); File.Delete(filename); action.Should().ThrowAsync(); } #region Helpers private string DownloadsPath([CallerMemberName] string path = null) { Contract.Assume(path != null); path = TestConfig.GetFolderPath(path); Directory.CreateDirectory(path); return path; } private static Task CancelledHttpRequest() { return Task.FromCanceled(new CancellationToken(true)); } private static Task CancelledHttpResponse() { IHttpResponseMessage httpResponse = Substitute.For(); httpResponse.IsSuccessStatusCode.Returns(true); httpResponse.Content.ReadAsStreamAsync().Returns(Task.FromCanceled(new CancellationToken(true))); return Task.FromResult(httpResponse); } private static Task FailedHttpRequest(Exception e) { return Task.FromException(e); } private static Task FailedHttpResponse(Exception e) { IHttpResponseMessage httpResponse = Substitute.For(); httpResponse.IsSuccessStatusCode.Returns(true); httpResponse.Content.ReadAsStreamAsync().Returns(Task.FromException(e)); return Task.FromResult(httpResponse); } private static IHttpResponseMessage HttpResponseFromFile(string filePath) { MemoryStream stream = new(); using (FileStream inputStream = new(TestConfig.GetFolderPath(filePath), FileMode.Open)) { inputStream.CopyTo(stream); inputStream.Flush(); } stream.Position = 0; return HttpResponseFromStream(stream); } private static IHttpResponseMessage HttpResponseFromStream(Stream stream) { IHttpResponseMessage httpResponse = Substitute.For(); httpResponse.IsSuccessStatusCode.Returns(true); httpResponse.Content.ReadAsStreamAsync().Returns(stream); httpResponse.When(x => x.Dispose()).Do(x => stream.Close()); return httpResponse; } #endregion } }