/* * 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.Collections.Generic; using System.IO; using System.Linq; using System.Net.Http; using System.Net.Sockets; using System.Threading; using System.Threading.Tasks; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; using NSubstitute; using ProtonVPN.Common.Legacy.OS.DeviceIds; using ProtonVPN.Common.Legacy.OS.Net.Http; using ProtonVPN.Configurations.Contracts; using ProtonVPN.Logging.Contracts; using ProtonVPN.Tests.Common; using ProtonVPN.Update.Config; using ProtonVPN.Update.Contracts.Config; using ProtonVPN.Update.Releases; using ProtonVPN.Update.Storage; namespace ProtonVPN.Update.Tests.Storage; [TestClass] public class WebReleaseStorageTest { private IConfiguration _configuration; private IDeviceIdCache _deviceIdCache; private ILogger _logger; private IHttpClient _httpClient; private IFeedUrlProvider _feedUrlProvider; private DefaultAppUpdateConfig _config; private Uri _feedUrl = new("http://127.0.0.1/windows-releases.json"); #region Initialization [TestInitialize] public void TestInitialize() { _configuration = Substitute.For(); _deviceIdCache = Substitute.For(); _logger = Substitute.For(); _httpClient = Substitute.For(); _feedUrlProvider = Substitute.For(); _feedUrlProvider.GetFeedUrl().Returns(_feedUrl); _config = new DefaultAppUpdateConfig { FeedHttpClient = _httpClient, FileHttpClient = _httpClient, FeedUriProvider = _feedUrlProvider, UpdatesPath = "Updates", CurrentVersion = new Version(), EarlyAccessCategoryName = "EarlyAccess" }; } private IReleaseStorage WebReleaseStorage(Task httpResponse) { _httpClient.GetAsync(_config.FeedUriProvider.GetFeedUrl()).Returns(httpResponse); return WebReleaseStorage(); } private IReleaseStorage WebReleaseStorage(IHttpResponseMessage httpResponse) { _httpClient.GetAsync(_config.FeedUriProvider.GetFeedUrl()).Returns(httpResponse); return WebReleaseStorage(); } private IReleaseStorage WebReleaseStorage() { return new WebReleaseStorage(_config, _logger, _deviceIdCache, _configuration); } #endregion [TestMethod] public async Task Releases_ShouldGet_FromFeedUri() { Uri feedUri = new("http://127.0.0.1/windows-releases.json"); _feedUrlProvider.GetFeedUrl().Returns(_feedUrl); IReleaseStorage storage = WebReleaseStorage(HttpResponseFromFile("windows-releases.json")); await storage.GetReleasesAsync(); await _httpClient.Received().GetAsync(feedUri); } [TestMethod] public async Task Releases_ShouldBe_AllFromSource() { IReleaseStorage storage = WebReleaseStorage(HttpResponseFromFile("windows-releases.json")); IEnumerable result = await storage.GetReleasesAsync(); var a = result.ToList(); result.Should().HaveCount(5); } [TestMethod] public void Releases_ShouldThrow_WhenHttpResponse_IsNotSuccess() { IHttpResponseMessage httpResponse = Substitute.For(); httpResponse.IsSuccessStatusCode.Returns(false); IReleaseStorage storage = WebReleaseStorage(httpResponse); Func action = () => storage.GetReleasesAsync(); action.Should().ThrowAsync(); } [TestMethod] public void Releases_ShouldThrow_WhenHttpRequest_Throws() { Exception[] exceptions = { new HttpRequestException(), new OperationCanceledException(), new SocketException() }; foreach (Exception exception in exceptions) { Releases_ShouldThrow_WhenHttpRequest_Throws(exception); Releases_ShouldThrow_WhenHttpResponse_Throws(exception); } } private void Releases_ShouldThrow_WhenHttpRequest_Throws(TE exception) where TE : Exception { IReleaseStorage storage = WebReleaseStorage(FailedHttpRequest(exception)); Func action = () => storage.GetReleasesAsync(); action.Should().ThrowAsync(); } private void Releases_ShouldThrow_WhenHttpResponse_Throws(TE exception) where TE : Exception { IReleaseStorage storage = WebReleaseStorage(FailedHttpResponse(exception)); Func action = () => storage.GetReleasesAsync(); action.Should().ThrowAsync(); } [TestMethod] public void Releases_ShouldThrow_WhenHttpRequest_Cancelled() { IReleaseStorage storage = WebReleaseStorage(CancelledHttpRequest()); Func action = () => storage.GetReleasesAsync(); action.Should().ThrowAsync(); } [TestMethod] public void Releases_ShouldThrow_WhenHttpResponse_Cancelled() { IReleaseStorage storage = WebReleaseStorage(CancelledHttpResponse()); Func action = () => storage.GetReleasesAsync(); action.Should().ThrowAsync(); } #region Helpers 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 }