mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-01-18 13:26:47 +01:00
Isolate tests requiring internet access
This commit is contained in:
parent
573f1ddf76
commit
fbb4b13898
@ -162,23 +162,6 @@ namespace BTCPayServer.Tests
|
||||
#endif
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CheckNoDeadLink()
|
||||
{
|
||||
var views = Path.Combine(TestUtils.TryGetSolutionDirectoryInfo().FullName, "BTCPayServer", "Views");
|
||||
var viewFiles = Directory.EnumerateFiles(views, "*.cshtml", SearchOption.AllDirectories).ToArray();
|
||||
Assert.NotEmpty(viewFiles);
|
||||
Regex regex = new Regex("href=\"(http.*?)\"");
|
||||
var httpClient = new HttpClient();
|
||||
List<Task> checkLinks = new List<Task>();
|
||||
foreach (var file in viewFiles)
|
||||
{
|
||||
checkLinks.Add(CheckDeadLinks(regex, httpClient, file));
|
||||
}
|
||||
|
||||
await Task.WhenAll(checkLinks);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CheckExternalNoReferrerLinks()
|
||||
{
|
||||
@ -243,73 +226,6 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CheckDeadLinks(Regex regex, HttpClient httpClient, string file)
|
||||
{
|
||||
List<Task> checkLinks = new List<Task>();
|
||||
var text = await File.ReadAllTextAsync(file);
|
||||
|
||||
var urlBlacklist = new string[]
|
||||
{
|
||||
"https://www.btse.com", // not allowing to be hit from circleci
|
||||
"https://www.bitpay.com", // not allowing to be hit from circleci
|
||||
"https://support.bitpay.com",
|
||||
"https://www.pnxbet.com" //has geo blocking
|
||||
};
|
||||
|
||||
foreach (var match in regex.Matches(text).OfType<Match>())
|
||||
{
|
||||
var url = match.Groups[1].Value;
|
||||
if (urlBlacklist.Any(a => url.StartsWith(a.ToLowerInvariant())))
|
||||
continue;
|
||||
checkLinks.Add(AssertLinkNotDead(httpClient, url, file));
|
||||
}
|
||||
|
||||
await Task.WhenAll(checkLinks);
|
||||
}
|
||||
|
||||
private async Task AssertLinkNotDead(HttpClient httpClient, string url, string file)
|
||||
{
|
||||
var uri = new Uri(url);
|
||||
|
||||
try
|
||||
{
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, uri);
|
||||
request.Headers.TryAddWithoutValidation("Accept",
|
||||
"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
|
||||
request.Headers.TryAddWithoutValidation("User-Agent",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:75.0) Gecko/20100101 Firefox/75.0");
|
||||
var response = await httpClient.SendAsync(request);
|
||||
if (response.StatusCode == HttpStatusCode.ServiceUnavailable) // Temporary issue
|
||||
{
|
||||
TestLogs.LogInformation($"Unavailable: {url} ({file})");
|
||||
return;
|
||||
}
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
if (uri.Fragment.Length != 0)
|
||||
{
|
||||
var fragment = uri.Fragment.Substring(1);
|
||||
var contents = await response.Content.ReadAsStringAsync();
|
||||
Assert.Matches($"id=\"{fragment}\"", contents);
|
||||
}
|
||||
|
||||
TestLogs.LogInformation($"OK: {url} ({file})");
|
||||
}
|
||||
catch (Exception ex) when (ex is MatchesException)
|
||||
{
|
||||
var details = ex.Message;
|
||||
TestLogs.LogInformation($"FAILED: {url} ({file}) – anchor not found: {uri.Fragment}");
|
||||
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var details = ex is EqualException ? (ex as EqualException).Actual : ex.Message;
|
||||
TestLogs.LogInformation($"FAILED: {url} ({file}) {details}");
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanHandleUriValidation()
|
||||
{
|
||||
@ -535,25 +451,6 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
#endif
|
||||
|
||||
[Fact]
|
||||
public async Task CheckJsContent()
|
||||
{
|
||||
// This test verify that no malicious js is added in the minified files.
|
||||
// We should extend the tests to other js files, but we can do as we go...
|
||||
|
||||
using HttpClient client = new HttpClient();
|
||||
var actual = GetFileContent("BTCPayServer", "wwwroot", "vendor", "bootstrap", "bootstrap.bundle.min.js");
|
||||
var version = Regex.Match(actual, "Bootstrap v([0-9]+.[0-9]+.[0-9]+)").Groups[1].Value;
|
||||
var expected = await (await client.GetAsync($"https://cdn.jsdelivr.net/npm/bootstrap@{version}/dist/js/bootstrap.bundle.min.js")).Content.ReadAsStringAsync();
|
||||
Assert.Equal(expected, actual.Replace("\r\n", "\n", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
string GetFileContent(params string[] path)
|
||||
{
|
||||
var l = path.ToList();
|
||||
l.Insert(0, TestUtils.TryGetSolutionDirectoryInfo().FullName);
|
||||
return File.ReadAllText(Path.Combine(l.ToArray()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanParseLegacyLabels()
|
||||
{
|
||||
@ -919,166 +816,6 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact()]
|
||||
public void CanSolveTheDogesRatesOnKraken()
|
||||
{
|
||||
var provider = new BTCPayNetworkProvider(ChainName.Mainnet);
|
||||
var factory = CreateBTCPayRateFactory();
|
||||
var fetcher = new RateFetcher(factory);
|
||||
|
||||
Assert.True(RateRules.TryParse("X_X=kraken(X_BTC) * kraken(BTC_X)", out var rule));
|
||||
foreach (var pair in new[] { "DOGE_USD", "DOGE_CAD", "DASH_CAD", "DASH_USD", "DASH_EUR" })
|
||||
{
|
||||
var result = fetcher.FetchRate(CurrencyPair.Parse(pair), rule, default).GetAwaiter().GetResult();
|
||||
Assert.NotNull(result.BidAsk);
|
||||
Assert.Empty(result.Errors);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanGetRateCryptoCurrenciesByDefault()
|
||||
{
|
||||
var provider = new BTCPayNetworkProvider(ChainName.Mainnet);
|
||||
var factory = CreateBTCPayRateFactory();
|
||||
var fetcher = new RateFetcher(factory);
|
||||
var pairs =
|
||||
provider.GetAll()
|
||||
.Select(c => new CurrencyPair(c.CryptoCode, "USD"))
|
||||
.ToHashSet();
|
||||
|
||||
var rules = new StoreBlob().GetDefaultRateRules(provider);
|
||||
var result = fetcher.FetchRates(pairs, rules, default);
|
||||
foreach (var value in result)
|
||||
{
|
||||
var rateResult = value.Value.GetAwaiter().GetResult();
|
||||
TestLogs.LogInformation($"Testing {value.Key.ToString()}");
|
||||
if (value.Key.ToString() == "BTX_USD") // Broken shitcoin
|
||||
continue;
|
||||
Assert.True(rateResult.BidAsk != null, $"Impossible to get the rate {rateResult.EvaluatedRule}");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanQueryDirectProviders()
|
||||
{
|
||||
var factory = CreateBTCPayRateFactory();
|
||||
var directlySupported = factory.GetSupportedExchanges().Where(s => s.Source == RateSource.Direct)
|
||||
.Select(s => s.Id).ToHashSet();
|
||||
var all = string.Join("\r\n", factory.GetSupportedExchanges().Select(e => e.Id).ToArray());
|
||||
foreach (var result in factory
|
||||
.Providers
|
||||
.Where(p => p.Value is BackgroundFetcherRateProvider bf &&
|
||||
!(bf.Inner is CoinGeckoRateProvider cg && cg.UnderlyingExchange != null))
|
||||
.Select(p => (ExpectedName: p.Key, ResultAsync: p.Value.GetRatesAsync(default),
|
||||
Fetcher: (BackgroundFetcherRateProvider)p.Value))
|
||||
.ToList())
|
||||
{
|
||||
TestLogs.LogInformation($"Testing {result.ExpectedName}");
|
||||
if (result.ExpectedName == "ndax")
|
||||
{
|
||||
TestLogs.LogInformation($"Skipping (currently crashing)");
|
||||
continue;
|
||||
}
|
||||
|
||||
result.Fetcher.InvalidateCache();
|
||||
var exchangeRates = new ExchangeRates(result.ExpectedName, result.ResultAsync.Result);
|
||||
result.Fetcher.InvalidateCache();
|
||||
Assert.NotNull(exchangeRates);
|
||||
Assert.NotEmpty(exchangeRates);
|
||||
Assert.NotEmpty(exchangeRates.ByExchange[result.ExpectedName]);
|
||||
if (result.ExpectedName == "bitbank" || result.ExpectedName == "bitflyer")
|
||||
{
|
||||
Assert.Contains(exchangeRates.ByExchange[result.ExpectedName],
|
||||
e => e.CurrencyPair == new CurrencyPair("BTC", "JPY") &&
|
||||
e.BidAsk.Bid > 100m); // 1BTC will always be more than 100JPY
|
||||
}
|
||||
else if (result.ExpectedName == "polispay")
|
||||
{
|
||||
Assert.Contains(exchangeRates.ByExchange[result.ExpectedName],
|
||||
e => e.CurrencyPair == new CurrencyPair("BTC", "POLIS") &&
|
||||
e.BidAsk.Bid > 1.0m); // 1BTC will always be more than 1 POLIS
|
||||
}
|
||||
else if (result.ExpectedName == "argoneum")
|
||||
{
|
||||
Assert.Contains(exchangeRates.ByExchange[result.ExpectedName],
|
||||
e => e.CurrencyPair == new CurrencyPair("BTC", "AGM") &&
|
||||
e.BidAsk.Bid > 1.0m); // 1 BTC will always be more than 1 AGM
|
||||
}
|
||||
else if (result.ExpectedName == "ripio")
|
||||
{
|
||||
// Ripio keeps changing their pair, so anything is fine...
|
||||
Assert.NotEmpty(exchangeRates.ByExchange[result.ExpectedName]);
|
||||
}
|
||||
else if (result.ExpectedName == "cryptomarket")
|
||||
{
|
||||
Assert.Contains(exchangeRates.ByExchange[result.ExpectedName],
|
||||
e => e.CurrencyPair == new CurrencyPair("BTC", "CLP") &&
|
||||
e.BidAsk.Bid > 1.0m); // 1 BTC will always be more than 1 CLP
|
||||
}
|
||||
else
|
||||
{
|
||||
// This check if the currency pair is using right currency pair
|
||||
Assert.Contains(exchangeRates.ByExchange[result.ExpectedName],
|
||||
e => (e.CurrencyPair == new CurrencyPair("BTC", "USD") ||
|
||||
e.CurrencyPair == new CurrencyPair("BTC", "EUR") ||
|
||||
e.CurrencyPair == new CurrencyPair("BTC", "USDT") ||
|
||||
e.CurrencyPair == new CurrencyPair("BTC", "USDC") ||
|
||||
e.CurrencyPair == new CurrencyPair("BTC", "CAD"))
|
||||
&& e.BidAsk.Bid > 1.0m // 1BTC will always be more than 1USD
|
||||
);
|
||||
}
|
||||
// We are not showing a directly implemented exchange as directly implemented in the UI
|
||||
// we need to modify the AvailableRateProvider
|
||||
|
||||
// There are some exception we stopped supporting but don't want to break backward compat
|
||||
if (result.ExpectedName != "coinaverage" && result.ExpectedName != "gdax")
|
||||
Assert.Contains(result.ExpectedName, directlySupported);
|
||||
}
|
||||
|
||||
// Kraken emit one request only after first GetRates
|
||||
factory.Providers["kraken"].GetRatesAsync(default).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanExportBackgroundFetcherState()
|
||||
{
|
||||
var factory = CreateBTCPayRateFactory();
|
||||
var provider = (BackgroundFetcherRateProvider)factory.Providers["kraken"];
|
||||
await provider.GetRatesAsync(default);
|
||||
var state = provider.GetState();
|
||||
Assert.Single(state.Rates, r => r.Pair == new CurrencyPair("BTC", "EUR"));
|
||||
var provider2 = new BackgroundFetcherRateProvider(provider.Inner)
|
||||
{
|
||||
RefreshRate = provider.RefreshRate,
|
||||
ValidatyTime = provider.ValidatyTime
|
||||
};
|
||||
using (var cts = new CancellationTokenSource())
|
||||
{
|
||||
cts.Cancel();
|
||||
// Should throw
|
||||
await Assert.ThrowsAsync<OperationCanceledException>(async () =>
|
||||
await provider2.GetRatesAsync(cts.Token));
|
||||
}
|
||||
|
||||
provider2.LoadState(state);
|
||||
Assert.Equal(provider.LastRequested, provider2.LastRequested);
|
||||
using (var cts = new CancellationTokenSource())
|
||||
{
|
||||
cts.Cancel();
|
||||
// Should not throw, as things should be cached
|
||||
await provider2.GetRatesAsync(cts.Token);
|
||||
}
|
||||
|
||||
Assert.Equal(provider.NextUpdate, provider2.NextUpdate);
|
||||
Assert.NotEqual(provider.LastRequested, provider2.LastRequested);
|
||||
Assert.Equal(provider.Expiration, provider2.Expiration);
|
||||
|
||||
var str = JsonConvert.SerializeObject(state);
|
||||
var state2 = JsonConvert.DeserializeObject<BackgroundFetcherState>(str);
|
||||
var str2 = JsonConvert.SerializeObject(state2);
|
||||
Assert.Equal(str, str2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanExpandExternalConnectionString()
|
||||
{
|
||||
|
@ -1,259 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.ServerViewModels;
|
||||
using BTCPayServer.Storage.Models;
|
||||
using BTCPayServer.Storage.Services.Providers.AzureBlobStorage.Configuration;
|
||||
using BTCPayServer.Storage.Services.Providers.FileSystemStorage.Configuration;
|
||||
using BTCPayServer.Storage.ViewModels;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Newtonsoft.Json;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
public class StorageTests
|
||||
{
|
||||
public StorageTests(ITestOutputHelper helper)
|
||||
{
|
||||
Logs.Tester = new XUnitLog(helper) { Name = "Tests" };
|
||||
Logs.LogProvider = new XUnitLogProvider(helper);
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestUtils.TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanConfigureStorage()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
await tester.StartAsync();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
var controller = tester.PayTester.GetController<ServerController>(user.UserId, user.StoreId);
|
||||
|
||||
|
||||
//Once we select a provider, redirect to its view
|
||||
var localResult = Assert
|
||||
.IsType<RedirectToActionResult>(controller.Storage(new StorageSettings()
|
||||
{
|
||||
Provider = StorageProvider.FileSystem
|
||||
}));
|
||||
Assert.Equal(nameof(ServerController.StorageProvider), localResult.ActionName);
|
||||
Assert.Equal(StorageProvider.FileSystem.ToString(), localResult.RouteValues["provider"]);
|
||||
|
||||
|
||||
var AmazonS3result = Assert
|
||||
.IsType<RedirectToActionResult>(controller.Storage(new StorageSettings()
|
||||
{
|
||||
Provider = StorageProvider.AmazonS3
|
||||
}));
|
||||
Assert.Equal(nameof(ServerController.StorageProvider), AmazonS3result.ActionName);
|
||||
Assert.Equal(StorageProvider.AmazonS3.ToString(), AmazonS3result.RouteValues["provider"]);
|
||||
|
||||
var GoogleResult = Assert
|
||||
.IsType<RedirectToActionResult>(controller.Storage(new StorageSettings()
|
||||
{
|
||||
Provider = StorageProvider.GoogleCloudStorage
|
||||
}));
|
||||
Assert.Equal(nameof(ServerController.StorageProvider), GoogleResult.ActionName);
|
||||
Assert.Equal(StorageProvider.GoogleCloudStorage.ToString(), GoogleResult.RouteValues["provider"]);
|
||||
|
||||
|
||||
var AzureResult = Assert
|
||||
.IsType<RedirectToActionResult>(controller.Storage(new StorageSettings()
|
||||
{
|
||||
Provider = StorageProvider.AzureBlobStorage
|
||||
}));
|
||||
Assert.Equal(nameof(ServerController.StorageProvider), AzureResult.ActionName);
|
||||
Assert.Equal(StorageProvider.AzureBlobStorage.ToString(), AzureResult.RouteValues["provider"]);
|
||||
|
||||
//Cool, we get redirected to the config pages
|
||||
//Let's configure this stuff
|
||||
|
||||
//Let's try and cheat and go to an invalid storage provider config
|
||||
Assert.Equal(nameof(Storage), (Assert
|
||||
.IsType<RedirectToActionResult>(await controller.StorageProvider("I am not a real provider"))
|
||||
.ActionName));
|
||||
|
||||
//ok no more messing around, let's configure this shit.
|
||||
var fileSystemStorageConfiguration = Assert.IsType<FileSystemStorageConfiguration>(Assert
|
||||
.IsType<ViewResult>(await controller.StorageProvider(StorageProvider.FileSystem.ToString()))
|
||||
.Model);
|
||||
|
||||
//local file system does not need config, easy days!
|
||||
Assert.IsType<ViewResult>(
|
||||
await controller.EditFileSystemStorageProvider(fileSystemStorageConfiguration));
|
||||
|
||||
//ok cool, let's see if this got set right
|
||||
var shouldBeRedirectingToLocalStorageConfigPage =
|
||||
Assert.IsType<RedirectToActionResult>(await controller.Storage());
|
||||
Assert.Equal(nameof(StorageProvider), shouldBeRedirectingToLocalStorageConfigPage.ActionName);
|
||||
Assert.Equal(StorageProvider.FileSystem,
|
||||
shouldBeRedirectingToLocalStorageConfigPage.RouteValues["provider"]);
|
||||
|
||||
|
||||
//if we tell the settings page to force, it should allow us to select a new provider
|
||||
Assert.IsType<ChooseStorageViewModel>(Assert.IsType<ViewResult>(await controller.Storage(true)).Model);
|
||||
|
||||
//awesome, now let's see if the files result says we're all set up
|
||||
var viewFilesViewModel =
|
||||
Assert.IsType<ViewFilesViewModel>(Assert.IsType<ViewResult>(await controller.Files()).Model);
|
||||
Assert.True(viewFilesViewModel.StorageConfigured);
|
||||
Assert.Empty(viewFilesViewModel.Files);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async void CanUseLocalProviderFiles()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
await tester.StartAsync();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
var controller = tester.PayTester.GetController<ServerController>(user.UserId, user.StoreId);
|
||||
|
||||
var fileSystemStorageConfiguration = Assert.IsType<FileSystemStorageConfiguration>(Assert
|
||||
.IsType<ViewResult>(await controller.StorageProvider(StorageProvider.FileSystem.ToString()))
|
||||
.Model);
|
||||
Assert.IsType<ViewResult>(
|
||||
await controller.EditFileSystemStorageProvider(fileSystemStorageConfiguration));
|
||||
|
||||
var shouldBeRedirectingToLocalStorageConfigPage =
|
||||
Assert.IsType<RedirectToActionResult>(await controller.Storage());
|
||||
Assert.Equal(nameof(StorageProvider), shouldBeRedirectingToLocalStorageConfigPage.ActionName);
|
||||
Assert.Equal(StorageProvider.FileSystem,
|
||||
shouldBeRedirectingToLocalStorageConfigPage.RouteValues["provider"]);
|
||||
|
||||
|
||||
await CanUploadRemoveFiles(controller);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestUtils.TestTimeout)]
|
||||
[Trait("ExternalIntegration", "ExternalIntegration")]
|
||||
public async Task CanUseAzureBlobStorage()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
await tester.StartAsync();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
var controller = tester.PayTester.GetController<ServerController>(user.UserId, user.StoreId);
|
||||
var azureBlobStorageConfiguration = Assert.IsType<AzureBlobStorageConfiguration>(Assert
|
||||
.IsType<ViewResult>(await controller.StorageProvider(StorageProvider.AzureBlobStorage.ToString()))
|
||||
.Model);
|
||||
|
||||
azureBlobStorageConfiguration.ConnectionString = GetFromSecrets("AzureBlobStorageConnectionString");
|
||||
azureBlobStorageConfiguration.ContainerName = "testscontainer";
|
||||
Assert.IsType<ViewResult>(
|
||||
await controller.EditAzureBlobStorageStorageProvider(azureBlobStorageConfiguration));
|
||||
|
||||
|
||||
var shouldBeRedirectingToAzureStorageConfigPage =
|
||||
Assert.IsType<RedirectToActionResult>(await controller.Storage());
|
||||
Assert.Equal(nameof(StorageProvider), shouldBeRedirectingToAzureStorageConfigPage.ActionName);
|
||||
Assert.Equal(StorageProvider.AzureBlobStorage,
|
||||
shouldBeRedirectingToAzureStorageConfigPage.RouteValues["provider"]);
|
||||
|
||||
//seems like azure config worked, let's see if the conn string was actually saved
|
||||
|
||||
Assert.Equal(azureBlobStorageConfiguration.ConnectionString, Assert
|
||||
.IsType<AzureBlobStorageConfiguration>(Assert
|
||||
.IsType<ViewResult>(
|
||||
await controller.StorageProvider(StorageProvider.AzureBlobStorage.ToString()))
|
||||
.Model).ConnectionString);
|
||||
|
||||
|
||||
|
||||
await CanUploadRemoveFiles(controller);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private async Task CanUploadRemoveFiles(ServerController controller)
|
||||
{
|
||||
var fileContent = "content";
|
||||
List<IFormFile> fileList = new List<IFormFile>();
|
||||
fileList.Add(TestUtils.GetFormFile("uploadtestfile1.txt", fileContent));
|
||||
|
||||
var uploadFormFileResult = Assert.IsType<RedirectToActionResult>(await controller.CreateFiles(fileList));
|
||||
Assert.True(uploadFormFileResult.RouteValues.ContainsKey("fileIds"));
|
||||
string[] uploadFileList = (string[])uploadFormFileResult.RouteValues["fileIds"];
|
||||
var fileId = uploadFileList[0];
|
||||
Assert.Equal("Files", uploadFormFileResult.ActionName);
|
||||
|
||||
//check if file was uploaded and saved in db
|
||||
var viewFilesViewModel =
|
||||
Assert.IsType<ViewFilesViewModel>(Assert.IsType<ViewResult>(await controller.Files(new string[] { fileId })).Model);
|
||||
|
||||
Assert.NotEmpty(viewFilesViewModel.Files);
|
||||
Assert.True(viewFilesViewModel.DirectUrlByFiles.ContainsKey(fileId));
|
||||
Assert.NotEmpty(viewFilesViewModel.DirectUrlByFiles[fileId]);
|
||||
|
||||
|
||||
//verify file is available and the same
|
||||
var net = new System.Net.WebClient();
|
||||
var data = await net.DownloadStringTaskAsync(new Uri(viewFilesViewModel.DirectUrlByFiles[fileId]));
|
||||
Assert.Equal(fileContent, data);
|
||||
|
||||
//create a temporary link to file
|
||||
var tmpLinkGenerate = Assert.IsType<RedirectToActionResult>(await controller.CreateTemporaryFileUrl(fileId,
|
||||
new ServerController.CreateTemporaryFileUrlViewModel()
|
||||
{
|
||||
IsDownload = true,
|
||||
TimeAmount = 1,
|
||||
TimeType = ServerController.CreateTemporaryFileUrlViewModel.TmpFileTimeType.Minutes
|
||||
}));
|
||||
var statusMessageModel = controller.TempData.GetStatusMessageModel();
|
||||
Assert.NotNull(statusMessageModel);
|
||||
Assert.Equal(StatusMessageModel.StatusSeverity.Success, statusMessageModel.Severity);
|
||||
var index = statusMessageModel.Html.IndexOf("target='_blank'>");
|
||||
var url = statusMessageModel.Html.Substring(index)
|
||||
.Replace("</a>", string.Empty)
|
||||
.Replace("target='_blank'>", string.Empty);
|
||||
//verify tmpfile is available and the same
|
||||
data = await net.DownloadStringTaskAsync(new Uri(url));
|
||||
Assert.Equal(fileContent, data);
|
||||
|
||||
|
||||
//delete file
|
||||
Assert.IsType<RedirectToActionResult>(await controller.DeleteFile(fileId));
|
||||
statusMessageModel = controller.TempData.GetStatusMessageModel();
|
||||
Assert.NotNull(statusMessageModel);
|
||||
|
||||
Assert.Equal(StatusMessageModel.StatusSeverity.Success, statusMessageModel.Severity);
|
||||
|
||||
//attempt to fetch deleted file
|
||||
viewFilesViewModel =
|
||||
Assert.IsType<ViewFilesViewModel>(Assert.IsType<ViewResult>(await controller.Files(new string[] { fileId })).Model);
|
||||
Assert.Null(viewFilesViewModel.DirectUrlByFiles);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private static string GetFromSecrets(string key)
|
||||
{
|
||||
var connStr = Environment.GetEnvironmentVariable($"TESTS_{key}");
|
||||
if (!string.IsNullOrEmpty(connStr) && connStr != "none")
|
||||
return connStr;
|
||||
var builder = new ConfigurationBuilder();
|
||||
builder.AddUserSecrets("AB0AC1DD-9D26-485B-9416-56A33F268117");
|
||||
var config = builder.Build();
|
||||
var token = config[key];
|
||||
Assert.False(token == null, $"{key} is not set.\n Run \"dotnet user-secrets set {key} <value>\"");
|
||||
return token;
|
||||
}
|
||||
}
|
||||
}
|
1662
BTCPayServer.Tests/TestData/OpenAPI-Specification-schema.json
Normal file
1662
BTCPayServer.Tests/TestData/OpenAPI-Specification-schema.json
Normal file
File diff suppressed because it is too large
Load Diff
396
BTCPayServer.Tests/ThirdPartyTests.cs
Normal file
396
BTCPayServer.Tests/ThirdPartyTests.cs
Normal file
@ -0,0 +1,396 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.Rating;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Storage.Models;
|
||||
using BTCPayServer.Storage.Services.Providers.AzureBlobStorage.Configuration;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using NBitcoin;
|
||||
using NBitpayClient;
|
||||
using Newtonsoft.Json;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using Xunit.Sdk;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
[Trait("ThirdParty", "ThirdParty")]
|
||||
public class ThirdPartyTests : UnitTestBase
|
||||
{
|
||||
|
||||
public ThirdPartyTests(ITestOutputHelper helper) : base(helper)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestUtils.TestTimeout)]
|
||||
public async Task CanUseAzureBlobStorage()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
await tester.StartAsync();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
var controller = tester.PayTester.GetController<ServerController>(user.UserId, user.StoreId);
|
||||
var azureBlobStorageConfiguration = Assert.IsType<AzureBlobStorageConfiguration>(Assert
|
||||
.IsType<ViewResult>(await controller.StorageProvider(StorageProvider.AzureBlobStorage.ToString()))
|
||||
.Model);
|
||||
|
||||
azureBlobStorageConfiguration.ConnectionString = GetFromSecrets("AzureBlobStorageConnectionString");
|
||||
azureBlobStorageConfiguration.ContainerName = "testscontainer";
|
||||
Assert.IsType<ViewResult>(
|
||||
await controller.EditAzureBlobStorageStorageProvider(azureBlobStorageConfiguration));
|
||||
|
||||
|
||||
var shouldBeRedirectingToAzureStorageConfigPage =
|
||||
Assert.IsType<RedirectToActionResult>(await controller.Storage());
|
||||
Assert.Equal(nameof(StorageProvider), shouldBeRedirectingToAzureStorageConfigPage.ActionName);
|
||||
Assert.Equal(StorageProvider.AzureBlobStorage,
|
||||
shouldBeRedirectingToAzureStorageConfigPage.RouteValues["provider"]);
|
||||
|
||||
//seems like azure config worked, let's see if the conn string was actually saved
|
||||
|
||||
Assert.Equal(azureBlobStorageConfiguration.ConnectionString, Assert
|
||||
.IsType<AzureBlobStorageConfiguration>(Assert
|
||||
.IsType<ViewResult>(
|
||||
await controller.StorageProvider(StorageProvider.AzureBlobStorage.ToString()))
|
||||
.Model).ConnectionString);
|
||||
|
||||
|
||||
|
||||
await UnitTest1.CanUploadRemoveFiles(controller);
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetFromSecrets(string key)
|
||||
{
|
||||
var connStr = Environment.GetEnvironmentVariable($"TESTS_{key}");
|
||||
if (!string.IsNullOrEmpty(connStr) && connStr != "none")
|
||||
return connStr;
|
||||
var builder = new ConfigurationBuilder();
|
||||
builder.AddUserSecrets("AB0AC1DD-9D26-485B-9416-56A33F268117");
|
||||
var config = builder.Build();
|
||||
var token = config[key];
|
||||
Assert.False(token == null, $"{key} is not set.\n Run \"dotnet user-secrets set {key} <value>\"");
|
||||
return token;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanQueryDirectProviders()
|
||||
{
|
||||
var factory = FastTests.CreateBTCPayRateFactory();
|
||||
var directlySupported = factory.GetSupportedExchanges().Where(s => s.Source == RateSource.Direct)
|
||||
.Select(s => s.Id).ToHashSet();
|
||||
var all = string.Join("\r\n", factory.GetSupportedExchanges().Select(e => e.Id).ToArray());
|
||||
foreach (var result in factory
|
||||
.Providers
|
||||
.Where(p => p.Value is BackgroundFetcherRateProvider bf &&
|
||||
!(bf.Inner is CoinGeckoRateProvider cg && cg.UnderlyingExchange != null))
|
||||
.Select(p => (ExpectedName: p.Key, ResultAsync: p.Value.GetRatesAsync(default),
|
||||
Fetcher: (BackgroundFetcherRateProvider)p.Value))
|
||||
.ToList())
|
||||
{
|
||||
TestLogs.LogInformation($"Testing {result.ExpectedName}");
|
||||
if (result.ExpectedName == "ndax")
|
||||
{
|
||||
TestLogs.LogInformation($"Skipping (currently crashing)");
|
||||
continue;
|
||||
}
|
||||
|
||||
result.Fetcher.InvalidateCache();
|
||||
var exchangeRates = new ExchangeRates(result.ExpectedName, result.ResultAsync.Result);
|
||||
result.Fetcher.InvalidateCache();
|
||||
Assert.NotNull(exchangeRates);
|
||||
Assert.NotEmpty(exchangeRates);
|
||||
Assert.NotEmpty(exchangeRates.ByExchange[result.ExpectedName]);
|
||||
if (result.ExpectedName == "bitbank" || result.ExpectedName == "bitflyer")
|
||||
{
|
||||
Assert.Contains(exchangeRates.ByExchange[result.ExpectedName],
|
||||
e => e.CurrencyPair == new CurrencyPair("BTC", "JPY") &&
|
||||
e.BidAsk.Bid > 100m); // 1BTC will always be more than 100JPY
|
||||
}
|
||||
else if (result.ExpectedName == "polispay")
|
||||
{
|
||||
Assert.Contains(exchangeRates.ByExchange[result.ExpectedName],
|
||||
e => e.CurrencyPair == new CurrencyPair("BTC", "POLIS") &&
|
||||
e.BidAsk.Bid > 1.0m); // 1BTC will always be more than 1 POLIS
|
||||
}
|
||||
else if (result.ExpectedName == "argoneum")
|
||||
{
|
||||
Assert.Contains(exchangeRates.ByExchange[result.ExpectedName],
|
||||
e => e.CurrencyPair == new CurrencyPair("BTC", "AGM") &&
|
||||
e.BidAsk.Bid > 1.0m); // 1 BTC will always be more than 1 AGM
|
||||
}
|
||||
else if (result.ExpectedName == "ripio")
|
||||
{
|
||||
// Ripio keeps changing their pair, so anything is fine...
|
||||
Assert.NotEmpty(exchangeRates.ByExchange[result.ExpectedName]);
|
||||
}
|
||||
else if (result.ExpectedName == "cryptomarket")
|
||||
{
|
||||
Assert.Contains(exchangeRates.ByExchange[result.ExpectedName],
|
||||
e => e.CurrencyPair == new CurrencyPair("BTC", "CLP") &&
|
||||
e.BidAsk.Bid > 1.0m); // 1 BTC will always be more than 1 CLP
|
||||
}
|
||||
else
|
||||
{
|
||||
// This check if the currency pair is using right currency pair
|
||||
Assert.Contains(exchangeRates.ByExchange[result.ExpectedName],
|
||||
e => (e.CurrencyPair == new CurrencyPair("BTC", "USD") ||
|
||||
e.CurrencyPair == new CurrencyPair("BTC", "EUR") ||
|
||||
e.CurrencyPair == new CurrencyPair("BTC", "USDT") ||
|
||||
e.CurrencyPair == new CurrencyPair("BTC", "USDC") ||
|
||||
e.CurrencyPair == new CurrencyPair("BTC", "CAD"))
|
||||
&& e.BidAsk.Bid > 1.0m // 1BTC will always be more than 1USD
|
||||
);
|
||||
}
|
||||
// We are not showing a directly implemented exchange as directly implemented in the UI
|
||||
// we need to modify the AvailableRateProvider
|
||||
|
||||
// There are some exception we stopped supporting but don't want to break backward compat
|
||||
if (result.ExpectedName != "coinaverage" && result.ExpectedName != "gdax")
|
||||
Assert.Contains(result.ExpectedName, directlySupported);
|
||||
}
|
||||
|
||||
// Kraken emit one request only after first GetRates
|
||||
factory.Providers["kraken"].GetRatesAsync(default).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CheckNoDeadLink()
|
||||
{
|
||||
var views = Path.Combine(TestUtils.TryGetSolutionDirectoryInfo().FullName, "BTCPayServer", "Views");
|
||||
var viewFiles = Directory.EnumerateFiles(views, "*.cshtml", SearchOption.AllDirectories).ToArray();
|
||||
Assert.NotEmpty(viewFiles);
|
||||
Regex regex = new Regex("href=\"(http.*?)\"");
|
||||
var httpClient = new HttpClient();
|
||||
List<Task> checkLinks = new List<Task>();
|
||||
foreach (var file in viewFiles)
|
||||
{
|
||||
checkLinks.Add(CheckDeadLinks(regex, httpClient, file));
|
||||
}
|
||||
|
||||
await Task.WhenAll(checkLinks);
|
||||
}
|
||||
|
||||
private async Task CheckDeadLinks(Regex regex, HttpClient httpClient, string file)
|
||||
{
|
||||
List<Task> checkLinks = new List<Task>();
|
||||
var text = await File.ReadAllTextAsync(file);
|
||||
|
||||
var urlBlacklist = new string[]
|
||||
{
|
||||
"https://www.btse.com", // not allowing to be hit from circleci
|
||||
"https://www.bitpay.com", // not allowing to be hit from circleci
|
||||
"https://support.bitpay.com",
|
||||
"https://www.pnxbet.com" //has geo blocking
|
||||
};
|
||||
|
||||
foreach (var match in regex.Matches(text).OfType<Match>())
|
||||
{
|
||||
var url = match.Groups[1].Value;
|
||||
if (urlBlacklist.Any(a => url.StartsWith(a.ToLowerInvariant())))
|
||||
continue;
|
||||
checkLinks.Add(AssertLinkNotDead(httpClient, url, file));
|
||||
}
|
||||
|
||||
await Task.WhenAll(checkLinks);
|
||||
}
|
||||
|
||||
private async Task AssertLinkNotDead(HttpClient httpClient, string url, string file)
|
||||
{
|
||||
var uri = new Uri(url);
|
||||
|
||||
try
|
||||
{
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, uri);
|
||||
request.Headers.TryAddWithoutValidation("Accept",
|
||||
"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
|
||||
request.Headers.TryAddWithoutValidation("User-Agent",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:75.0) Gecko/20100101 Firefox/75.0");
|
||||
var response = await httpClient.SendAsync(request);
|
||||
if (response.StatusCode == HttpStatusCode.ServiceUnavailable) // Temporary issue
|
||||
{
|
||||
TestLogs.LogInformation($"Unavailable: {url} ({file})");
|
||||
return;
|
||||
}
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
if (uri.Fragment.Length != 0)
|
||||
{
|
||||
var fragment = uri.Fragment.Substring(1);
|
||||
var contents = await response.Content.ReadAsStringAsync();
|
||||
Assert.Matches($"id=\"{fragment}\"", contents);
|
||||
}
|
||||
|
||||
TestLogs.LogInformation($"OK: {url} ({file})");
|
||||
}
|
||||
catch (Exception ex) when (ex is MatchesException)
|
||||
{
|
||||
var details = ex.Message;
|
||||
TestLogs.LogInformation($"FAILED: {url} ({file}) – anchor not found: {uri.Fragment}");
|
||||
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var details = ex is EqualException ? (ex as EqualException).Actual : ex.Message;
|
||||
TestLogs.LogInformation($"FAILED: {url} ({file}) {details}");
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact()]
|
||||
public void CanSolveTheDogesRatesOnKraken()
|
||||
{
|
||||
var provider = new BTCPayNetworkProvider(ChainName.Mainnet);
|
||||
var factory = FastTests.CreateBTCPayRateFactory();
|
||||
var fetcher = new RateFetcher(factory);
|
||||
|
||||
Assert.True(RateRules.TryParse("X_X=kraken(X_BTC) * kraken(BTC_X)", out var rule));
|
||||
foreach (var pair in new[] { "DOGE_USD", "DOGE_CAD", "DASH_CAD", "DASH_USD", "DASH_EUR" })
|
||||
{
|
||||
var result = fetcher.FetchRate(CurrencyPair.Parse(pair), rule, default).GetAwaiter().GetResult();
|
||||
Assert.NotNull(result.BidAsk);
|
||||
Assert.Empty(result.Errors);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanGetRateCryptoCurrenciesByDefault()
|
||||
{
|
||||
var provider = new BTCPayNetworkProvider(ChainName.Mainnet);
|
||||
var factory = FastTests.CreateBTCPayRateFactory();
|
||||
var fetcher = new RateFetcher(factory);
|
||||
var pairs =
|
||||
provider.GetAll()
|
||||
.Select(c => new CurrencyPair(c.CryptoCode, "USD"))
|
||||
.ToHashSet();
|
||||
|
||||
var rules = new StoreBlob().GetDefaultRateRules(provider);
|
||||
var result = fetcher.FetchRates(pairs, rules, default);
|
||||
foreach (var value in result)
|
||||
{
|
||||
var rateResult = value.Value.GetAwaiter().GetResult();
|
||||
TestLogs.LogInformation($"Testing {value.Key.ToString()}");
|
||||
if (value.Key.ToString() == "BTX_USD") // Broken shitcoin
|
||||
continue;
|
||||
Assert.True(rateResult.BidAsk != null, $"Impossible to get the rate {rateResult.EvaluatedRule}");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CheckJsContent()
|
||||
{
|
||||
// This test verify that no malicious js is added in the minified files.
|
||||
// We should extend the tests to other js files, but we can do as we go...
|
||||
|
||||
using HttpClient client = new HttpClient();
|
||||
var actual = GetFileContent("BTCPayServer", "wwwroot", "vendor", "bootstrap", "bootstrap.bundle.min.js");
|
||||
var version = Regex.Match(actual, "Bootstrap v([0-9]+.[0-9]+.[0-9]+)").Groups[1].Value;
|
||||
var expected = await (await client.GetAsync($"https://cdn.jsdelivr.net/npm/bootstrap@{version}/dist/js/bootstrap.bundle.min.js")).Content.ReadAsStringAsync();
|
||||
Assert.Equal(expected, actual.Replace("\r\n", "\n", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
string GetFileContent(params string[] path)
|
||||
{
|
||||
var l = path.ToList();
|
||||
l.Insert(0, TestUtils.TryGetSolutionDirectoryInfo().FullName);
|
||||
return File.ReadAllText(Path.Combine(l.ToArray()));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanExportBackgroundFetcherState()
|
||||
{
|
||||
var factory = FastTests.CreateBTCPayRateFactory();
|
||||
var provider = (BackgroundFetcherRateProvider)factory.Providers["kraken"];
|
||||
await provider.GetRatesAsync(default);
|
||||
var state = provider.GetState();
|
||||
Assert.Single(state.Rates, r => r.Pair == new CurrencyPair("BTC", "EUR"));
|
||||
var provider2 = new BackgroundFetcherRateProvider(provider.Inner)
|
||||
{
|
||||
RefreshRate = provider.RefreshRate,
|
||||
ValidatyTime = provider.ValidatyTime
|
||||
};
|
||||
using (var cts = new CancellationTokenSource())
|
||||
{
|
||||
cts.Cancel();
|
||||
// Should throw
|
||||
await Assert.ThrowsAsync<OperationCanceledException>(async () =>
|
||||
await provider2.GetRatesAsync(cts.Token));
|
||||
}
|
||||
|
||||
provider2.LoadState(state);
|
||||
Assert.Equal(provider.LastRequested, provider2.LastRequested);
|
||||
using (var cts = new CancellationTokenSource())
|
||||
{
|
||||
cts.Cancel();
|
||||
// Should not throw, as things should be cached
|
||||
await provider2.GetRatesAsync(cts.Token);
|
||||
}
|
||||
|
||||
Assert.Equal(provider.NextUpdate, provider2.NextUpdate);
|
||||
Assert.NotEqual(provider.LastRequested, provider2.LastRequested);
|
||||
Assert.Equal(provider.Expiration, provider2.Expiration);
|
||||
|
||||
var str = JsonConvert.SerializeObject(state);
|
||||
var state2 = JsonConvert.DeserializeObject<BackgroundFetcherState>(str);
|
||||
var str2 = JsonConvert.SerializeObject(state2);
|
||||
Assert.Equal(str, str2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CanUseExchangeSpecificRate()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
tester.PayTester.MockRates = false;
|
||||
await tester.StartAsync();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
List<decimal> rates = new List<decimal>();
|
||||
rates.Add(await CreateInvoice(tester, user, "coingecko"));
|
||||
var bitflyer = await CreateInvoice(tester, user, "bitflyer", "JPY");
|
||||
var bitflyer2 = await CreateInvoice(tester, user, "bitflyer", "JPY");
|
||||
Assert.Equal(bitflyer, bitflyer2); // Should be equal because cache
|
||||
rates.Add(bitflyer);
|
||||
|
||||
foreach (var rate in rates)
|
||||
{
|
||||
Assert.Single(rates.Where(r => r == rate));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<decimal> CreateInvoice(ServerTester tester, TestAccount user, string exchange,
|
||||
string currency = "USD")
|
||||
{
|
||||
var storeController = user.GetController<StoresController>();
|
||||
var vm = (RatesViewModel)((ViewResult)storeController.Rates()).Model;
|
||||
vm.PreferredExchange = exchange;
|
||||
await storeController.Rates(vm);
|
||||
var invoice2 = await user.BitPay.CreateInvoiceAsync(
|
||||
new Invoice()
|
||||
{
|
||||
Price = 5000.0m,
|
||||
Currency = currency,
|
||||
PosData = "posData",
|
||||
OrderId = "orderId",
|
||||
ItemDesc = "Some description",
|
||||
FullNotifications = true
|
||||
}, Facade.Merchant);
|
||||
return invoice2.CryptoInfo[0].Rate;
|
||||
}
|
||||
}
|
||||
}
|
@ -45,10 +45,14 @@ using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Labels;
|
||||
using BTCPayServer.Services.Mails;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Storage.Models;
|
||||
using BTCPayServer.Storage.Services.Providers.FileSystemStorage.Configuration;
|
||||
using BTCPayServer.Storage.ViewModels;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
using BTCPayServer.Validation;
|
||||
using ExchangeSharp;
|
||||
using Fido2NetLib;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@ -138,12 +142,8 @@ namespace BTCPayServer.Tests
|
||||
var sresp = Assert
|
||||
.IsType<JsonResult>(await tester.PayTester.GetController<HomeController>(acc.UserId, acc.StoreId)
|
||||
.Swagger()).Value.ToJson();
|
||||
|
||||
JObject swagger = JObject.Parse(sresp);
|
||||
using HttpClient client = new HttpClient();
|
||||
var resp = await client.GetAsync(
|
||||
"https://raw.githubusercontent.com/OAI/OpenAPI-Specification/master/schemas/v3.0/schema.json");
|
||||
var schema = JSchema.Parse(await resp.Content.ReadAsStringAsync());
|
||||
var schema = JSchema.Parse(File.ReadAllText(TestUtils.GetTestDataFullPath("OpenAPI-Specification-schema.json")));
|
||||
IList<ValidationError> errors;
|
||||
bool valid = swagger.IsValid(schema, out errors);
|
||||
//the schema is not fully compliant to the spec. We ARE allowed to have multiple security schemas.
|
||||
@ -1265,51 +1265,6 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanUseExchangeSpecificRate()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
tester.PayTester.MockRates = false;
|
||||
await tester.StartAsync();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
List<decimal> rates = new List<decimal>();
|
||||
rates.Add(await CreateInvoice(tester, user, "coingecko"));
|
||||
var bitflyer = await CreateInvoice(tester, user, "bitflyer", "JPY");
|
||||
var bitflyer2 = await CreateInvoice(tester, user, "bitflyer", "JPY");
|
||||
Assert.Equal(bitflyer, bitflyer2); // Should be equal because cache
|
||||
rates.Add(bitflyer);
|
||||
|
||||
foreach (var rate in rates)
|
||||
{
|
||||
Assert.Single(rates.Where(r => r == rate));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<decimal> CreateInvoice(ServerTester tester, TestAccount user, string exchange,
|
||||
string currency = "USD")
|
||||
{
|
||||
var storeController = user.GetController<StoresController>();
|
||||
var vm = (RatesViewModel)((ViewResult)storeController.Rates()).Model;
|
||||
vm.PreferredExchange = exchange;
|
||||
await storeController.Rates(vm);
|
||||
var invoice2 = await user.BitPay.CreateInvoiceAsync(
|
||||
new Invoice()
|
||||
{
|
||||
Price = 5000.0m,
|
||||
Currency = currency,
|
||||
PosData = "posData",
|
||||
OrderId = "orderId",
|
||||
ItemDesc = "Some description",
|
||||
FullNotifications = true
|
||||
}, Facade.Merchant);
|
||||
return invoice2.CryptoInfo[0].Rate;
|
||||
}
|
||||
|
||||
[Fact(Timeout = LongRunningTestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanUseAnyoneCanCreateInvoice()
|
||||
@ -2794,7 +2749,175 @@ namespace BTCPayServer.Tests
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
[Fact(Timeout = TestUtils.TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanConfigureStorage()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
await tester.StartAsync();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
var controller = tester.PayTester.GetController<ServerController>(user.UserId, user.StoreId);
|
||||
|
||||
|
||||
//Once we select a provider, redirect to its view
|
||||
var localResult = Assert
|
||||
.IsType<RedirectToActionResult>(controller.Storage(new StorageSettings()
|
||||
{
|
||||
Provider = StorageProvider.FileSystem
|
||||
}));
|
||||
Assert.Equal(nameof(ServerController.StorageProvider), localResult.ActionName);
|
||||
Assert.Equal(StorageProvider.FileSystem.ToString(), localResult.RouteValues["provider"]);
|
||||
|
||||
|
||||
var AmazonS3result = Assert
|
||||
.IsType<RedirectToActionResult>(controller.Storage(new StorageSettings()
|
||||
{
|
||||
Provider = StorageProvider.AmazonS3
|
||||
}));
|
||||
Assert.Equal(nameof(ServerController.StorageProvider), AmazonS3result.ActionName);
|
||||
Assert.Equal(StorageProvider.AmazonS3.ToString(), AmazonS3result.RouteValues["provider"]);
|
||||
|
||||
var GoogleResult = Assert
|
||||
.IsType<RedirectToActionResult>(controller.Storage(new StorageSettings()
|
||||
{
|
||||
Provider = StorageProvider.GoogleCloudStorage
|
||||
}));
|
||||
Assert.Equal(nameof(ServerController.StorageProvider), GoogleResult.ActionName);
|
||||
Assert.Equal(StorageProvider.GoogleCloudStorage.ToString(), GoogleResult.RouteValues["provider"]);
|
||||
|
||||
|
||||
var AzureResult = Assert
|
||||
.IsType<RedirectToActionResult>(controller.Storage(new StorageSettings()
|
||||
{
|
||||
Provider = StorageProvider.AzureBlobStorage
|
||||
}));
|
||||
Assert.Equal(nameof(ServerController.StorageProvider), AzureResult.ActionName);
|
||||
Assert.Equal(StorageProvider.AzureBlobStorage.ToString(), AzureResult.RouteValues["provider"]);
|
||||
|
||||
//Cool, we get redirected to the config pages
|
||||
//Let's configure this stuff
|
||||
|
||||
//Let's try and cheat and go to an invalid storage provider config
|
||||
Assert.Equal(nameof(Storage), (Assert
|
||||
.IsType<RedirectToActionResult>(await controller.StorageProvider("I am not a real provider"))
|
||||
.ActionName));
|
||||
|
||||
//ok no more messing around, let's configure this shit.
|
||||
var fileSystemStorageConfiguration = Assert.IsType<FileSystemStorageConfiguration>(Assert
|
||||
.IsType<ViewResult>(await controller.StorageProvider(StorageProvider.FileSystem.ToString()))
|
||||
.Model);
|
||||
|
||||
//local file system does not need config, easy days!
|
||||
Assert.IsType<ViewResult>(
|
||||
await controller.EditFileSystemStorageProvider(fileSystemStorageConfiguration));
|
||||
|
||||
//ok cool, let's see if this got set right
|
||||
var shouldBeRedirectingToLocalStorageConfigPage =
|
||||
Assert.IsType<RedirectToActionResult>(await controller.Storage());
|
||||
Assert.Equal(nameof(StorageProvider), shouldBeRedirectingToLocalStorageConfigPage.ActionName);
|
||||
Assert.Equal(StorageProvider.FileSystem,
|
||||
shouldBeRedirectingToLocalStorageConfigPage.RouteValues["provider"]);
|
||||
|
||||
|
||||
//if we tell the settings page to force, it should allow us to select a new provider
|
||||
Assert.IsType<ChooseStorageViewModel>(Assert.IsType<ViewResult>(await controller.Storage(true)).Model);
|
||||
|
||||
//awesome, now let's see if the files result says we're all set up
|
||||
var viewFilesViewModel =
|
||||
Assert.IsType<ViewFilesViewModel>(Assert.IsType<ViewResult>(await controller.Files()).Model);
|
||||
Assert.True(viewFilesViewModel.StorageConfigured);
|
||||
Assert.Empty(viewFilesViewModel.Files);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async void CanUseLocalProviderFiles()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
await tester.StartAsync();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
var controller = tester.PayTester.GetController<ServerController>(user.UserId, user.StoreId);
|
||||
|
||||
var fileSystemStorageConfiguration = Assert.IsType<FileSystemStorageConfiguration>(Assert
|
||||
.IsType<ViewResult>(await controller.StorageProvider(StorageProvider.FileSystem.ToString()))
|
||||
.Model);
|
||||
Assert.IsType<ViewResult>(
|
||||
await controller.EditFileSystemStorageProvider(fileSystemStorageConfiguration));
|
||||
|
||||
var shouldBeRedirectingToLocalStorageConfigPage =
|
||||
Assert.IsType<RedirectToActionResult>(await controller.Storage());
|
||||
Assert.Equal(nameof(StorageProvider), shouldBeRedirectingToLocalStorageConfigPage.ActionName);
|
||||
Assert.Equal(StorageProvider.FileSystem,
|
||||
shouldBeRedirectingToLocalStorageConfigPage.RouteValues["provider"]);
|
||||
|
||||
|
||||
await CanUploadRemoveFiles(controller);
|
||||
}
|
||||
}
|
||||
|
||||
internal static async Task CanUploadRemoveFiles(ServerController controller)
|
||||
{
|
||||
var fileContent = "content";
|
||||
List<IFormFile> fileList = new List<IFormFile>();
|
||||
fileList.Add(TestUtils.GetFormFile("uploadtestfile1.txt", fileContent));
|
||||
|
||||
var uploadFormFileResult = Assert.IsType<RedirectToActionResult>(await controller.CreateFiles(fileList));
|
||||
Assert.True(uploadFormFileResult.RouteValues.ContainsKey("fileIds"));
|
||||
string[] uploadFileList = (string[])uploadFormFileResult.RouteValues["fileIds"];
|
||||
var fileId = uploadFileList[0];
|
||||
Assert.Equal("Files", uploadFormFileResult.ActionName);
|
||||
|
||||
//check if file was uploaded and saved in db
|
||||
var viewFilesViewModel =
|
||||
Assert.IsType<ViewFilesViewModel>(Assert.IsType<ViewResult>(await controller.Files(new string[] { fileId })).Model);
|
||||
|
||||
Assert.NotEmpty(viewFilesViewModel.Files);
|
||||
Assert.True(viewFilesViewModel.DirectUrlByFiles.ContainsKey(fileId));
|
||||
Assert.NotEmpty(viewFilesViewModel.DirectUrlByFiles[fileId]);
|
||||
|
||||
|
||||
//verify file is available and the same
|
||||
var net = new System.Net.WebClient();
|
||||
var data = await net.DownloadStringTaskAsync(new Uri(viewFilesViewModel.DirectUrlByFiles[fileId]));
|
||||
Assert.Equal(fileContent, data);
|
||||
|
||||
//create a temporary link to file
|
||||
var tmpLinkGenerate = Assert.IsType<RedirectToActionResult>(await controller.CreateTemporaryFileUrl(fileId,
|
||||
new ServerController.CreateTemporaryFileUrlViewModel()
|
||||
{
|
||||
IsDownload = true,
|
||||
TimeAmount = 1,
|
||||
TimeType = ServerController.CreateTemporaryFileUrlViewModel.TmpFileTimeType.Minutes
|
||||
}));
|
||||
var statusMessageModel = controller.TempData.GetStatusMessageModel();
|
||||
Assert.NotNull(statusMessageModel);
|
||||
Assert.Equal(StatusMessageModel.StatusSeverity.Success, statusMessageModel.Severity);
|
||||
var index = statusMessageModel.Html.IndexOf("target='_blank'>");
|
||||
var url = statusMessageModel.Html.Substring(index)
|
||||
.Replace("</a>", string.Empty)
|
||||
.Replace("target='_blank'>", string.Empty);
|
||||
//verify tmpfile is available and the same
|
||||
data = await net.DownloadStringTaskAsync(new Uri(url));
|
||||
Assert.Equal(fileContent, data);
|
||||
|
||||
|
||||
//delete file
|
||||
Assert.IsType<RedirectToActionResult>(await controller.DeleteFile(fileId));
|
||||
statusMessageModel = controller.TempData.GetStatusMessageModel();
|
||||
Assert.NotNull(statusMessageModel);
|
||||
|
||||
Assert.Equal(StatusMessageModel.StatusSeverity.Success, statusMessageModel.Severity);
|
||||
|
||||
//attempt to fetch deleted file
|
||||
viewFilesViewModel =
|
||||
Assert.IsType<ViewFilesViewModel>(Assert.IsType<ViewResult>(await controller.Files(new string[] { fileId })).Model);
|
||||
Assert.Null(viewFilesViewModel.DirectUrlByFiles);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,8 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
TestLogs = new XUnitLog(helper) { Name = "Tests" };
|
||||
TestLogProvider = new XUnitLogProvider(helper);
|
||||
Logs.Tester = TestLogs;
|
||||
Logs.LogProvider = TestLogProvider;
|
||||
}
|
||||
public ILog TestLogs
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user