2020-06-29 04:44:35 +02:00
using System ;
2020-06-28 10:55:27 +02:00
using System.Collections.Generic ;
using System.Globalization ;
using System.IO ;
2017-09-13 08:47:34 +02:00
using System.Linq ;
2020-06-28 10:55:27 +02:00
using System.Net ;
using System.Net.Http ;
using System.Runtime.CompilerServices ;
using System.Security ;
using System.Text ;
using System.Text.RegularExpressions ;
2017-09-13 08:47:34 +02:00
using System.Threading ;
2020-06-28 10:55:27 +02:00
using System.Threading.Tasks ;
using BTCPayServer.Client ;
using BTCPayServer.Client.Models ;
using BTCPayServer.Configuration ;
2017-10-11 05:20:44 +02:00
using BTCPayServer.Controllers ;
2017-10-24 18:41:01 +02:00
using BTCPayServer.Data ;
2020-06-28 10:55:27 +02:00
using BTCPayServer.Events ;
using BTCPayServer.HostedServices ;
using BTCPayServer.Lightning ;
using BTCPayServer.Models ;
using BTCPayServer.Models.AccountViewModels ;
using BTCPayServer.Models.AppViewModels ;
using BTCPayServer.Models.InvoicingModels ;
using BTCPayServer.Models.ServerViewModels ;
2018-01-17 07:59:31 +01:00
using BTCPayServer.Models.StoreViewModels ;
2020-06-28 10:55:27 +02:00
using BTCPayServer.Models.WalletViewModels ;
2018-02-18 18:38:03 +01:00
using BTCPayServer.Payments ;
2018-02-19 10:54:21 +01:00
using BTCPayServer.Payments.Bitcoin ;
2018-02-25 16:48:12 +01:00
using BTCPayServer.Payments.Lightning ;
2020-06-28 10:55:27 +02:00
using BTCPayServer.Rating ;
using BTCPayServer.Security.Bitpay ;
using BTCPayServer.Services ;
2018-04-03 09:53:55 +02:00
using BTCPayServer.Services.Apps ;
2020-06-28 10:55:27 +02:00
using BTCPayServer.Services.Invoices ;
using BTCPayServer.Services.Rates ;
using BTCPayServer.Tests.Logging ;
using BTCPayServer.U2F.Models ;
2018-05-14 09:32:04 +02:00
using BTCPayServer.Validation ;
2018-05-04 08:35:39 +02:00
using ExchangeSharp ;
2020-06-28 10:55:27 +02:00
using Microsoft.AspNetCore.Mvc ;
using Microsoft.EntityFrameworkCore ;
using NBitcoin ;
using NBitcoin.DataEncoders ;
using NBitcoin.Payment ;
using NBitpayClient ;
2019-05-09 11:38:25 +02:00
using NBXplorer.DerivationStrategy ;
2020-06-28 10:55:27 +02:00
using NBXplorer.Models ;
using Newtonsoft.Json ;
using Newtonsoft.Json.Linq ;
2020-03-18 12:08:09 +01:00
using Newtonsoft.Json.Schema ;
2020-06-28 10:55:27 +02:00
using Xunit ;
using Xunit.Abstractions ;
using Xunit.Sdk ;
using RatesViewModel = BTCPayServer . Models . StoreViewModels . RatesViewModel ;
2017-09-13 08:47:34 +02:00
namespace BTCPayServer.Tests
{
2017-10-27 10:53:04 +02:00
public class UnitTest1
{
2019-10-06 15:49:28 +02:00
public const int TestTimeout = 60_000 ;
2020-04-05 13:48:00 +02:00
2017-10-27 10:53:04 +02:00
public UnitTest1 ( ITestOutputHelper helper )
{
2020-06-24 03:34:09 +02:00
Logs . Tester = new XUnitLog ( helper ) { Name = "Tests" } ;
2017-10-27 10:53:04 +02:00
Logs . LogProvider = new XUnitLogProvider ( helper ) ;
}
2020-07-29 15:12:46 +02:00
class DockerImage
{
public string User { get ; private set ; }
public string Name { get ; private set ; }
public string Tag { get ; private set ; }
public string Source { get ; set ; }
public static DockerImage Parse ( string str )
{
//${BTCPAY_IMAGE: -btcpayserver / btcpayserver:1.0.3.21}
var variableMatch = Regex . Match ( str , @"\$\{[^-]+-([^\}]+)\}" ) ;
if ( variableMatch . Success )
{
str = variableMatch . Groups [ 1 ] . Value ;
}
DockerImage img = new DockerImage ( ) ;
var match = Regex . Match ( str , "([^/]*/)?([^:]+):?(.*)" ) ;
if ( ! match . Success )
throw new FormatException ( ) ;
img . User = match . Groups [ 1 ] . Length = = 0 ? string . Empty : match . Groups [ 1 ] . Value . Substring ( 0 , match . Groups [ 1 ] . Value . Length - 1 ) ;
img . Name = match . Groups [ 2 ] . Value ;
img . Tag = match . Groups [ 3 ] . Value ;
if ( img . Tag = = string . Empty )
img . Tag = "latest" ;
return img ;
}
public override string ToString ( )
{
return ToString ( true ) ;
}
public string ToString ( bool includeTag )
{
StringBuilder builder = new StringBuilder ( ) ;
if ( ! String . IsNullOrWhiteSpace ( User ) )
builder . Append ( $"{User}/" ) ;
builder . Append ( $"{Name}" ) ;
if ( includeTag )
{
if ( ! String . IsNullOrWhiteSpace ( Tag ) )
builder . Append ( $":{Tag}" ) ;
}
return builder . ToString ( ) ;
}
}
/// <summary>
/// This test check that we don't forget to bump one image in both docker-compose.altcoins.yml and docker-compose.yml
/// </summary>
[Fact]
[Trait("Fast", "Fast")]
public void CheckDockerComposeUpToDate ( )
{
var compose1 = File . ReadAllText ( Path . Combine ( TestUtils . TryGetSolutionDirectoryInfo ( ) . FullName , "BTCPayServer.Tests" , "docker-compose.yml" ) ) ;
var compose2 = File . ReadAllText ( Path . Combine ( TestUtils . TryGetSolutionDirectoryInfo ( ) . FullName , "BTCPayServer.Tests" , "docker-compose.altcoins.yml" ) ) ;
List < DockerImage > GetImages ( string content )
{
List < DockerImage > images = new List < DockerImage > ( ) ;
foreach ( var line in content . Split ( new [ ] { "\n" , "\r\n" } , StringSplitOptions . RemoveEmptyEntries ) )
{
var l = line . Trim ( ) ;
if ( l . StartsWith ( "image:" , StringComparison . OrdinalIgnoreCase ) )
{
images . Add ( DockerImage . Parse ( l . Substring ( "image:" . Length ) . Trim ( ) ) ) ;
}
}
return images ;
}
var img1 = GetImages ( compose1 ) ;
var img2 = GetImages ( compose2 ) ;
var groups = img1 . Concat ( img2 ) . GroupBy ( g = > g . Name ) ;
foreach ( var g in groups )
{
var tags = new HashSet < String > ( g . Select ( o = > o . Tag ) ) ;
if ( tags . Count ! = 1 )
{
Assert . False ( true , $"All docker images '{g.Key}' in docker-compose.yml and docker-compose.altcoins.yml should have the same tags. (Found {string.Join(',', tags)})" ) ;
}
}
}
2020-08-26 14:24:37 +02:00
[Fact]
[Trait("Fast", "Fast")]
public void CanParsePaymentMethodId ( )
{
var id = PaymentMethodId . Parse ( "BTC" ) ;
var id1 = PaymentMethodId . Parse ( "BTC-OnChain" ) ;
var id2 = PaymentMethodId . Parse ( "BTC-BTCLike" ) ;
Assert . Equal ( id , id1 ) ;
Assert . Equal ( id , id2 ) ;
Assert . Equal ( "BTC" , id . ToString ( ) ) ;
Assert . Equal ( "BTC" , id . ToString ( ) ) ;
id = PaymentMethodId . Parse ( "LTC" ) ;
Assert . Equal ( "LTC" , id . ToString ( ) ) ;
Assert . Equal ( "LTC" , id . ToStringNormalized ( ) ) ;
id = PaymentMethodId . Parse ( "LTC-offchain" ) ;
id1 = PaymentMethodId . Parse ( "LTC-OffChain" ) ;
id2 = PaymentMethodId . Parse ( "LTC-LightningLike" ) ;
Assert . Equal ( id , id1 ) ;
Assert . Equal ( id , id2 ) ;
Assert . Equal ( "LTC_LightningLike" , id . ToString ( ) ) ;
Assert . Equal ( "LTC-LightningNetwork" , id . ToStringNormalized ( ) ) ;
#if ALTCOINS
id = PaymentMethodId . Parse ( "XMR" ) ;
id1 = PaymentMethodId . Parse ( "XMR-MoneroLike" ) ;
Assert . Equal ( id , id1 ) ;
Assert . Equal ( "XMR_MoneroLike" , id . ToString ( ) ) ;
Assert . Equal ( "XMR" , id . ToStringNormalized ( ) ) ;
#endif
}
2019-09-02 09:32:38 +02:00
[Fact]
[Trait("Fast", "Fast")]
public async Task CheckNoDeadLink ( )
{
2019-10-06 16:41:27 +02:00
var views = Path . Combine ( TestUtils . TryGetSolutionDirectoryInfo ( ) . FullName , "BTCPayServer" , "Views" ) ;
2019-09-02 09:32:38 +02:00
var viewFiles = Directory . EnumerateFiles ( views , "*.cshtml" , SearchOption . AllDirectories ) . ToArray ( ) ;
Assert . NotEmpty ( viewFiles ) ;
2020-06-23 17:51:25 +02:00
Regex regex = new Regex ( "href=\"(http.*?)\"" ) ;
2019-09-02 09:32:38 +02:00
var httpClient = new HttpClient ( ) ;
List < Task > checkLinks = new List < Task > ( ) ;
foreach ( var file in viewFiles )
{
checkLinks . Add ( CheckLinks ( regex , httpClient , file ) ) ;
}
2020-04-05 13:48:00 +02:00
2019-09-02 09:32:38 +02:00
await Task . WhenAll ( checkLinks ) ;
}
2020-03-18 12:08:09 +01:00
[Fact]
2020-04-05 09:43:49 +02:00
[Trait("Integration", "Integration")]
2020-03-18 12:08:09 +01:00
public async Task CheckSwaggerIsConformToSchema ( )
{
2020-04-05 09:43:49 +02:00
using ( var tester = ServerTester . Create ( ) )
2020-03-20 17:37:39 +01:00
{
2020-04-05 09:43:49 +02:00
await tester . StartAsync ( ) ;
2020-05-23 21:18:51 +02:00
var acc = tester . NewAccount ( ) ;
2020-04-05 13:48:00 +02:00
2020-05-23 21:18:51 +02:00
var sresp = Assert
. IsType < JsonResult > ( await tester . PayTester . GetController < HomeController > ( acc . UserId , acc . StoreId )
. Swagger ( ) ) . Value . ToJson ( ) ;
JObject swagger = JObject . Parse ( sresp ) ;
2020-04-05 09:43:49 +02:00
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 ( ) ) ;
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.
2020-04-05 11:26:40 +02:00
var matchedError = errors . Where ( error = >
error . Path = = "components.securitySchemes.Basic" & & error . ErrorType = = ErrorType . OneOf ) . ToList ( ) ;
foreach ( ValidationError validationError in matchedError )
2020-04-05 09:43:49 +02:00
{
2020-04-05 11:26:40 +02:00
errors . Remove ( validationError ) ;
2020-04-05 09:43:49 +02:00
}
2020-04-05 11:26:40 +02:00
valid = ! errors . Any ( ) ;
2020-04-05 09:43:49 +02:00
Assert . Empty ( errors ) ;
Assert . True ( valid ) ;
2020-03-20 17:37:39 +01:00
}
2020-03-18 12:08:09 +01:00
}
2019-09-02 09:32:38 +02:00
private static async Task CheckLinks ( Regex regex , HttpClient httpClient , string file )
{
List < Task > checkLinks = new List < Task > ( ) ;
var text = await File . ReadAllTextAsync ( file ) ;
2020-03-27 00:58:12 +01:00
var urlBlacklist = new string [ ]
{
2020-03-31 11:13:22 +02:00
"https://www.btse.com" , // not allowing to be hit from circleci
"https://www.bitpay.com" // not allowing to be hit from circleci
2020-03-27 00:58:12 +01:00
} ;
2019-09-02 09:32:38 +02:00
foreach ( var match in regex . Matches ( text ) . OfType < Match > ( ) )
{
2020-03-27 00:58:12 +01:00
var url = match . Groups [ 1 ] . Value ;
if ( urlBlacklist . Any ( a = > a . StartsWith ( url . ToLowerInvariant ( ) ) ) )
continue ;
checkLinks . Add ( AssertLinkNotDead ( httpClient , url , file ) ) ;
2019-09-02 09:32:38 +02:00
}
2020-04-05 13:48:00 +02:00
2019-09-02 09:32:38 +02:00
await Task . WhenAll ( checkLinks ) ;
}
2020-03-27 00:58:12 +01:00
private static async Task AssertLinkNotDead ( HttpClient httpClient , string url , string file )
2019-09-02 09:32:38 +02:00
{
2020-06-23 17:51:25 +02:00
var uri = new Uri ( url ) ;
2019-09-02 09:32:38 +02:00
try
{
2020-06-23 17:51:25 +02:00
using var request = new HttpRequestMessage ( HttpMethod . Get , uri ) ;
2020-04-05 13:48:00 +02:00
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" ) ;
2020-06-23 17:51:25 +02:00
var response = await httpClient . SendAsync ( request ) ;
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 ) ;
}
2019-09-02 09:32:38 +02:00
Logs . Tester . LogInformation ( $"OK: {url} ({file})" ) ;
}
2020-06-23 17:51:25 +02:00
catch ( Exception ex ) when ( ex is MatchesException )
{
var details = ex . Message ;
Logs . Tester . LogInformation ( $"FAILED: {url} ({file}) – anchor not found: {uri.Fragment}" ) ;
throw ;
}
2020-04-18 19:33:53 +02:00
catch ( Exception ex )
2019-09-02 09:32:38 +02:00
{
2020-04-18 19:33:53 +02:00
var details = ex is EqualException ? ( ex as EqualException ) . Actual : ex . Message ;
Logs . Tester . LogInformation ( $"FAILED: {url} ({file}) {details}" ) ;
2020-06-24 03:34:09 +02:00
2019-09-02 09:32:38 +02:00
throw ;
}
}
2018-05-14 09:32:04 +02:00
[Fact]
2018-10-28 12:59:59 +01:00
[Trait("Fast", "Fast")]
2018-05-14 09:32:04 +02:00
public void CanHandleUriValidation ( )
{
var attribute = new UriAttribute ( ) ;
Assert . True ( attribute . IsValid ( "http://localhost" ) ) ;
Assert . True ( attribute . IsValid ( "http://localhost:1234" ) ) ;
Assert . True ( attribute . IsValid ( "https://localhost" ) ) ;
Assert . True ( attribute . IsValid ( "https://127.0.0.1" ) ) ;
Assert . True ( attribute . IsValid ( "http://127.0.0.1" ) ) ;
Assert . True ( attribute . IsValid ( "http://127.0.0.1:1234" ) ) ;
Assert . True ( attribute . IsValid ( "http://gozo.com" ) ) ;
Assert . True ( attribute . IsValid ( "https://gozo.com" ) ) ;
Assert . True ( attribute . IsValid ( "https://gozo.com:1234" ) ) ;
2018-05-14 09:34:19 +02:00
Assert . True ( attribute . IsValid ( "https://gozo.com:1234/test.css" ) ) ;
Assert . True ( attribute . IsValid ( "https://gozo.com:1234/test.png" ) ) ;
2020-04-05 13:48:00 +02:00
Assert . False ( attribute . IsValid (
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud e" ) ) ;
2018-05-14 09:32:04 +02:00
Assert . False ( attribute . IsValid ( 2 ) ) ;
Assert . False ( attribute . IsValid ( "http://" ) ) ;
Assert . False ( attribute . IsValid ( "httpdsadsa.com" ) ) ;
}
2018-11-30 09:34:43 +01:00
2019-03-17 12:49:26 +01:00
[Fact]
[Trait("Fast", "Fast")]
public void CanParseTorrc ( )
{
var nl = "\n" ;
var input = "# For the hidden service BTCPayServer" + nl +
"HiddenServiceDir /var/lib/tor/hidden_services/BTCPayServer" + nl +
"# Redirecting to nginx" + nl +
"HiddenServicePort 80 172.19.0.10:81" ;
nl = Environment . NewLine ;
var expected = "HiddenServiceDir /var/lib/tor/hidden_services/BTCPayServer" + nl +
"HiddenServicePort 80 172.19.0.10:81" + nl ;
Assert . True ( Torrc . TryParse ( input , out var torrc ) ) ;
Assert . Equal ( expected , torrc . ToString ( ) ) ;
nl = "\r\n" ;
input = "# For the hidden service BTCPayServer" + nl +
2020-04-05 13:48:00 +02:00
"HiddenServiceDir /var/lib/tor/hidden_services/BTCPayServer" + nl +
"# Redirecting to nginx" + nl +
"HiddenServicePort 80 172.19.0.10:81" ;
2019-03-17 12:49:26 +01:00
Assert . True ( Torrc . TryParse ( input , out torrc ) ) ;
Assert . Equal ( expected , torrc . ToString ( ) ) ;
input = "# For the hidden service BTCPayServer" + nl +
2020-04-05 13:48:00 +02:00
"HiddenServiceDir /var/lib/tor/hidden_services/BTCPayServer" + nl +
"# Redirecting to nginx" + nl +
"HiddenServicePort 80 172.19.0.10:80" + nl +
"HiddenServiceDir /var/lib/tor/hidden_services/Woocommerce" + nl +
"# Redirecting to nginx" + nl +
"HiddenServicePort 80 172.19.0.11:80" ;
2019-03-17 12:49:26 +01:00
nl = Environment . NewLine ;
expected = "HiddenServiceDir /var/lib/tor/hidden_services/BTCPayServer" + nl +
2020-04-05 13:48:00 +02:00
"HiddenServicePort 80 172.19.0.10:80" + nl +
"HiddenServiceDir /var/lib/tor/hidden_services/Woocommerce" + nl +
"HiddenServicePort 80 172.19.0.11:80" + nl ;
2019-03-17 12:49:26 +01:00
Assert . True ( Torrc . TryParse ( input , out torrc ) ) ;
Assert . Equal ( expected , torrc . ToString ( ) ) ;
}
2020-08-25 07:33:00 +02:00
#if ALTCOINS
2017-10-27 10:53:04 +02:00
[Fact]
2018-10-28 12:59:59 +01:00
[Trait("Fast", "Fast")]
2017-10-27 10:53:04 +02:00
public void CanCalculateCryptoDue ( )
{
2019-05-29 16:33:31 +02:00
var networkProvider = new BTCPayNetworkProvider ( NetworkType . Regtest ) ;
var paymentMethodHandlerDictionary = new PaymentMethodHandlerDictionary ( new IPaymentMethodHandler [ ]
{
2020-03-29 17:28:22 +02:00
new BitcoinLikePaymentHandler ( null , networkProvider , null , null , null ) ,
2019-05-29 16:33:31 +02:00
new LightningLikePaymentHandler ( null , null , networkProvider , null ) ,
} ) ;
2019-06-04 03:11:52 +02:00
var entity = new InvoiceEntity ( ) ;
2019-12-24 08:20:44 +01:00
entity . Networks = networkProvider ;
2017-12-21 07:52:04 +01:00
#pragma warning disable CS0618
2018-01-12 09:04:47 +01:00
entity . Payments = new System . Collections . Generic . List < PaymentEntity > ( ) ;
2019-05-29 16:33:31 +02:00
entity . SetPaymentMethod ( new PaymentMethod ( )
{
2020-06-24 03:34:09 +02:00
CryptoCode = "BTC" ,
Rate = 5000 ,
NextNetworkFee = Money . Coins ( 0.1 m )
2019-05-29 16:33:31 +02:00
} ) ;
2020-08-25 07:33:00 +02:00
entity . Price = 5000 ;
2018-01-12 09:04:47 +01:00
2019-05-24 15:22:38 +02:00
var paymentMethod = entity . GetPaymentMethods ( ) . TryGet ( "BTC" , PaymentTypes . BTCLike ) ;
2018-02-19 07:09:05 +01:00
var accounting = paymentMethod . Calculate ( ) ;
2017-12-21 10:01:26 +01:00
Assert . Equal ( Money . Coins ( 1.1 m ) , accounting . Due ) ;
Assert . Equal ( Money . Coins ( 1.1 m ) , accounting . TotalDue ) ;
2017-10-27 10:53:04 +02:00
2019-05-29 16:33:31 +02:00
entity . Payments . Add ( new PaymentEntity ( )
{
2020-06-24 03:34:09 +02:00
Output = new TxOut ( Money . Coins ( 0.5 m ) , new Key ( ) ) ,
Accounted = true ,
NetworkFee = 0.1 m
2019-05-29 16:33:31 +02:00
} ) ;
2017-10-27 10:53:04 +02:00
2018-02-19 07:09:05 +01:00
accounting = paymentMethod . Calculate ( ) ;
2017-10-27 10:53:04 +02:00
//Since we need to spend one more txout, it should be 1.1 - 0,5 + 0.1
2017-12-21 10:01:26 +01:00
Assert . Equal ( Money . Coins ( 0.7 m ) , accounting . Due ) ;
Assert . Equal ( Money . Coins ( 1.2 m ) , accounting . TotalDue ) ;
2017-10-27 10:53:04 +02:00
2019-05-29 16:33:31 +02:00
entity . Payments . Add ( new PaymentEntity ( )
{
2020-06-24 03:34:09 +02:00
Output = new TxOut ( Money . Coins ( 0.2 m ) , new Key ( ) ) ,
Accounted = true ,
NetworkFee = 0.1 m
2019-05-29 16:33:31 +02:00
} ) ;
2017-12-21 10:01:26 +01:00
2018-02-19 07:09:05 +01:00
accounting = paymentMethod . Calculate ( ) ;
2017-12-21 10:01:26 +01:00
Assert . Equal ( Money . Coins ( 0.6 m ) , accounting . Due ) ;
Assert . Equal ( Money . Coins ( 1.3 m ) , accounting . TotalDue ) ;
2017-10-27 10:53:04 +02:00
2019-05-29 16:33:31 +02:00
entity . Payments . Add ( new PaymentEntity ( )
{
2020-06-24 03:34:09 +02:00
Output = new TxOut ( Money . Coins ( 0.6 m ) , new Key ( ) ) ,
Accounted = true ,
NetworkFee = 0.1 m
2019-05-29 16:33:31 +02:00
} ) ;
2017-10-27 10:53:04 +02:00
2018-02-19 07:09:05 +01:00
accounting = paymentMethod . Calculate ( ) ;
2017-12-21 10:01:26 +01:00
Assert . Equal ( Money . Zero , accounting . Due ) ;
Assert . Equal ( Money . Coins ( 1.3 m ) , accounting . TotalDue ) ;
2017-10-27 10:53:04 +02:00
2019-07-01 05:39:25 +02:00
entity . Payments . Add (
2020-06-24 03:34:09 +02:00
new PaymentEntity ( ) { Output = new TxOut ( Money . Coins ( 0.2 m ) , new Key ( ) ) , Accounted = true } ) ;
2017-10-27 10:53:04 +02:00
2018-02-19 07:09:05 +01:00
accounting = paymentMethod . Calculate ( ) ;
2017-12-21 10:01:26 +01:00
Assert . Equal ( Money . Zero , accounting . Due ) ;
Assert . Equal ( Money . Coins ( 1.3 m ) , accounting . TotalDue ) ;
2018-01-09 02:54:19 +01:00
2019-06-04 03:11:52 +02:00
entity = new InvoiceEntity ( ) ;
2019-12-24 08:20:44 +01:00
entity . Networks = networkProvider ;
2020-08-25 07:33:00 +02:00
entity . Price = 5000 ;
2018-02-19 07:09:05 +01:00
PaymentMethodDictionary paymentMethods = new PaymentMethodDictionary ( ) ;
2019-05-29 16:33:31 +02:00
paymentMethods . Add (
2020-06-24 03:34:09 +02:00
new PaymentMethod ( ) { CryptoCode = "BTC" , Rate = 1000 , NextNetworkFee = Money . Coins ( 0.1 m ) } ) ;
2019-05-29 16:33:31 +02:00
paymentMethods . Add (
2020-06-24 03:34:09 +02:00
new PaymentMethod ( ) { CryptoCode = "LTC" , Rate = 500 , NextNetworkFee = Money . Coins ( 0.01 m ) } ) ;
2018-02-19 07:09:05 +01:00
entity . SetPaymentMethods ( paymentMethods ) ;
2018-01-09 02:54:19 +01:00
entity . Payments = new List < PaymentEntity > ( ) ;
2019-06-07 06:24:36 +02:00
paymentMethod = entity . GetPaymentMethod ( new PaymentMethodId ( "BTC" , PaymentTypes . BTCLike ) ) ;
2018-02-19 07:09:05 +01:00
accounting = paymentMethod . Calculate ( ) ;
2018-01-09 02:54:19 +01:00
Assert . Equal ( Money . Coins ( 5.1 m ) , accounting . Due ) ;
2019-06-07 06:24:36 +02:00
paymentMethod = entity . GetPaymentMethod ( new PaymentMethodId ( "LTC" , PaymentTypes . BTCLike ) ) ;
2018-02-19 07:09:05 +01:00
accounting = paymentMethod . Calculate ( ) ;
2019-05-29 16:33:31 +02:00
2018-01-09 02:54:19 +01:00
Assert . Equal ( Money . Coins ( 10.01 m ) , accounting . TotalDue ) ;
2019-05-29 16:33:31 +02:00
entity . Payments . Add ( new PaymentEntity ( )
{
CryptoCode = "BTC" ,
Output = new TxOut ( Money . Coins ( 1.0 m ) , new Key ( ) ) ,
Accounted = true ,
2019-06-04 03:11:52 +02:00
NetworkFee = 0.1 m
2019-05-29 16:33:31 +02:00
} ) ;
2018-01-09 02:54:19 +01:00
2019-06-07 06:24:36 +02:00
paymentMethod = entity . GetPaymentMethod ( new PaymentMethodId ( "BTC" , PaymentTypes . BTCLike ) ) ;
2018-02-19 07:09:05 +01:00
accounting = paymentMethod . Calculate ( ) ;
2018-01-09 02:54:19 +01:00
Assert . Equal ( Money . Coins ( 4.2 m ) , accounting . Due ) ;
Assert . Equal ( Money . Coins ( 1.0 m ) , accounting . CryptoPaid ) ;
Assert . Equal ( Money . Coins ( 1.0 m ) , accounting . Paid ) ;
Assert . Equal ( Money . Coins ( 5.2 m ) , accounting . TotalDue ) ;
2018-02-25 16:48:12 +01:00
Assert . Equal ( 2 , accounting . TxRequired ) ;
2018-01-09 02:54:19 +01:00
2019-06-07 06:24:36 +02:00
paymentMethod = entity . GetPaymentMethod ( new PaymentMethodId ( "LTC" , PaymentTypes . BTCLike ) ) ;
2018-02-19 07:09:05 +01:00
accounting = paymentMethod . Calculate ( ) ;
2018-01-09 02:54:19 +01:00
Assert . Equal ( Money . Coins ( 10.01 m + 0.1 m * 2 - 2.0 m /* 8.21m */ ) , accounting . Due ) ;
Assert . Equal ( Money . Coins ( 0.0 m ) , accounting . CryptoPaid ) ;
Assert . Equal ( Money . Coins ( 2.0 m ) , accounting . Paid ) ;
Assert . Equal ( Money . Coins ( 10.01 m + 0.1 m * 2 ) , accounting . TotalDue ) ;
2019-05-29 16:33:31 +02:00
entity . Payments . Add ( new PaymentEntity ( )
{
CryptoCode = "LTC" ,
Output = new TxOut ( Money . Coins ( 1.0 m ) , new Key ( ) ) ,
Accounted = true ,
2019-06-04 03:11:52 +02:00
NetworkFee = 0.01 m
2019-05-29 16:33:31 +02:00
} ) ;
2018-01-09 02:54:19 +01:00
2019-06-07 06:24:36 +02:00
paymentMethod = entity . GetPaymentMethod ( new PaymentMethodId ( "BTC" , PaymentTypes . BTCLike ) ) ;
2018-02-19 07:09:05 +01:00
accounting = paymentMethod . Calculate ( ) ;
2018-01-09 02:54:19 +01:00
Assert . Equal ( Money . Coins ( 4.2 m - 0.5 m + 0.01 m / 2 ) , accounting . Due ) ;
Assert . Equal ( Money . Coins ( 1.0 m ) , accounting . CryptoPaid ) ;
Assert . Equal ( Money . Coins ( 1.5 m ) , accounting . Paid ) ;
Assert . Equal ( Money . Coins ( 5.2 m + 0.01 m / 2 ) , accounting . TotalDue ) ; // The fee for LTC added
2018-02-25 16:48:12 +01:00
Assert . Equal ( 2 , accounting . TxRequired ) ;
2018-01-09 02:54:19 +01:00
2019-06-07 06:24:36 +02:00
paymentMethod = entity . GetPaymentMethod ( new PaymentMethodId ( "LTC" , PaymentTypes . BTCLike ) ) ;
2018-02-19 07:09:05 +01:00
accounting = paymentMethod . Calculate ( ) ;
2018-01-09 02:54:19 +01:00
Assert . Equal ( Money . Coins ( 8.21 m - 1.0 m + 0.01 m ) , accounting . Due ) ;
Assert . Equal ( Money . Coins ( 1.0 m ) , accounting . CryptoPaid ) ;
Assert . Equal ( Money . Coins ( 3.0 m ) , accounting . Paid ) ;
Assert . Equal ( Money . Coins ( 10.01 m + 0.1 m * 2 + 0.01 m ) , accounting . TotalDue ) ;
2018-02-25 16:48:12 +01:00
Assert . Equal ( 2 , accounting . TxRequired ) ;
2018-01-09 02:54:19 +01:00
var remaining = Money . Coins ( 4.2 m - 0.5 m + 0.01 m / 2 ) ;
2019-05-29 16:33:31 +02:00
entity . Payments . Add ( new PaymentEntity ( )
{
2020-06-24 03:34:09 +02:00
CryptoCode = "BTC" ,
Output = new TxOut ( remaining , new Key ( ) ) ,
Accounted = true ,
NetworkFee = 0.1 m
2019-05-29 16:33:31 +02:00
} ) ;
2018-01-09 02:54:19 +01:00
2019-06-07 06:24:36 +02:00
paymentMethod = entity . GetPaymentMethod ( new PaymentMethodId ( "BTC" , PaymentTypes . BTCLike ) ) ;
2018-02-19 07:09:05 +01:00
accounting = paymentMethod . Calculate ( ) ;
2018-01-09 02:54:19 +01:00
Assert . Equal ( Money . Zero , accounting . Due ) ;
Assert . Equal ( Money . Coins ( 1.0 m ) + remaining , accounting . CryptoPaid ) ;
Assert . Equal ( Money . Coins ( 1.5 m ) + remaining , accounting . Paid ) ;
Assert . Equal ( Money . Coins ( 5.2 m + 0.01 m / 2 ) , accounting . TotalDue ) ;
Assert . Equal ( accounting . Paid , accounting . TotalDue ) ;
2018-02-25 16:48:12 +01:00
Assert . Equal ( 2 , accounting . TxRequired ) ;
2018-01-09 02:54:19 +01:00
2019-06-07 06:24:36 +02:00
paymentMethod = entity . GetPaymentMethod ( new PaymentMethodId ( "LTC" , PaymentTypes . BTCLike ) ) ;
2018-02-19 07:09:05 +01:00
accounting = paymentMethod . Calculate ( ) ;
2018-01-09 02:54:19 +01:00
Assert . Equal ( Money . Zero , accounting . Due ) ;
Assert . Equal ( Money . Coins ( 1.0 m ) , accounting . CryptoPaid ) ;
Assert . Equal ( Money . Coins ( 3.0 m ) + remaining * 2 , accounting . Paid ) ;
// Paying 2 BTC fee, LTC fee removed because fully paid
2019-05-29 16:33:31 +02:00
Assert . Equal ( Money . Coins ( 10.01 m + 0.1 m * 2 + 0.1 m * 2 /* + 0.01m no need to pay this fee anymore */ ) ,
accounting . TotalDue ) ;
2018-02-25 16:48:12 +01:00
Assert . Equal ( 1 , accounting . TxRequired ) ;
2018-01-09 02:54:19 +01:00
Assert . Equal ( accounting . Paid , accounting . TotalDue ) ;
2017-12-21 07:52:04 +01:00
#pragma warning restore CS0618
2017-10-27 10:53:04 +02:00
}
2020-08-25 07:33:00 +02:00
#endif
2019-05-14 17:46:43 +02:00
[Fact]
[Trait("Integration", "Integration")]
public async Task CanUseTestWebsiteUI ( )
{
using ( var tester = ServerTester . Create ( ) )
{
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2019-07-01 05:39:25 +02:00
var response = await tester . PayTester . HttpClient . GetAsync ( "" ) ;
2019-05-14 17:46:43 +02:00
Assert . True ( response . IsSuccessStatusCode ) ;
}
}
2020-04-08 06:46:11 +02:00
[Fact]
[Trait("Fast", "Fast")]
public void DeterministicUTXOSorter ( )
{
UTXO CreateRandomUTXO ( )
{
return new UTXO ( ) { Outpoint = new OutPoint ( RandomUtils . GetUInt256 ( ) , RandomUtils . GetUInt32 ( ) % 0xff ) } ;
}
var comparer = Payments . PayJoin . PayJoinEndpointController . UTXODeterministicComparer . Instance ;
var utxos = Enumerable . Range ( 0 , 100 ) . Select ( _ = > CreateRandomUTXO ( ) ) . ToArray ( ) ;
Array . Sort ( utxos , comparer ) ;
var utxo53 = utxos [ 53 ] ;
Array . Sort ( utxos , comparer ) ;
Assert . Equal ( utxo53 , utxos [ 53 ] ) ;
var utxo54 = utxos [ 54 ] ;
var utxo52 = utxos [ 52 ] ;
utxos = utxos . Where ( ( _ , i ) = > i ! = 53 ) . ToArray ( ) ;
Array . Sort ( utxos , comparer ) ;
Assert . Equal ( utxo52 , utxos [ 52 ] ) ;
Assert . Equal ( utxo54 , utxos [ 53 ] ) ;
}
2018-05-05 16:07:22 +02:00
[Fact]
2019-05-29 16:33:31 +02:00
[Trait("Fast", "Fast")]
2018-05-05 16:07:22 +02:00
public void CanAcceptInvoiceWithTolerance ( )
{
2019-05-29 16:33:31 +02:00
var networkProvider = new BTCPayNetworkProvider ( NetworkType . Regtest ) ;
var paymentMethodHandlerDictionary = new PaymentMethodHandlerDictionary ( new IPaymentMethodHandler [ ]
{
2020-03-29 17:28:22 +02:00
new BitcoinLikePaymentHandler ( null , networkProvider , null , null , null ) ,
2019-05-29 16:33:31 +02:00
new LightningLikePaymentHandler ( null , null , networkProvider , null ) ,
} ) ;
2019-06-04 03:11:52 +02:00
var entity = new InvoiceEntity ( ) ;
2019-12-24 08:20:44 +01:00
entity . Networks = networkProvider ;
2018-05-05 16:07:22 +02:00
#pragma warning disable CS0618
entity . Payments = new List < PaymentEntity > ( ) ;
2019-05-29 16:33:31 +02:00
entity . SetPaymentMethod ( new PaymentMethod ( )
{
2020-06-24 03:34:09 +02:00
CryptoCode = "BTC" ,
Rate = 5000 ,
NextNetworkFee = Money . Coins ( 0.1 m )
2019-05-29 16:33:31 +02:00
} ) ;
2020-08-25 07:33:00 +02:00
entity . Price = 5000 ;
2018-05-05 16:07:22 +02:00
entity . PaymentTolerance = 0 ;
2019-05-24 15:22:38 +02:00
var paymentMethod = entity . GetPaymentMethods ( ) . TryGet ( "BTC" , PaymentTypes . BTCLike ) ;
2018-05-05 16:07:22 +02:00
var accounting = paymentMethod . Calculate ( ) ;
Assert . Equal ( Money . Coins ( 1.1 m ) , accounting . Due ) ;
Assert . Equal ( Money . Coins ( 1.1 m ) , accounting . TotalDue ) ;
Assert . Equal ( Money . Coins ( 1.1 m ) , accounting . MinimumTotalDue ) ;
2020-04-05 13:48:00 +02:00
entity . PaymentTolerance = 10 ;
accounting = paymentMethod . Calculate ( ) ;
Assert . Equal ( Money . Coins ( 0.99 m ) , accounting . MinimumTotalDue ) ;
2018-05-05 16:07:22 +02:00
2020-04-05 13:48:00 +02:00
entity . PaymentTolerance = 100 ;
accounting = paymentMethod . Calculate ( ) ;
Assert . Equal ( Money . Satoshis ( 1 ) , accounting . MinimumTotalDue ) ;
2018-05-05 16:07:22 +02:00
}
2018-05-05 17:40:44 +02:00
[Fact]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2019-10-07 09:04:25 +02:00
public async Task CanAcceptInvoiceWithTolerance2 ( )
2018-05-05 17:40:44 +02:00
{
using ( var tester = ServerTester . Create ( ) )
{
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2018-05-05 17:40:44 +02:00
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
2018-05-13 08:09:17 +02:00
2018-05-05 17:40:44 +02:00
// Set tolerance to 50%
var stores = user . GetController < StoresController > ( ) ;
var vm = Assert . IsType < StoreViewModel > ( Assert . IsType < ViewResult > ( stores . UpdateStore ( ) ) . Model ) ;
Assert . Equal ( 0.0 , vm . PaymentTolerance ) ;
vm . PaymentTolerance = 50.0 ;
Assert . IsType < RedirectToActionResult > ( stores . UpdateStore ( vm ) . Result ) ;
2020-04-05 13:48:00 +02:00
var invoice = user . BitPay . CreateInvoice (
new Invoice ( )
{
2020-06-24 03:34:09 +02:00
Buyer = new Buyer ( ) { email = "test@fwf.com" } ,
2020-04-05 13:48:00 +02:00
Price = 5000.0 m ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
2018-05-05 17:40:44 +02:00
// Pays 75%
var invoiceAddress = BitcoinAddress . Create ( invoice . CryptoInfo [ 0 ] . Address , tester . ExplorerNode . Network ) ;
2020-04-05 13:48:00 +02:00
tester . ExplorerNode . SendToAddress ( invoiceAddress ,
2020-06-29 05:07:48 +02:00
Money . Satoshis ( invoice . BtcDue . Satoshi * 0.75 m ) ) ;
2018-05-05 17:40:44 +02:00
2019-01-05 19:47:39 +01:00
TestUtils . Eventually ( ( ) = >
2018-05-05 17:40:44 +02:00
{
var localInvoice = user . BitPay . GetInvoice ( invoice . Id , Facade . Merchant ) ;
Assert . Equal ( "paid" , localInvoice . Status ) ;
} ) ;
}
}
2020-02-03 09:21:03 +01:00
[Fact]
[Trait("Integration", "Integration")]
public async Task CanThrowBitpay404Error ( )
{
using ( var tester = ServerTester . Create ( ) )
{
await tester . StartAsync ( ) ;
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
2020-04-05 13:48:00 +02:00
var invoice = user . BitPay . CreateInvoice (
new Invoice ( )
{
2020-06-24 03:34:09 +02:00
Buyer = new Buyer ( ) { email = "test@fwf.com" } ,
2020-04-05 13:48:00 +02:00
Price = 5000.0 m ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
2020-02-03 09:21:03 +01:00
try
{
var throwsBitpay404Error = user . BitPay . GetInvoice ( invoice . Id + "123" ) ;
}
catch ( BitPayException ex )
{
Assert . Equal ( "Object not found" , ex . Errors . First ( ) ) ;
}
2020-05-01 14:33:27 +02:00
var req = new HttpRequestMessage ( HttpMethod . Get , "/invoices/Cy9jfK82eeEED1T3qhwF3Y" ) ;
req . Headers . TryAddWithoutValidation ( "Authorization" , "Basic dGVzdA==" ) ;
req . Content = new StringContent ( "{}" , Encoding . UTF8 , "application/json" ) ;
var result = await tester . PayTester . HttpClient . SendAsync ( req ) ;
Assert . Equal ( HttpStatusCode . Unauthorized , result . StatusCode ) ;
Assert . Equal ( 0 , result . Content . Headers . ContentLength . Value ) ;
2020-02-03 09:21:03 +01:00
}
}
2020-06-24 03:34:09 +02:00
[Fact]
[Trait("Fast", "Fast")]
public void CanCalculatePeriod ( )
{
Data . PullPaymentData data = new Data . PullPaymentData ( ) ;
data . StartDate = Date ( 0 ) ;
data . EndDate = null ;
var period = data . GetPeriod ( Date ( 1 ) ) . Value ;
Assert . Equal ( Date ( 0 ) , period . Start ) ;
Assert . Null ( period . End ) ;
data . EndDate = Date ( 7 ) ;
period = data . GetPeriod ( Date ( 1 ) ) . Value ;
Assert . Equal ( Date ( 0 ) , period . Start ) ;
Assert . Equal ( Date ( 7 ) , period . End ) ;
data . Period = ( long ) TimeSpan . FromDays ( 2 ) . TotalSeconds ;
period = data . GetPeriod ( Date ( 1 ) ) . Value ;
Assert . Equal ( Date ( 0 ) , period . Start ) ;
Assert . Equal ( Date ( 2 ) , period . End ) ;
period = data . GetPeriod ( Date ( 2 ) ) . Value ;
Assert . Equal ( Date ( 2 ) , period . Start ) ;
Assert . Equal ( Date ( 4 ) , period . End ) ;
period = data . GetPeriod ( Date ( 6 ) ) . Value ;
Assert . Equal ( Date ( 6 ) , period . Start ) ;
Assert . Equal ( Date ( 7 ) , period . End ) ;
Assert . Null ( data . GetPeriod ( Date ( 7 ) ) ) ;
Assert . Null ( data . GetPeriod ( Date ( 8 ) ) ) ;
data . EndDate = null ;
period = data . GetPeriod ( Date ( 6 ) ) . Value ;
Assert . Equal ( Date ( 6 ) , period . Start ) ;
Assert . Equal ( Date ( 8 ) , period . End ) ;
Assert . Null ( data . GetPeriod ( Date ( - 1 ) ) ) ;
}
private DateTimeOffset Date ( int days )
{
return new DateTimeOffset ( 1970 , 1 , 1 , 0 , 0 , 0 , TimeSpan . Zero ) + TimeSpan . FromDays ( days ) ;
}
2018-05-13 08:09:17 +02:00
[Fact]
2018-10-28 12:59:59 +01:00
[Trait("Fast", "Fast")]
2018-05-13 08:09:17 +02:00
public void RoundupCurrenciesCorrectly ( )
{
2018-06-18 16:07:05 +02:00
foreach ( var test in new [ ]
2018-05-13 08:09:17 +02:00
{
2020-04-05 13:48:00 +02:00
( 0.0005 m , "$0.0005 (USD)" , "USD" ) , ( 0.001 m , "$0.001 (USD)" , "USD" ) , ( 0.01 m , "$0.01 (USD)" , "USD" ) ,
( 0.1 m , "$0.10 (USD)" , "USD" ) , ( 0.1 m , "0,10 € (EUR)" , "EUR" ) , ( 1000 m , "¥1,000 (JPY)" , "JPY" ) ,
2018-12-17 16:25:17 +01:00
( 1000.0001 m , "₹ 1,000.00 (INR)" , "INR" )
2018-05-13 08:09:17 +02:00
} )
{
2020-05-31 12:18:29 +02:00
var actual = CurrencyNameTable . Instance . DisplayFormatCurrency ( test . Item1 , test . Item3 ) ;
2018-12-18 16:28:06 +01:00
actual = actual . Replace ( "ï¿¥" , "Â¥" ) ; // Hack so JPY test pass on linux as well
2018-05-13 08:09:17 +02:00
Assert . Equal ( test . Item2 , actual ) ;
}
}
2019-11-08 08:10:49 +01:00
[Fact]
[Trait("Fast", "Fast")]
public async Task CanEnumerateTorServices ( )
{
2020-04-05 13:48:00 +02:00
var tor = new TorServices ( new BTCPayNetworkProvider ( NetworkType . Regtest ) ,
2020-06-24 03:34:09 +02:00
new BTCPayServerOptions ( ) { TorrcFile = TestUtils . GetTestDataFullPath ( "Tor/torrc" ) } ) ;
2019-11-08 08:10:49 +01:00
await tor . Refresh ( ) ;
Assert . Single ( tor . Services . Where ( t = > t . ServiceType = = TorServiceType . BTCPayServer ) ) ;
Assert . Single ( tor . Services . Where ( t = > t . ServiceType = = TorServiceType . P2P ) ) ;
Assert . Single ( tor . Services . Where ( t = > t . ServiceType = = TorServiceType . RPC ) ) ;
Assert . True ( tor . Services . Where ( t = > t . ServiceType = = TorServiceType . Other ) . Count ( ) > 1 ) ;
}
2020-09-15 13:46:45 +02:00
[Fact(Timeout = 60 * 2 * 1000)]
[Trait("Integration", "Integration")]
[Trait("Lightning", "Lightning")]
public async Task EnsureNewLightningInvoiceOnPartialPayment ( )
{
using var tester = ServerTester . Create ( ) ;
tester . ActivateLightning ( ) ;
await tester . StartAsync ( ) ;
await tester . EnsureChannelsSetup ( ) ;
var user = tester . NewAccount ( ) ;
await user . GrantAccessAsync ( ) ;
await user . RegisterDerivationSchemeAsync ( "BTC" ) ;
await user . RegisterLightningNodeAsync ( "BTC" , LightningConnectionType . CLightning ) ;
var invoice = await user . BitPay . CreateInvoiceAsync ( new Invoice ( 0.01 m , "BTC" ) ) ;
await tester . WaitForEvent < InvoiceNewAddressEvent > ( async ( ) = >
{
await tester . ExplorerNode . SendToAddressAsync (
BitcoinAddress . Create ( invoice . BitcoinAddress , Network . RegTest ) , Money . Coins ( 0.005 m ) ) ;
} ) ;
var localInvoice = await user . BitPay . GetInvoiceAsync ( invoice . Id ) ;
Assert . NotEqual ( invoice . CryptoInfo . First ( o = > o . PaymentUrls . BOLT11 ! = null ) . PaymentUrls . BOLT11 ,
localInvoice . CryptoInfo . First ( o = > o . PaymentUrls . BOLT11 ! = null ) . PaymentUrls . BOLT11 ) ;
}
2019-04-05 09:28:18 +02:00
[Fact(Timeout = 60 * 2 * 1000)]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2019-12-24 10:11:21 +01:00
[Trait("Lightning", "Lightning")]
2018-10-28 14:46:03 +01:00
public async Task CanSetLightningServer ( )
2018-02-25 16:48:12 +01:00
{
using ( var tester = ServerTester . Create ( ) )
{
2019-12-24 10:11:21 +01:00
tester . ActivateLightning ( ) ;
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2018-10-28 14:46:03 +01:00
await tester . EnsureChannelsSetup ( ) ;
2018-02-25 16:48:12 +01:00
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
2018-04-29 19:33:42 +02:00
var storeController = user . GetController < StoresController > ( ) ;
2018-05-03 18:46:52 +02:00
Assert . IsType < ViewResult > ( storeController . UpdateStore ( ) ) ;
2018-04-29 19:33:42 +02:00
Assert . IsType < ViewResult > ( storeController . AddLightningNode ( user . StoreId , "BTC" ) ) ;
2018-02-25 16:48:12 +01:00
var testResult = storeController . AddLightningNode ( user . StoreId , new LightningNodeViewModel ( )
{
2020-08-16 15:09:10 +02:00
ConnectionString = $"type=charge;server={tester.MerchantCharge.Client.Uri.AbsoluteUri};allowinsecure=true" ,
2018-07-19 07:49:30 +02:00
SkipPortTest = true // We can't test this as the IP can't be resolved by the test host :(
2018-03-20 18:48:11 +01:00
} , "test" , "BTC" ) . GetAwaiter ( ) . GetResult ( ) ;
2019-10-31 04:29:59 +01:00
Assert . False ( storeController . TempData . ContainsKey ( WellKnownTempData . ErrorMessage ) ) ;
storeController . TempData . Clear ( ) ;
2018-02-25 16:48:12 +01:00
Assert . True ( storeController . ModelState . IsValid ) ;
2020-04-05 13:48:00 +02:00
Assert . IsType < RedirectToActionResult > ( storeController . AddLightningNode ( user . StoreId ,
new LightningNodeViewModel ( )
{
2020-08-16 15:09:10 +02:00
ConnectionString = $"type=charge;server={tester.MerchantCharge.Client.Uri.AbsoluteUri};allowinsecure=true"
2020-04-05 13:48:00 +02:00
} , "save" , "BTC" ) . GetAwaiter ( ) . GetResult ( ) ) ;
2018-07-01 09:10:17 +02:00
// Make sure old connection string format does not work
2020-04-05 13:48:00 +02:00
Assert . IsType < ViewResult > ( storeController . AddLightningNode ( user . StoreId ,
2020-06-24 03:34:09 +02:00
new LightningNodeViewModel ( ) { ConnectionString = tester . MerchantCharge . Client . Uri . AbsoluteUri } ,
2020-04-05 13:48:00 +02:00
"save" , "BTC" ) . GetAwaiter ( ) . GetResult ( ) ) ;
2018-02-25 16:48:12 +01:00
2020-04-05 13:48:00 +02:00
var storeVm =
Assert . IsType < Models . StoreViewModels . StoreViewModel > ( Assert
. IsType < ViewResult > ( storeController . UpdateStore ( ) ) . Model ) ;
2018-03-20 18:48:11 +01:00
Assert . Single ( storeVm . LightningNodes . Where ( l = > ! string . IsNullOrEmpty ( l . Address ) ) ) ;
2018-02-25 16:48:12 +01:00
}
2017-12-13 07:49:19 +01:00
}
2019-03-27 10:57:51 +01:00
[Fact(Timeout = 60 * 2 * 1000)]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2019-12-24 10:11:21 +01:00
[Trait("Lightning", "Lightning")]
2018-08-30 04:50:39 +02:00
public async Task CanSendLightningPaymentCLightning ( )
2018-03-20 16:31:19 +01:00
{
2018-08-30 04:50:39 +02:00
await ProcessLightningPayment ( LightningConnectionType . CLightning ) ;
2018-03-20 16:31:19 +01:00
}
2019-03-27 10:57:51 +01:00
[Fact(Timeout = 60 * 2 * 1000)]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2019-12-24 10:11:21 +01:00
[Trait("Lightning", "Lightning")]
2018-08-30 04:50:39 +02:00
public async Task CanSendLightningPaymentCharge ( )
2018-02-23 07:21:42 +01:00
{
2018-08-30 04:50:39 +02:00
await ProcessLightningPayment ( LightningConnectionType . Charge ) ;
2018-05-25 19:18:47 +02:00
}
2018-02-23 07:21:42 +01:00
2019-03-27 10:57:51 +01:00
[Fact(Timeout = 60 * 2 * 1000)]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2019-12-24 10:11:21 +01:00
[Trait("Lightning", "Lightning")]
2018-08-30 04:50:39 +02:00
public async Task CanSendLightningPaymentLnd ( )
2018-05-31 23:07:59 +02:00
{
2018-08-30 04:50:39 +02:00
await ProcessLightningPayment ( LightningConnectionType . LndREST ) ;
2018-05-31 23:07:59 +02:00
}
2018-05-25 19:18:47 +02:00
2018-08-30 04:50:39 +02:00
async Task ProcessLightningPayment ( LightningConnectionType type )
2018-05-25 19:18:47 +02:00
{
2018-06-15 20:57:39 +02:00
// For easier debugging and testing
2018-06-16 00:20:56 +02:00
// LightningLikePaymentHandler.LIGHTNING_TIMEOUT = int.MaxValue;
2018-02-23 07:21:42 +01:00
using ( var tester = ServerTester . Create ( ) )
{
2019-12-24 10:11:21 +01:00
tester . ActivateLightning ( ) ;
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2018-10-28 14:46:03 +01:00
await tester . EnsureChannelsSetup ( ) ;
2018-02-25 16:48:12 +01:00
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
2018-05-25 19:18:47 +02:00
user . RegisterLightningNode ( "BTC" , type ) ;
2018-02-25 16:48:12 +01:00
user . RegisterDerivationScheme ( "BTC" ) ;
2018-07-27 11:04:41 +02:00
2018-08-30 04:50:39 +02:00
await CanSendLightningPaymentCore ( tester , user ) ;
2018-02-26 05:29:23 +01:00
2018-08-30 04:50:39 +02:00
await Task . WhenAll ( Enumerable . Range ( 0 , 5 )
2018-02-26 05:29:23 +01:00
. Select ( _ = > CanSendLightningPaymentCore ( tester , user ) )
. ToArray ( ) ) ;
2018-02-23 07:21:42 +01:00
}
}
2018-02-26 05:29:23 +01:00
async Task CanSendLightningPaymentCore ( ServerTester tester , TestAccount user )
{
var invoice = await user . BitPay . CreateInvoiceAsync ( new Invoice ( )
{
2018-05-11 15:38:31 +02:00
Price = 0.01 m ,
2018-02-26 05:29:23 +01:00
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description"
} ) ;
2018-10-28 16:22:30 +01:00
await Task . Delay ( TimeSpan . FromMilliseconds ( 1000 ) ) ; // Give time to listen the new invoices
2019-03-27 07:53:06 +01:00
Logs . Tester . LogInformation ( $"Trying to send Lightning payment to {invoice.Id}" ) ;
2018-02-26 05:29:23 +01:00
await tester . SendLightningPaymentAsync ( invoice ) ;
2019-03-27 07:53:06 +01:00
Logs . Tester . LogInformation ( $"Lightning payment to {invoice.Id} is sent" ) ;
2019-01-05 19:47:39 +01:00
await TestUtils . EventuallyAsync ( async ( ) = >
2018-02-26 05:29:23 +01:00
{
var localInvoice = await user . BitPay . GetInvoiceAsync ( invoice . Id ) ;
Assert . Equal ( "complete" , localInvoice . Status ) ;
2020-01-10 03:13:54 +01:00
// C-Lightning may overpay for privacy
2020-06-24 03:34:09 +02:00
Assert . Contains ( localInvoice . ExceptionStatus . ToString ( ) , new [ ] { "False" , "paidOver" } ) ;
2018-02-26 05:29:23 +01:00
} ) ;
}
2019-10-06 15:51:01 +02:00
[Fact(Timeout = TestTimeout)]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2019-10-07 09:04:25 +02:00
public async Task CanUseServerInitiatedPairingCode ( )
2017-10-27 10:53:04 +02:00
{
using ( var tester = ServerTester . Create ( ) )
{
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2017-10-27 10:53:04 +02:00
var acc = tester . NewAccount ( ) ;
acc . Register ( ) ;
acc . CreateStore ( ) ;
2018-04-29 19:33:42 +02:00
var controller = acc . GetController < StoresController > ( ) ;
2020-04-05 13:48:00 +02:00
var token = ( RedirectToActionResult ) await controller . CreateToken2 (
new Models . StoreViewModels . CreateTokenViewModel ( )
{
2020-06-24 03:34:09 +02:00
Label = "bla" ,
PublicKey = null ,
StoreId = acc . StoreId
2020-04-05 13:48:00 +02:00
} ) ;
2017-10-27 10:53:04 +02:00
var pairingCode = ( string ) token . RouteValues [ "pairingCode" ] ;
acc . BitPay . AuthorizeClient ( new PairingCode ( pairingCode ) ) . GetAwaiter ( ) . GetResult ( ) ;
Assert . True ( acc . BitPay . TestAccess ( Facade . Merchant ) ) ;
}
}
2019-10-06 15:51:01 +02:00
[Fact(Timeout = TestTimeout)]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2019-01-06 07:04:30 +01:00
public async Task CanSendIPN ( )
2017-10-27 10:53:04 +02:00
{
using ( var callbackServer = new CustomServer ( ) )
{
using ( var tester = ServerTester . Create ( ) )
{
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2017-10-27 10:53:04 +02:00
var acc = tester . NewAccount ( ) ;
acc . GrantAccess ( ) ;
2018-02-23 07:21:42 +01:00
acc . RegisterDerivationScheme ( "BTC" ) ;
2019-01-06 07:04:30 +01:00
acc . ModifyStore ( s = > s . SpeedPolicy = SpeedPolicy . LowSpeed ) ;
2017-10-27 10:53:04 +02:00
var invoice = acc . BitPay . CreateInvoice ( new Invoice ( )
{
2018-05-11 15:38:31 +02:00
Price = 5.0 m ,
2017-10-27 10:53:04 +02:00
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
NotificationURL = callbackServer . GetUri ( ) . AbsoluteUri ,
ItemDesc = "Some description" ,
2018-01-18 12:56:55 +01:00
FullNotifications = true ,
ExtendedNotifications = true
2017-10-27 10:53:04 +02:00
} ) ;
2020-04-05 13:48:00 +02:00
BitcoinUrlBuilder url = new BitcoinUrlBuilder ( invoice . PaymentUrls . BIP21 ,
tester . NetworkProvider . BTC . NBitcoinNetwork ) ;
2019-01-06 07:04:30 +01:00
bool receivedPayment = false ;
bool paid = false ;
bool confirmed = false ;
bool completed = false ;
while ( ! completed | | ! confirmed )
2017-10-27 10:53:04 +02:00
{
2019-01-06 07:04:30 +01:00
var request = await callbackServer . GetNextRequest ( ) ;
if ( request . ContainsKey ( "event" ) )
{
var evtName = request [ "event" ] [ "name" ] . Value < string > ( ) ;
switch ( evtName )
{
2019-01-06 10:12:45 +01:00
case InvoiceEvent . Created :
2019-01-06 07:04:30 +01:00
tester . ExplorerNode . SendToAddress ( url . Address , url . Amount ) ;
break ;
2019-01-06 10:12:45 +01:00
case InvoiceEvent . ReceivedPayment :
2019-01-06 07:04:30 +01:00
receivedPayment = true ;
break ;
2019-01-06 10:12:45 +01:00
case InvoiceEvent . PaidInFull :
2019-01-06 07:04:30 +01:00
Assert . True ( receivedPayment ) ;
tester . ExplorerNode . Generate ( 6 ) ;
paid = true ;
break ;
2019-01-06 10:12:45 +01:00
case InvoiceEvent . Confirmed :
2019-01-06 07:04:30 +01:00
Assert . True ( paid ) ;
confirmed = true ;
break ;
2019-01-06 10:12:45 +01:00
case InvoiceEvent . Completed :
2020-04-05 13:48:00 +02:00
Assert . True (
paid ) ; //TODO: Fix, out of order event mean we can receive invoice_confirmed after invoice_complete
2019-01-06 07:04:30 +01:00
completed = true ;
break ;
default :
Assert . False ( true , $"{evtName} was not expected" ) ;
break ;
}
}
}
2020-04-05 13:48:00 +02:00
2017-10-27 10:53:04 +02:00
var invoice2 = acc . BitPay . GetInvoice ( invoice . Id ) ;
Assert . NotNull ( invoice2 ) ;
}
}
}
2019-10-06 15:51:01 +02:00
[Fact(Timeout = TestTimeout)]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2019-10-07 09:04:25 +02:00
public async Task CantPairTwiceWithSamePubkey ( )
2017-10-27 10:53:04 +02:00
{
using ( var tester = ServerTester . Create ( ) )
{
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2017-10-27 10:53:04 +02:00
var acc = tester . NewAccount ( ) ;
acc . Register ( ) ;
2018-04-30 15:28:00 +02:00
acc . CreateStore ( ) ;
var store = acc . GetController < StoresController > ( ) ;
2017-10-27 10:53:04 +02:00
var pairingCode = acc . BitPay . RequestClientAuthorization ( "test" , Facade . Merchant ) ;
2020-04-05 13:48:00 +02:00
Assert . IsType < RedirectToActionResult > ( store . Pair ( pairingCode . ToString ( ) , acc . StoreId ) . GetAwaiter ( )
. GetResult ( ) ) ;
2017-10-27 10:53:04 +02:00
pairingCode = acc . BitPay . RequestClientAuthorization ( "test1" , Facade . Merchant ) ;
2018-04-30 15:28:00 +02:00
acc . CreateStore ( ) ;
var store2 = acc . GetController < StoresController > ( ) ;
2019-10-12 13:35:30 +02:00
await store2 . Pair ( pairingCode . ToString ( ) , store2 . CurrentStore . Id ) ;
2020-04-05 13:48:00 +02:00
Assert . Contains ( nameof ( PairingResult . ReusedKey ) ,
( string ) store2 . TempData [ WellKnownTempData . ErrorMessage ] , StringComparison . CurrentCultureIgnoreCase ) ;
2017-10-27 10:53:04 +02:00
}
}
2019-10-06 15:51:01 +02:00
[Fact(Timeout = TestTimeout)]
2018-11-05 04:14:39 +01:00
[Trait("Integration", "Integration")]
public void CanSolveTheDogesRatesOnKraken ( )
{
var provider = new BTCPayNetworkProvider ( NetworkType . Mainnet ) ;
var factory = CreateBTCPayRateFactory ( ) ;
var fetcher = new RateFetcher ( factory ) ;
Assert . True ( RateRules . TryParse ( "X_X=kraken(X_BTC) * kraken(BTC_X)" , out var rule ) ) ;
2020-06-24 03:34:09 +02:00
foreach ( var pair in new [ ] { "DOGE_USD" , "DOGE_CAD" , "DASH_CAD" , "DASH_USD" , "DASH_EUR" } )
2018-11-05 04:14:39 +01:00
{
2019-03-05 09:09:17 +01:00
var result = fetcher . FetchRate ( CurrencyPair . Parse ( pair ) , rule , default ) . GetAwaiter ( ) . GetResult ( ) ;
2018-11-05 04:14:39 +01:00
Assert . NotNull ( result . BidAsk ) ;
Assert . Empty ( result . Errors ) ;
}
}
2020-04-08 15:40:41 +02:00
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async Task CanUseTorClient ( )
{
using ( var tester = ServerTester . Create ( ) )
{
await tester . StartAsync ( ) ;
2020-04-09 10:38:55 +02:00
var proxy = tester . PayTester . GetService < Socks5HttpProxyServer > ( ) ;
2020-04-09 11:49:30 +02:00
void AssertConnectionDropped ( )
{
TestUtils . Eventually ( ( ) = >
{
Thread . MemoryBarrier ( ) ;
Assert . Equal ( 0 , proxy . ConnectionCount ) ;
} ) ;
}
2020-04-09 10:38:55 +02:00
var httpFactory = tester . PayTester . GetService < IHttpClientFactory > ( ) ;
var client = httpFactory . CreateClient ( PayjoinClient . PayjoinOnionNamedClient ) ;
2020-04-08 15:40:41 +02:00
Assert . NotNull ( client ) ;
var response = await client . GetAsync ( "https://check.torproject.org/" ) ;
response . EnsureSuccessStatusCode ( ) ;
var result = await response . Content . ReadAsStringAsync ( ) ;
Assert . DoesNotContain ( "You are not using Tor." , result ) ;
Assert . Contains ( "Congratulations. This browser is configured to use Tor." , result ) ;
2020-04-09 11:49:30 +02:00
AssertConnectionDropped ( ) ;
2020-04-09 10:38:55 +02:00
response = await client . GetAsync ( "http://explorerzydxu5ecjrkwceayqybizmpjjznk5izmitf2modhcusuqlid.onion/" ) ;
response . EnsureSuccessStatusCode ( ) ;
result = await response . Content . ReadAsStringAsync ( ) ;
Assert . Contains ( "Bitcoin" , result ) ;
2020-04-09 11:49:30 +02:00
AssertConnectionDropped ( ) ;
2020-04-09 10:38:55 +02:00
response = await client . GetAsync ( "http://explorerzydxu5ecjrkwceayqybizmpjjznk5izmitf2modhcusuqlid.onion/" ) ;
response . EnsureSuccessStatusCode ( ) ;
2020-04-09 11:49:30 +02:00
AssertConnectionDropped ( ) ;
2020-04-09 10:38:55 +02:00
client . Dispose ( ) ;
2020-04-09 11:49:30 +02:00
AssertConnectionDropped ( ) ;
2020-04-09 10:38:55 +02:00
client = httpFactory . CreateClient ( PayjoinClient . PayjoinOnionNamedClient ) ;
response = await client . GetAsync ( "http://explorerzydxu5ecjrkwceayqybizmpjjznk5izmitf2modhcusuqlid.onion/" ) ;
response . EnsureSuccessStatusCode ( ) ;
2020-04-09 11:49:30 +02:00
AssertConnectionDropped ( ) ;
Logs . Tester . LogInformation ( "Querying an onion address which can't be found should send http 500" ) ;
response = await client . GetAsync ( "http://dwoduwoi.onion/" ) ;
Assert . Equal ( HttpStatusCode . InternalServerError , response . StatusCode ) ;
AssertConnectionDropped ( ) ;
Logs . Tester . LogInformation ( "Querying valid onion but unreachable should send error 502" ) ;
response = await client . GetAsync ( "http://fastrcl5totos3vekjbqcmgpnias5qytxnaj7gpxtxhubdcnfrkapqad.onion/" ) ;
Assert . Equal ( HttpStatusCode . BadGateway , response . StatusCode ) ;
AssertConnectionDropped ( ) ;
2020-04-08 15:40:41 +02:00
}
}
2019-10-06 15:51:01 +02:00
[Fact(Timeout = TestTimeout)]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2019-08-02 17:42:30 +02:00
public async Task CanRescanWallet ( )
2018-10-26 16:07:39 +02:00
{
using ( var tester = ServerTester . Create ( ) )
{
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2018-10-26 16:07:39 +02:00
var acc = tester . NewAccount ( ) ;
acc . GrantAccess ( ) ;
2020-04-07 12:32:30 +02:00
acc . RegisterDerivationScheme ( "BTC" , ScriptPubKeyType . Segwit ) ;
2018-10-26 16:07:39 +02:00
var btcDerivationScheme = acc . DerivationScheme ;
2018-11-02 06:26:13 +01:00
2019-10-12 13:35:30 +02:00
var walletController = acc . GetController < WalletsController > ( ) ;
2018-10-26 16:07:39 +02:00
2019-12-24 10:11:21 +01:00
var walletId = new WalletId ( acc . StoreId , "BTC" ) ;
2019-10-12 13:35:30 +02:00
acc . IsAdmin = true ;
walletController = acc . GetController < WalletsController > ( ) ;
2020-02-13 06:44:31 +01:00
2020-04-05 13:48:00 +02:00
var rescan =
Assert . IsType < RescanWalletModel > ( Assert
. IsType < ViewResult > ( walletController . WalletRescan ( walletId ) . Result ) . Model ) ;
2018-10-26 16:07:39 +02:00
Assert . True ( rescan . Ok ) ;
Assert . True ( rescan . IsFullySync ) ;
Assert . True ( rescan . IsSupportedByCurrency ) ;
Assert . True ( rescan . IsServerAdmin ) ;
rescan . GapLimit = 100 ;
// Sending a coin
2020-04-05 13:48:00 +02:00
var txId = tester . ExplorerNode . SendToAddress (
btcDerivationScheme . GetDerivation ( new KeyPath ( "0/90" ) ) . ScriptPubKey , Money . Coins ( 1.0 m ) ) ;
2018-10-26 16:07:39 +02:00
tester . ExplorerNode . Generate ( 1 ) ;
2020-04-05 13:48:00 +02:00
var transactions = Assert . IsType < ListTransactionsViewModel > ( Assert
. IsType < ViewResult > ( walletController . WalletTransactions ( walletId ) . Result ) . Model ) ;
2018-10-26 16:07:39 +02:00
Assert . Empty ( transactions . Transactions ) ;
Assert . IsType < RedirectToActionResult > ( walletController . WalletRescan ( walletId , rescan ) . Result ) ;
2018-11-02 06:26:13 +01:00
while ( true )
2018-10-26 16:07:39 +02:00
{
2020-04-05 13:48:00 +02:00
rescan = Assert . IsType < RescanWalletModel > ( Assert
. IsType < ViewResult > ( walletController . WalletRescan ( walletId ) . Result ) . Model ) ;
2018-11-02 06:26:13 +01:00
if ( rescan . Progress = = null & & rescan . LastSuccess ! = null )
2018-10-26 16:07:39 +02:00
{
if ( rescan . LastSuccess . Found = = 0 )
continue ;
// Scan over
break ;
}
else
{
Assert . Null ( rescan . TimeOfScan ) ;
Assert . NotNull ( rescan . RemainingTime ) ;
Assert . NotNull ( rescan . Progress ) ;
Thread . Sleep ( 100 ) ;
}
}
2020-04-05 13:48:00 +02:00
2018-10-26 16:07:39 +02:00
Assert . Null ( rescan . PreviousError ) ;
Assert . NotNull ( rescan . TimeOfScan ) ;
Assert . Equal ( 1 , rescan . LastSuccess . Found ) ;
2020-04-05 13:48:00 +02:00
transactions = Assert . IsType < ListTransactionsViewModel > ( Assert
. IsType < ViewResult > ( walletController . WalletTransactions ( walletId ) . Result ) . Model ) ;
2018-10-26 16:07:39 +02:00
var tx = Assert . Single ( transactions . Transactions ) ;
Assert . Equal ( tx . Id , txId . ToString ( ) ) ;
2019-08-02 17:42:30 +02:00
// Hijack the test to see if we can add label and comments
2020-04-05 13:48:00 +02:00
Assert . IsType < RedirectToActionResult > (
await walletController . ModifyTransaction ( walletId , tx . Id , addcomment : "hello-pouet" ) ) ;
Assert . IsType < RedirectToActionResult > (
await walletController . ModifyTransaction ( walletId , tx . Id , addlabel : "test" ) ) ;
Assert . IsType < RedirectToActionResult > (
await walletController . ModifyTransaction ( walletId , tx . Id , addlabelclick : "test2" ) ) ;
Assert . IsType < RedirectToActionResult > (
await walletController . ModifyTransaction ( walletId , tx . Id , addcomment : "hello" ) ) ;
transactions = Assert . IsType < ListTransactionsViewModel > ( Assert
. IsType < ViewResult > ( walletController . WalletTransactions ( walletId ) . Result ) . Model ) ;
2019-08-02 17:42:30 +02:00
tx = Assert . Single ( transactions . Transactions ) ;
Assert . Equal ( "hello" , tx . Comment ) ;
Assert . Contains ( "test" , tx . Labels . Select ( l = > l . Value ) ) ;
Assert . Contains ( "test2" , tx . Labels . Select ( l = > l . Value ) ) ;
Assert . Equal ( 2 , tx . Labels . GroupBy ( l = > l . Color ) . Count ( ) ) ;
2020-04-05 13:48:00 +02:00
Assert . IsType < RedirectToActionResult > (
await walletController . ModifyTransaction ( walletId , tx . Id , removelabel : "test2" ) ) ;
2019-08-02 17:42:30 +02:00
2020-04-05 13:48:00 +02:00
transactions = Assert . IsType < ListTransactionsViewModel > ( Assert
. IsType < ViewResult > ( walletController . WalletTransactions ( walletId ) . Result ) . Model ) ;
2019-08-02 17:42:30 +02:00
tx = Assert . Single ( transactions . Transactions ) ;
Assert . Equal ( "hello" , tx . Comment ) ;
Assert . Contains ( "test" , tx . Labels . Select ( l = > l . Value ) ) ;
Assert . DoesNotContain ( "test2" , tx . Labels . Select ( l = > l . Value ) ) ;
Assert . Single ( tx . Labels . GroupBy ( l = > l . Color ) ) ;
var walletInfo = await tester . PayTester . GetService < WalletRepository > ( ) . GetWalletInfo ( walletId ) ;
Assert . Single ( walletInfo . LabelColors ) ; // the test2 color should have been removed
2018-10-26 16:07:39 +02:00
}
}
2019-10-06 15:51:01 +02:00
[Fact(Timeout = TestTimeout)]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2019-10-07 09:04:25 +02:00
public async Task CanListInvoices ( )
2018-05-06 06:16:39 +02:00
{
using ( var tester = ServerTester . Create ( ) )
{
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2018-05-06 06:16:39 +02:00
var acc = tester . NewAccount ( ) ;
acc . GrantAccess ( ) ;
acc . RegisterDerivationScheme ( "BTC" ) ;
// First we try payment with a merchant having only BTC
2020-04-05 13:48:00 +02:00
var invoice = acc . BitPay . CreateInvoice (
new Invoice ( )
{
Price = 500 ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
2018-05-13 08:09:17 +02:00
2018-05-06 06:16:39 +02:00
var cashCow = tester . ExplorerNode ;
var invoiceAddress = BitcoinAddress . Create ( invoice . CryptoInfo [ 0 ] . Address , cashCow . Network ) ;
var firstPayment = invoice . CryptoInfo [ 0 ] . TotalDue - Money . Satoshis ( 10 ) ;
cashCow . SendToAddress ( invoiceAddress , firstPayment ) ;
2019-01-05 19:47:39 +01:00
TestUtils . Eventually ( ( ) = >
2018-05-06 06:16:39 +02:00
{
invoice = acc . BitPay . GetInvoice ( invoice . Id ) ;
Assert . Equal ( firstPayment , invoice . CryptoInfo [ 0 ] . Paid ) ;
} ) ;
AssertSearchInvoice ( acc , true , invoice . Id , $"storeid:{acc.StoreId}" ) ;
AssertSearchInvoice ( acc , false , invoice . Id , $"storeid:blah" ) ;
AssertSearchInvoice ( acc , true , invoice . Id , $"{invoice.Id}" ) ;
AssertSearchInvoice ( acc , true , invoice . Id , $"exceptionstatus:paidPartial" ) ;
AssertSearchInvoice ( acc , false , invoice . Id , $"exceptionstatus:paidOver" ) ;
AssertSearchInvoice ( acc , true , invoice . Id , $"unusual:true" ) ;
AssertSearchInvoice ( acc , false , invoice . Id , $"unusual:false" ) ;
2019-04-26 01:13:17 +02:00
var time = invoice . InvoiceTime ;
AssertSearchInvoice ( acc , true , invoice . Id , $"startdate:{time.ToString(" yyyy - MM - dd HH : mm : ss ")}" ) ;
AssertSearchInvoice ( acc , true , invoice . Id , $"enddate:{time.ToStringLowerInvariant()}" ) ;
2020-04-05 13:48:00 +02:00
AssertSearchInvoice ( acc , false , invoice . Id ,
$"startdate:{time.AddSeconds(1).ToString(" yyyy - MM - dd HH : mm : ss ")}" ) ;
AssertSearchInvoice ( acc , false , invoice . Id ,
$"enddate:{time.AddSeconds(-1).ToString(" yyyy - MM - dd HH : mm : ss ")}" ) ;
2018-05-06 06:16:39 +02:00
}
}
2020-06-15 07:53:12 +02:00
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async Task CanListNotifications ( )
{
using ( var tester = ServerTester . Create ( ) )
{
await tester . StartAsync ( ) ;
var acc = tester . NewAccount ( ) ;
acc . GrantAccess ( true ) ;
acc . RegisterDerivationScheme ( "BTC" ) ;
const string newVersion = "1.0.4.4" ;
var ctrl = acc . GetController < NotificationsController > ( ) ;
var resp = await ctrl . Generate ( newVersion ) ;
var vm = Assert . IsType < Models . NotificationViewModels . IndexViewModel > (
2020-06-26 13:52:39 +02:00
Assert . IsType < ViewResult > ( ctrl . Index ( ) ) . Model ) ;
2020-06-15 07:53:12 +02:00
Assert . True ( vm . Skip = = 0 ) ;
Assert . True ( vm . Count = = 50 ) ;
Assert . True ( vm . Total = = 1 ) ;
Assert . True ( vm . Items . Count = = 1 ) ;
var fn = vm . Items . First ( ) ;
2020-06-15 07:57:20 +02:00
var now = DateTimeOffset . UtcNow ;
Assert . True ( fn . Created > = now . AddSeconds ( - 3 ) ) ;
Assert . True ( fn . Created < = now ) ;
2020-06-15 07:53:12 +02:00
Assert . Equal ( $"New version {newVersion} released!" , fn . Body ) ;
Assert . Equal ( $"https://github.com/btcpayserver/btcpayserver/releases/tag/v{newVersion}" , fn . ActionLink ) ;
2020-06-15 07:57:20 +02:00
Assert . False ( fn . Seen ) ;
2020-06-15 07:53:12 +02:00
}
}
2018-05-29 17:12:07 +02:00
[Fact]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2019-10-07 09:04:25 +02:00
public async Task CanGetRates ( )
2018-05-29 17:12:07 +02:00
{
using ( var tester = ServerTester . Create ( ) )
{
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2018-05-29 17:12:07 +02:00
var acc = tester . NewAccount ( ) ;
acc . GrantAccess ( ) ;
acc . RegisterDerivationScheme ( "BTC" ) ;
var rateController = acc . GetController < RateController > ( ) ;
2020-04-05 13:48:00 +02:00
var GetBaseCurrencyRatesResult = JObject . Parse ( ( ( JsonResult ) rateController
. GetBaseCurrencyRates ( "BTC" , default )
2018-07-30 16:22:26 +02:00
. GetAwaiter ( ) . GetResult ( ) ) . Value . ToJson ( ) ) . ToObject < DataWrapper < Rate [ ] > > ( ) ;
2018-05-29 17:12:07 +02:00
Assert . NotNull ( GetBaseCurrencyRatesResult ) ;
Assert . NotNull ( GetBaseCurrencyRatesResult . Data ) ;
2019-12-24 10:11:21 +01:00
var rate = Assert . Single ( GetBaseCurrencyRatesResult . Data ) ;
Assert . Equal ( "BTC" , rate . Code ) ;
2018-05-29 17:12:07 +02:00
2019-10-12 13:35:30 +02:00
var GetRatesResult = JObject . Parse ( ( ( JsonResult ) rateController . GetRates ( null , default )
2018-07-30 16:22:26 +02:00
. GetAwaiter ( ) . GetResult ( ) ) . Value . ToJson ( ) ) . ToObject < DataWrapper < Rate [ ] > > ( ) ;
2019-03-11 10:39:21 +01:00
// We don't have any default currencies, so this should be failing
Assert . Null ( GetRatesResult ? . Data ) ;
var store = acc . GetController < StoresController > ( ) ;
2020-01-13 14:20:45 +01:00
var ratesVM = ( RatesViewModel ) ( Assert . IsType < ViewResult > ( store . Rates ( ) ) . Model ) ;
2019-03-11 10:39:21 +01:00
ratesVM . DefaultCurrencyPairs = "BTC_USD,LTC_USD" ;
2020-01-13 14:20:45 +01:00
await store . Rates ( ratesVM ) ;
2019-03-11 10:39:21 +01:00
store = acc . GetController < StoresController > ( ) ;
rateController = acc . GetController < RateController > ( ) ;
2019-10-12 13:35:30 +02:00
GetRatesResult = JObject . Parse ( ( ( JsonResult ) rateController . GetRates ( null , default )
2019-03-11 10:39:21 +01:00
. GetAwaiter ( ) . GetResult ( ) ) . Value . ToJson ( ) ) . ToObject < DataWrapper < Rate [ ] > > ( ) ;
// Now we should have a result
2018-05-29 17:12:07 +02:00
Assert . NotNull ( GetRatesResult ) ;
Assert . NotNull ( GetRatesResult . Data ) ;
Assert . Equal ( 2 , GetRatesResult . Data . Length ) ;
2020-04-05 13:48:00 +02:00
var GetCurrencyPairRateResult = JObject . Parse ( ( ( JsonResult ) rateController
. GetCurrencyPairRate ( "BTC" , "LTC" , default )
2018-07-30 16:22:26 +02:00
. GetAwaiter ( ) . GetResult ( ) ) . Value . ToJson ( ) ) . ToObject < DataWrapper < Rate > > ( ) ;
2018-05-29 17:12:07 +02:00
Assert . NotNull ( GetCurrencyPairRateResult ) ;
Assert . NotNull ( GetCurrencyPairRateResult . Data ) ;
Assert . Equal ( "LTC" , GetCurrencyPairRateResult . Data . Code ) ;
2018-10-15 11:11:20 +02:00
// Should be OK because the request is signed, so we can know the store
var rates = acc . BitPay . GetRates ( ) ;
HttpClient client = new HttpClient ( ) ;
// Unauthentified requests should also be ok
2020-04-05 13:48:00 +02:00
var response =
await client . GetAsync ( $"http://127.0.0.1:{tester.PayTester.Port}/api/rates?storeId={acc.StoreId}" ) ;
2019-10-12 13:35:30 +02:00
response . EnsureSuccessStatusCode ( ) ;
2020-04-05 13:48:00 +02:00
response = await client . GetAsync (
$"http://127.0.0.1:{tester.PayTester.Port}/rates?storeId={acc.StoreId}" ) ;
2018-10-15 11:11:20 +02:00
response . EnsureSuccessStatusCode ( ) ;
2018-05-29 17:12:07 +02:00
}
}
2018-05-06 06:16:39 +02:00
private void AssertSearchInvoice ( TestAccount acc , bool expected , string invoiceId , string filter )
{
2020-04-05 13:48:00 +02:00
var result =
( Models . InvoicingModels . InvoicesModel ) ( ( ViewResult ) acc . GetController < InvoiceController > ( )
2020-07-30 16:10:10 +02:00
. ListInvoices ( new InvoicesModel { SearchTerm = filter } ) . Result ) . Model ;
2018-05-06 06:16:39 +02:00
Assert . Equal ( expected , result . Invoices . Any ( i = > i . InvoiceId = = invoiceId ) ) ;
}
2020-04-05 13:48:00 +02:00
// [Fact(Timeout = TestTimeout)]
[Fact()]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2019-10-07 09:04:25 +02:00
public async Task CanRBFPayment ( )
2017-11-06 09:31:02 +01:00
{
using ( var tester = ServerTester . Create ( ) )
{
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2017-11-06 09:31:02 +01:00
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
2018-02-23 07:21:42 +01:00
user . RegisterDerivationScheme ( "BTC" ) ;
2019-01-04 16:37:09 +01:00
user . SetNetworkFeeMode ( NetworkFeeMode . Always ) ;
2020-04-05 13:48:00 +02:00
var invoice =
2020-06-24 03:34:09 +02:00
user . BitPay . CreateInvoice ( new Invoice ( ) { Price = 5000.0 m , Currency = "USD" } , Facade . Merchant ) ;
2018-02-19 07:09:05 +01:00
var payment1 = invoice . BtcDue + Money . Coins ( 0.0001 m ) ;
var payment2 = invoice . BtcDue ;
2018-10-28 15:43:48 +01:00
2017-11-06 09:31:02 +01:00
var tx1 = new uint256 ( tester . ExplorerNode . SendCommand ( "sendtoaddress" , new object [ ]
{
2020-04-05 13:48:00 +02:00
invoice . BitcoinAddress , payment1 . ToString ( ) , null , //comment
2017-11-06 09:31:02 +01:00
null , //comment_to
false , //subtractfeefromamount
true , //replaceable
} ) . ResultString ) ;
2020-04-05 13:48:00 +02:00
Logs . Tester . LogInformation (
$"Let's send a first payment of {payment1} for the {invoice.BtcDue} invoice ({tx1})" ) ;
var invoiceAddress =
BitcoinAddress . Create ( invoice . BitcoinAddress , user . SupportedNetwork . NBitcoinNetwork ) ;
2017-11-06 09:31:02 +01:00
2018-10-28 15:43:48 +01:00
Logs . Tester . LogInformation ( $"The invoice should be paidOver" ) ;
2019-01-05 19:47:39 +01:00
TestUtils . Eventually ( ( ) = >
2017-11-06 09:31:02 +01:00
{
invoice = user . BitPay . GetInvoice ( invoice . Id ) ;
Assert . Equal ( payment1 , invoice . BtcPaid ) ;
2018-02-19 07:09:05 +01:00
Assert . Equal ( "paid" , invoice . Status ) ;
Assert . Equal ( "paidOver" , invoice . ExceptionStatus . ToString ( ) ) ;
2020-04-05 13:48:00 +02:00
invoiceAddress =
BitcoinAddress . Create ( invoice . BitcoinAddress , user . SupportedNetwork . NBitcoinNetwork ) ;
2017-11-06 09:31:02 +01:00
} ) ;
var tx = tester . ExplorerNode . GetRawTransaction ( new uint256 ( tx1 ) ) ;
foreach ( var input in tx . Inputs )
{
input . ScriptSig = Script . Empty ; //Strip signatures
}
2020-04-05 13:48:00 +02:00
2017-11-06 09:31:02 +01:00
var output = tx . Outputs . First ( o = > o . Value = = payment1 ) ;
output . Value = payment2 ;
output . ScriptPubKey = invoiceAddress . ScriptPubKey ;
2018-10-28 15:43:48 +01:00
2018-11-02 06:26:13 +01:00
using ( var cts = new CancellationTokenSource ( 10000 ) )
2018-11-21 12:41:51 +01:00
using ( var listener = tester . ExplorerClient . CreateWebsocketNotificationSession ( ) )
2018-10-28 15:43:48 +01:00
{
listener . ListenAllDerivationSchemes ( ) ;
var replaced = tester . ExplorerNode . SignRawTransaction ( tx ) ;
2018-10-31 08:57:31 +01:00
Thread . Sleep ( 1000 ) ; // Make sure the replacement has a different timestamp
2018-10-28 15:43:48 +01:00
var tx2 = tester . ExplorerNode . SendRawTransaction ( replaced ) ;
2020-04-05 13:48:00 +02:00
Logs . Tester . LogInformation (
$"Let's RBF with a payment of {payment2} ({tx2}), waiting for NBXplorer to pick it up" ) ;
Assert . Equal ( tx2 ,
( ( NewTransactionEvent ) listener . NextEvent ( cts . Token ) ) . TransactionData . TransactionHash ) ;
2018-10-28 15:43:48 +01:00
}
2020-04-05 13:48:00 +02:00
2018-10-28 15:43:48 +01:00
Logs . Tester . LogInformation ( $"The invoice should now not be paidOver anymore" ) ;
2019-01-05 19:47:39 +01:00
TestUtils . Eventually ( ( ) = >
2017-11-06 09:31:02 +01:00
{
invoice = user . BitPay . GetInvoice ( invoice . Id ) ;
Assert . Equal ( payment2 , invoice . BtcPaid ) ;
2018-02-19 07:09:05 +01:00
Assert . Equal ( "False" , invoice . ExceptionStatus . ToString ( ) ) ;
2017-11-06 09:31:02 +01:00
} ) ;
2020-03-11 12:46:37 +01:00
2020-04-05 13:48:00 +02:00
Logs . Tester . LogInformation (
$"Let's test out rbf payments where the payment gets sent elsehwere instead" ) ;
var invoice2 =
2020-06-24 03:34:09 +02:00
user . BitPay . CreateInvoice ( new Invoice ( ) { Price = 0.01 m , Currency = "BTC" } , Facade . Merchant ) ;
2020-03-11 12:46:37 +01:00
2020-04-05 13:48:00 +02:00
var invoice2Address =
BitcoinAddress . Create ( invoice2 . BitcoinAddress , user . SupportedNetwork . NBitcoinNetwork ) ;
uint256 invoice2tx1Id =
await tester . ExplorerNode . SendToAddressAsync ( invoice2Address , invoice2 . BtcDue , replaceable : true ) ;
2020-03-11 12:46:37 +01:00
Transaction invoice2Tx1 = null ;
TestUtils . Eventually ( ( ) = >
{
invoice2 = user . BitPay . GetInvoice ( invoice2 . Id ) ;
Assert . Equal ( "paid" , invoice2 . Status ) ;
invoice2Tx1 = tester . ExplorerNode . GetRawTransaction ( new uint256 ( invoice2tx1Id ) ) ;
} ) ;
var invoice2Tx2 = invoice2Tx1 . Clone ( ) ;
foreach ( var input in invoice2Tx2 . Inputs )
{
input . ScriptSig = Script . Empty ; //Strip signatures
input . WitScript = WitScript . Empty ; //Strip signatures
}
output = invoice2Tx2 . Outputs . First ( o = >
o . ScriptPubKey = = invoice2Address . ScriptPubKey ) ;
output . Value - = new Money ( 10_000 , MoneyUnit . Satoshi ) ;
output . ScriptPubKey = new Key ( ) . ScriptPubKey ;
invoice2Tx2 = await tester . ExplorerNode . SignRawTransactionAsync ( invoice2Tx2 ) ;
await tester . ExplorerNode . SendRawTransactionAsync ( invoice2Tx2 ) ;
tester . ExplorerNode . Generate ( 1 ) ;
await TestUtils . EventuallyAsync ( async ( ) = >
{
var i = await tester . PayTester . InvoiceRepository . GetInvoice ( invoice2 . Id ) ;
Assert . Equal ( InvoiceStatus . New , i . Status ) ;
Assert . Single ( i . GetPayments ( ) ) ;
Assert . False ( i . GetPayments ( ) . First ( ) . Accounted ) ;
} ) ;
2020-04-05 13:48:00 +02:00
Logs . Tester . LogInformation (
$"Let's test if we can RBF a normal payment without adding fees to the invoice" ) ;
user . SetNetworkFeeMode ( NetworkFeeMode . MultiplePaymentsOnly ) ;
2020-06-24 03:34:09 +02:00
invoice = user . BitPay . CreateInvoice ( new Invoice ( ) { Price = 5000.0 m , Currency = "USD" } , Facade . Merchant ) ;
2020-04-05 13:48:00 +02:00
payment1 = invoice . BtcDue ;
tx1 = new uint256 ( tester . ExplorerNode . SendCommand ( "sendtoaddress" , new object [ ]
{
invoice . BitcoinAddress , payment1 . ToString ( ) , null , //comment
null , //comment_to
false , //subtractfeefromamount
true , //replaceable
} ) . ResultString ) ;
Logs . Tester . LogInformation ( $"Paid {tx1}" ) ;
TestUtils . Eventually ( ( ) = >
{
invoice = user . BitPay . GetInvoice ( invoice . Id ) ;
Assert . Equal ( payment1 , invoice . BtcPaid ) ;
Assert . Equal ( "paid" , invoice . Status ) ;
Assert . Equal ( "False" , invoice . ExceptionStatus . ToString ( ) ) ;
}
) ;
var tx1Bump = new uint256 ( tester . ExplorerNode . SendCommand ( "bumpfee" , new object [ ]
{
tx1 . ToString ( ) ,
} ) . Result [ "txid" ] . Value < string > ( ) ) ;
Logs . Tester . LogInformation ( $"Bumped with {tx1Bump}" ) ;
await TestUtils . EventuallyAsync ( async ( ) = >
{
var invoiceEntity = await tester . PayTester . InvoiceRepository . GetInvoice ( invoice . Id ) ;
var btcPayments = invoiceEntity . GetAllBitcoinPaymentData ( ) . ToArray ( ) ;
var payments = invoiceEntity . GetPayments ( ) . ToArray ( ) ;
Assert . Equal ( tx1 , btcPayments [ 0 ] . Outpoint . Hash ) ;
Assert . False ( payments [ 0 ] . Accounted ) ;
Assert . Equal ( tx1Bump , payments [ 1 ] . Outpoint . Hash ) ;
Assert . True ( payments [ 1 ] . Accounted ) ;
Assert . Equal ( 0.0 m , payments [ 1 ] . NetworkFee ) ;
invoice = user . BitPay . GetInvoice ( invoice . Id ) ;
Assert . Equal ( payment1 , invoice . BtcPaid ) ;
Assert . Equal ( "paid" , invoice . Status ) ;
Assert . Equal ( "False" , invoice . ExceptionStatus . ToString ( ) ) ;
}
) ;
2017-11-06 09:31:02 +01:00
}
}
2019-10-06 15:51:01 +02:00
[Fact(Timeout = TestTimeout)]
2018-10-28 12:59:59 +01:00
[Trait("Fast", "Fast")]
2017-12-03 15:35:52 +01:00
public void CanParseFilter ( )
{
2019-04-26 01:13:17 +02:00
var filter = "storeid:abc, status:abed, blabhbalh " ;
2017-12-03 15:35:52 +01:00
var search = new SearchString ( filter ) ;
2019-04-26 01:13:17 +02:00
Assert . Equal ( "storeid:abc, status:abed, blabhbalh" , search . ToString ( ) ) ;
2017-12-03 15:35:52 +01:00
Assert . Equal ( "blabhbalh" , search . TextSearch ) ;
2018-04-26 04:01:59 +02:00
Assert . Single ( search . Filters [ "storeid" ] ) ;
Assert . Single ( search . Filters [ "status" ] ) ;
Assert . Equal ( "abc" , search . Filters [ "storeid" ] . First ( ) ) ;
Assert . Equal ( "abed" , search . Filters [ "status" ] . First ( ) ) ;
2019-04-26 01:13:17 +02:00
filter = "status:abed, status:abed2" ;
2018-04-26 04:01:59 +02:00
search = new SearchString ( filter ) ;
2019-04-26 01:13:17 +02:00
Assert . Equal ( "" , search . TextSearch ) ;
Assert . Equal ( "status:abed, status:abed2" , search . ToString ( ) ) ;
2018-04-26 04:01:59 +02:00
Assert . Throws < KeyNotFoundException > ( ( ) = > search . Filters [ "test" ] ) ;
Assert . Equal ( 2 , search . Filters [ "status" ] . Count ) ;
Assert . Equal ( "abed" , search . Filters [ "status" ] . First ( ) ) ;
Assert . Equal ( "abed2" , search . Filters [ "status" ] . Skip ( 1 ) . First ( ) ) ;
2019-04-26 01:13:17 +02:00
filter = "StartDate:2019-04-25 01:00 AM, hekki" ;
search = new SearchString ( filter ) ;
Assert . Equal ( "2019-04-25 01:00 AM" , search . Filters [ "startdate" ] . First ( ) ) ;
Assert . Equal ( "hekki" , search . TextSearch ) ;
2017-12-03 15:35:52 +01:00
}
2019-10-06 15:51:01 +02:00
[Fact(Timeout = TestTimeout)]
2018-10-28 12:59:59 +01:00
[Trait("Fast", "Fast")]
2018-08-13 02:43:59 +02:00
public void CanParseFingerprint ( )
{
Assert . True ( SSH . SSHFingerprint . TryParse ( "4e343c6fc6cfbf9339c02d06a151e1dd" , out var unused ) ) ;
Assert . Equal ( "4e:34:3c:6f:c6:cf:bf:93:39:c0:2d:06:a1:51:e1:dd" , unused . ToString ( ) ) ;
Assert . True ( SSH . SSHFingerprint . TryParse ( "4e:34:3c:6f:c6:cf:bf:93:39:c0:2d:06:a1:51:e1:dd" , out unused ) ) ;
Assert . True ( SSH . SSHFingerprint . TryParse ( "SHA256:Wl7CdRgT4u5T7yPMsxSrlFP+HIJJWwidGkzphJ8di5w" , out unused ) ) ;
Assert . True ( SSH . SSHFingerprint . TryParse ( "SHA256:Wl7CdRgT4u5T7yPMsxSrlFP+HIJJWwidGkzphJ8di5w=" , out unused ) ) ;
Assert . True ( SSH . SSHFingerprint . TryParse ( "Wl7CdRgT4u5T7yPMsxSrlFP+HIJJWwidGkzphJ8di5w=" , out unused ) ) ;
Assert . Equal ( "SHA256:Wl7CdRgT4u5T7yPMsxSrlFP+HIJJWwidGkzphJ8di5w" , unused . ToString ( ) ) ;
Assert . True ( SSH . SSHFingerprint . TryParse ( "Wl7CdRgT4u5T7yPMsxSrlFP+HIJJWwidGkzphJ8di5w=" , out var f1 ) ) ;
Assert . True ( SSH . SSHFingerprint . TryParse ( "SHA256:Wl7CdRgT4u5T7yPMsxSrlFP+HIJJWwidGkzphJ8di5w" , out var f2 ) ) ;
Assert . Equal ( f1 . ToString ( ) , f2 . ToString ( ) ) ;
}
2019-10-06 15:51:01 +02:00
[Fact(Timeout = TestTimeout)]
2019-02-02 08:12:51 +01:00
[Trait("Integration", "Integration")]
public async void CheckCORSSetOnBitpayAPI ( )
{
using ( var tester = ServerTester . Create ( ) )
{
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2020-06-24 03:34:09 +02:00
foreach ( var req in new [ ] { "invoices/" , "invoices" , "rates" , "tokens" } . Select ( async path = >
{
using ( HttpClient client = new HttpClient ( ) )
{
HttpRequestMessage message = new HttpRequestMessage ( HttpMethod . Options ,
tester . PayTester . ServerUri . AbsoluteUri + path ) ;
message . Headers . Add ( "Access-Control-Request-Headers" , "test" ) ;
var response = await client . SendAsync ( message ) ;
response . EnsureSuccessStatusCode ( ) ;
Assert . True ( response . Headers . TryGetValues ( "Access-Control-Allow-Origin" , out var val ) ) ;
Assert . Equal ( "*" , val . FirstOrDefault ( ) ) ;
Assert . True ( response . Headers . TryGetValues ( "Access-Control-Allow-Headers" , out val ) ) ;
Assert . Equal ( "test" , val . FirstOrDefault ( ) ) ;
}
} ) . ToList ( ) )
2019-02-02 08:12:51 +01:00
{
await req ;
}
2020-04-05 13:48:00 +02:00
2019-02-02 08:12:51 +01:00
HttpClient client2 = new HttpClient ( ) ;
2020-04-05 13:48:00 +02:00
HttpRequestMessage message2 = new HttpRequestMessage ( HttpMethod . Options ,
tester . PayTester . ServerUri . AbsoluteUri + "rates" ) ;
2019-02-02 08:12:51 +01:00
var response2 = await client2 . SendAsync ( message2 ) ;
Assert . True ( response2 . Headers . TryGetValues ( "Access-Control-Allow-Origin" , out var val2 ) ) ;
Assert . Equal ( "*" , val2 . FirstOrDefault ( ) ) ;
}
}
2019-10-06 15:51:01 +02:00
[Fact(Timeout = TestTimeout)]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2019-10-07 09:04:25 +02:00
public async Task TestAccessBitpayAPI ( )
2017-10-27 10:53:04 +02:00
{
using ( var tester = ServerTester . Create ( ) )
{
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2017-10-27 10:53:04 +02:00
var user = tester . NewAccount ( ) ;
Assert . False ( user . BitPay . TestAccess ( Facade . Merchant ) ) ;
user . GrantAccess ( ) ;
2018-02-23 07:21:42 +01:00
user . RegisterDerivationScheme ( "BTC" ) ;
2018-06-06 09:02:37 +02:00
2017-10-27 10:53:04 +02:00
Assert . True ( user . BitPay . TestAccess ( Facade . Merchant ) ) ;
2018-04-29 11:28:04 +02:00
2018-06-06 07:46:41 +02:00
// Test request pairing code client side
var storeController = user . GetController < StoresController > ( ) ;
2020-04-05 13:48:00 +02:00
storeController
2020-06-24 03:34:09 +02:00
. CreateToken ( user . StoreId , new CreateTokenViewModel ( ) { Label = "test2" , StoreId = user . StoreId } )
2020-04-05 13:48:00 +02:00
. GetAwaiter ( ) . GetResult ( ) ;
2018-06-06 07:46:41 +02:00
Assert . NotNull ( storeController . GeneratedPairingCode ) ;
2018-06-06 09:02:37 +02:00
var k = new Key ( ) ;
var bitpay = new Bitpay ( k , tester . PayTester . ServerUri ) ;
2018-06-06 07:46:41 +02:00
bitpay . AuthorizeClient ( new PairingCode ( storeController . GeneratedPairingCode ) ) . Wait ( ) ;
Assert . True ( bitpay . TestAccess ( Facade . Merchant ) ) ;
Assert . True ( bitpay . TestAccess ( Facade . PointOfSale ) ) ;
2018-06-06 09:02:37 +02:00
// Same with new instance
bitpay = new Bitpay ( k , tester . PayTester . ServerUri ) ;
Assert . True ( bitpay . TestAccess ( Facade . Merchant ) ) ;
Assert . True ( bitpay . TestAccess ( Facade . PointOfSale ) ) ;
2018-06-06 07:46:41 +02:00
2018-04-29 11:28:04 +02:00
// Can generate API Key
var repo = tester . PayTester . GetService < TokenRepository > ( ) ;
Assert . Empty ( repo . GetLegacyAPIKeys ( user . StoreId ) . GetAwaiter ( ) . GetResult ( ) ) ;
2020-04-05 13:48:00 +02:00
Assert . IsType < RedirectToActionResult > ( user . GetController < StoresController > ( )
. GenerateAPIKey ( user . StoreId ) . GetAwaiter ( ) . GetResult ( ) ) ;
2018-04-29 11:28:04 +02:00
var apiKey = Assert . Single ( repo . GetLegacyAPIKeys ( user . StoreId ) . GetAwaiter ( ) . GetResult ( ) ) ;
///////
// Generating a new one remove the previous
2020-04-05 13:48:00 +02:00
Assert . IsType < RedirectToActionResult > ( user . GetController < StoresController > ( )
. GenerateAPIKey ( user . StoreId ) . GetAwaiter ( ) . GetResult ( ) ) ;
2018-04-29 11:28:04 +02:00
var apiKey2 = Assert . Single ( repo . GetLegacyAPIKeys ( user . StoreId ) . GetAwaiter ( ) . GetResult ( ) ) ;
Assert . NotEqual ( apiKey , apiKey2 ) ;
////////
apiKey = apiKey2 ;
// Can create an invoice with this new API Key
HttpClient client = new HttpClient ( ) ;
2020-04-05 13:48:00 +02:00
HttpRequestMessage message = new HttpRequestMessage ( HttpMethod . Post ,
tester . PayTester . ServerUri . AbsoluteUri + "invoices" ) ;
message . Headers . Authorization = new System . Net . Http . Headers . AuthenticationHeaderValue ( "Basic" ,
Encoders . Base64 . EncodeData ( Encoders . ASCII . DecodeData ( apiKey ) ) ) ;
2020-06-24 03:34:09 +02:00
var invoice = new Invoice ( ) { Price = 5000.0 m , Currency = "USD" } ;
2020-04-05 13:48:00 +02:00
message . Content = new StringContent ( JsonConvert . SerializeObject ( invoice ) , Encoding . UTF8 ,
"application/json" ) ;
2018-04-29 11:28:04 +02:00
var result = client . SendAsync ( message ) . GetAwaiter ( ) . GetResult ( ) ;
result . EnsureSuccessStatusCode ( ) ;
/////////////////////
2018-12-18 16:28:06 +01:00
2018-12-07 06:34:07 +01:00
// Have error 403 with bad signature
client = new HttpClient ( ) ;
2020-04-05 13:48:00 +02:00
HttpRequestMessage mess =
new HttpRequestMessage ( HttpMethod . Get , tester . PayTester . ServerUri . AbsoluteUri + "tokens" ) ;
2018-12-07 06:34:07 +01:00
mess . Content = new StringContent ( string . Empty , Encoding . UTF8 , "application/json" ) ;
2020-04-05 13:48:00 +02:00
mess . Headers . Add ( "x-signature" ,
"3045022100caa123193afc22ef93d9c6b358debce6897c09dd9869fe6fe029c9cb43623fac022000b90c65c50ba8bbbc6ebee8878abe5659e17b9f2e1b27d95eda4423da5608fe" ) ;
mess . Headers . Add ( "x-identity" ,
"04b4d82095947262dd70f94c0a0e005ec3916e3f5f2181c176b8b22a52db22a8c436c4703f43a9e8884104854a11e1eb30df8fdf116e283807a1f1b8fe4c182b99" ) ;
2018-12-07 06:34:07 +01:00
mess . Method = HttpMethod . Get ;
result = client . SendAsync ( mess ) . GetAwaiter ( ) . GetResult ( ) ;
Assert . Equal ( System . Net . HttpStatusCode . Unauthorized , result . StatusCode ) ;
//
2018-01-07 18:36:41 +01:00
}
}
2018-01-13 14:01:09 +01:00
2019-10-06 15:51:01 +02:00
[Fact(Timeout = TestTimeout)]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2019-10-07 09:04:25 +02:00
public async Task CanUseExchangeSpecificRate ( )
2018-04-15 14:18:51 +02:00
{
using ( var tester = ServerTester . Create ( ) )
{
tester . PayTester . MockRates = false ;
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2018-04-15 14:18:51 +02:00
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
List < decimal > rates = new List < decimal > ( ) ;
2020-01-13 14:20:45 +01:00
rates . Add ( await CreateInvoice ( tester , user , "coingecko" ) ) ;
var bitflyer = await CreateInvoice ( tester , user , "bitflyer" , "JPY" ) ;
var bitflyer2 = await CreateInvoice ( tester , user , "bitflyer" , "JPY" ) ;
2018-04-15 14:29:44 +02:00
Assert . Equal ( bitflyer , bitflyer2 ) ; // Should be equal because cache
rates . Add ( bitflyer ) ;
2018-04-18 09:07:16 +02:00
2018-04-23 09:44:59 +02:00
foreach ( var rate in rates )
2018-04-15 14:18:51 +02:00
{
Assert . Single ( rates . Where ( r = > r = = rate ) ) ;
}
}
}
2020-04-05 13:48:00 +02:00
private static async Task < decimal > CreateInvoice ( ServerTester tester , TestAccount user , string exchange ,
string currency = "USD" )
2018-04-15 14:18:51 +02:00
{
2018-04-29 19:33:42 +02:00
var storeController = user . GetController < StoresController > ( ) ;
2020-01-13 14:20:45 +01:00
var vm = ( RatesViewModel ) ( ( ViewResult ) storeController . Rates ( ) ) . Model ;
2018-04-15 14:18:51 +02:00
vm . PreferredExchange = exchange ;
2020-01-13 14:20:45 +01:00
await storeController . Rates ( vm ) ;
2020-04-05 13:48:00 +02:00
var invoice2 = await user . BitPay . CreateInvoiceAsync (
new Invoice ( )
{
Price = 5000.0 m ,
Currency = currency ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
2018-04-15 14:18:51 +02:00
return invoice2 . CryptoInfo [ 0 ] . Rate ;
}
2018-01-17 07:59:31 +01:00
2019-10-06 15:51:01 +02:00
[Fact(Timeout = TestTimeout)]
2019-03-25 04:59:42 +01:00
[Trait("Integration", "Integration")]
public async Task CanUseAnyoneCanCreateInvoice ( )
{
using ( var tester = ServerTester . Create ( ) )
{
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2019-03-25 04:59:42 +01:00
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
2019-10-12 13:35:30 +02:00
Logs . Tester . LogInformation ( "StoreId without anyone can create invoice = 403" ) ;
2020-04-05 13:48:00 +02:00
var response = await tester . PayTester . HttpClient . SendAsync (
new HttpRequestMessage ( HttpMethod . Post , $"invoices?storeId={user.StoreId}" )
{
Content = new StringContent ( "{\"Price\": 5000, \"currency\": \"USD\"}" , Encoding . UTF8 ,
"application/json" ) ,
} ) ;
2019-10-12 13:35:30 +02:00
Assert . Equal ( 403 , ( int ) response . StatusCode ) ;
2019-03-25 04:59:42 +01:00
2020-04-05 13:48:00 +02:00
Logs . Tester . LogInformation (
"No store without anyone can create invoice = 404 because the bitpay API can't know the storeid" ) ;
response = await tester . PayTester . HttpClient . SendAsync (
new HttpRequestMessage ( HttpMethod . Post , $"invoices" )
{
Content = new StringContent ( "{\"Price\": 5000, \"currency\": \"USD\"}" , Encoding . UTF8 ,
"application/json" ) ,
} ) ;
2019-03-25 04:59:42 +01:00
Assert . Equal ( 404 , ( int ) response . StatusCode ) ;
user . ModifyStore ( s = > s . AnyoneCanCreateInvoice = true ) ;
2019-10-12 13:35:30 +02:00
Logs . Tester . LogInformation ( "Bad store with anyone can create invoice = 403" ) ;
2020-04-05 13:48:00 +02:00
response = await tester . PayTester . HttpClient . SendAsync (
new HttpRequestMessage ( HttpMethod . Post , $"invoices?storeId=badid" )
{
Content = new StringContent ( "{\"Price\": 5000, \"currency\": \"USD\"}" , Encoding . UTF8 ,
"application/json" ) ,
} ) ;
2019-10-12 13:35:30 +02:00
Assert . Equal ( 403 , ( int ) response . StatusCode ) ;
2019-03-25 04:59:42 +01:00
Logs . Tester . LogInformation ( "Good store with anyone can create invoice = 200" ) ;
2020-04-05 13:48:00 +02:00
response = await tester . PayTester . HttpClient . SendAsync (
new HttpRequestMessage ( HttpMethod . Post , $"invoices?storeId={user.StoreId}" )
{
Content = new StringContent ( "{\"Price\": 5000, \"currency\": \"USD\"}" , Encoding . UTF8 ,
"application/json" ) ,
} ) ;
2019-03-25 04:59:42 +01:00
Assert . Equal ( 200 , ( int ) response . StatusCode ) ;
}
}
2019-10-06 15:51:01 +02:00
[Fact(Timeout = TestTimeout)]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2019-10-07 09:04:25 +02:00
public async Task CanTweakRate ( )
2018-01-17 07:59:31 +01:00
{
using ( var tester = ServerTester . Create ( ) )
{
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2018-01-17 07:59:31 +01:00
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
2018-02-23 07:21:42 +01:00
user . RegisterDerivationScheme ( "BTC" ) ;
2018-01-17 07:59:31 +01:00
// First we try payment with a merchant having only BTC
2020-04-05 13:48:00 +02:00
var invoice1 = user . BitPay . CreateInvoice (
new Invoice ( )
{
Price = 5000.0 m ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
2018-08-22 17:24:33 +02:00
Assert . Equal ( Money . Coins ( 1.0 m ) , invoice1 . BtcPrice ) ;
2018-01-17 07:59:31 +01:00
2018-04-29 19:33:42 +02:00
var storeController = user . GetController < StoresController > ( ) ;
2020-01-13 14:20:45 +01:00
var vm = ( RatesViewModel ) ( ( ViewResult ) storeController . Rates ( ) ) . Model ;
2018-08-01 11:38:46 +02:00
Assert . Equal ( 0.0 , vm . Spread ) ;
vm . Spread = 40 ;
2020-01-13 14:20:45 +01:00
await storeController . Rates ( vm ) ;
2018-01-17 07:59:31 +01:00
2020-04-05 13:48:00 +02:00
var invoice2 = user . BitPay . CreateInvoice (
new Invoice ( )
{
Price = 5000.0 m ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
2018-01-17 07:59:31 +01:00
2018-08-22 17:24:33 +02:00
var expectedRate = 5000.0 m * 0.6 m ;
var expectedCoins = invoice2 . Price / expectedRate ;
Assert . True ( invoice2 . BtcPrice . Almost ( Money . Coins ( expectedCoins ) , 0.00001 m ) ) ;
2018-01-17 07:59:31 +01:00
}
}
2019-10-06 15:51:01 +02:00
[Fact(Timeout = TestTimeout)]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2019-10-07 09:04:25 +02:00
public async Task CanModifyRates ( )
2018-05-03 18:46:52 +02:00
{
using ( var tester = ServerTester . Create ( ) )
{
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2018-05-03 18:46:52 +02:00
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
var store = user . GetController < StoresController > ( ) ;
2020-01-13 14:20:45 +01:00
var rateVm = Assert . IsType < RatesViewModel > ( Assert . IsType < ViewResult > ( store . Rates ( ) ) . Model ) ;
2018-05-03 18:46:52 +02:00
Assert . False ( rateVm . ShowScripting ) ;
2020-01-10 14:50:39 +01:00
Assert . Equal ( CoinGeckoRateProvider . CoinGeckoName , rateVm . PreferredExchange ) ;
2018-08-01 11:38:46 +02:00
Assert . Equal ( 0.0 , rateVm . Spread ) ;
2018-05-03 18:46:52 +02:00
Assert . Null ( rateVm . TestRateRules ) ;
rateVm . PreferredExchange = "bitflyer" ;
2020-01-13 14:20:45 +01:00
Assert . IsType < RedirectToActionResult > ( await store . Rates ( rateVm , "Save" ) ) ;
rateVm = Assert . IsType < RatesViewModel > ( Assert . IsType < ViewResult > ( store . Rates ( ) ) . Model ) ;
2018-05-03 18:46:52 +02:00
Assert . Equal ( "bitflyer" , rateVm . PreferredExchange ) ;
rateVm . ScriptTest = "BTC_JPY,BTC_CAD" ;
2018-08-01 11:38:46 +02:00
rateVm . Spread = 10 ;
2018-05-03 18:46:52 +02:00
store = user . GetController < StoresController > ( ) ;
2020-04-05 13:48:00 +02:00
rateVm = Assert . IsType < RatesViewModel > ( Assert . IsType < ViewResult > ( await store . Rates ( rateVm , "Test" ) )
. Model ) ;
2018-05-03 18:46:52 +02:00
Assert . NotNull ( rateVm . TestRateRules ) ;
Assert . Equal ( 2 , rateVm . TestRateRules . Count ) ;
Assert . False ( rateVm . TestRateRules [ 0 ] . Error ) ;
2020-04-05 13:48:00 +02:00
Assert . StartsWith ( "(bitflyer(BTC_JPY)) * (0.9, 1.1) =" , rateVm . TestRateRules [ 0 ] . Rule ,
StringComparison . OrdinalIgnoreCase ) ;
2018-05-03 18:46:52 +02:00
Assert . True ( rateVm . TestRateRules [ 1 ] . Error ) ;
2020-01-13 14:20:45 +01:00
Assert . IsType < RedirectToActionResult > ( await store . Rates ( rateVm , "Save" ) ) ;
2018-05-03 18:46:52 +02:00
Assert . IsType < RedirectToActionResult > ( store . ShowRateRulesPost ( true ) . Result ) ;
2020-01-13 14:20:45 +01:00
Assert . IsType < RedirectToActionResult > ( await store . Rates ( rateVm , "Save" ) ) ;
2018-05-03 18:46:52 +02:00
store = user . GetController < StoresController > ( ) ;
2020-01-13 14:20:45 +01:00
rateVm = Assert . IsType < RatesViewModel > ( Assert . IsType < ViewResult > ( store . Rates ( ) ) . Model ) ;
2019-03-11 10:39:21 +01:00
Assert . Equal ( rateVm . StoreId , user . StoreId ) ;
2018-05-03 18:46:52 +02:00
Assert . Equal ( rateVm . DefaultScript , rateVm . Script ) ;
Assert . True ( rateVm . ShowScripting ) ;
rateVm . ScriptTest = "BTC_JPY" ;
2020-04-05 13:48:00 +02:00
rateVm = Assert . IsType < RatesViewModel > ( Assert . IsType < ViewResult > ( await store . Rates ( rateVm , "Test" ) )
. Model ) ;
2018-05-03 18:46:52 +02:00
Assert . True ( rateVm . ShowScripting ) ;
2020-04-05 13:48:00 +02:00
Assert . Contains ( "(bitflyer(BTC_JPY)) * (0.9, 1.1) = " , rateVm . TestRateRules [ 0 ] . Rule ,
StringComparison . OrdinalIgnoreCase ) ;
2018-05-03 18:46:52 +02:00
rateVm . ScriptTest = "BTC_USD,BTC_CAD,DOGE_USD,DOGE_CAD" ;
rateVm . Script = "DOGE_X = bittrex(DOGE_BTC) * BTC_X;\n" +
"X_CAD = quadrigacx(X_CAD);\n" +
2020-04-05 13:48:00 +02:00
"X_X = coingecko(X_X);" ;
2018-08-01 11:38:46 +02:00
rateVm . Spread = 50 ;
2020-04-05 13:48:00 +02:00
rateVm = Assert . IsType < RatesViewModel > ( Assert . IsType < ViewResult > ( await store . Rates ( rateVm , "Test" ) )
. Model ) ;
2018-05-03 18:46:52 +02:00
Assert . True ( rateVm . TestRateRules . All ( t = > ! t . Error ) ) ;
2020-01-13 14:20:45 +01:00
Assert . IsType < RedirectToActionResult > ( await store . Rates ( rateVm , "Save" ) ) ;
2018-05-03 18:46:52 +02:00
store = user . GetController < StoresController > ( ) ;
2020-01-13 14:20:45 +01:00
rateVm = Assert . IsType < RatesViewModel > ( Assert . IsType < ViewResult > ( store . Rates ( ) ) . Model ) ;
2018-08-01 11:38:46 +02:00
Assert . Equal ( 50 , rateVm . Spread ) ;
2018-05-03 18:46:52 +02:00
Assert . True ( rateVm . ShowScripting ) ;
Assert . Contains ( "DOGE_X" , rateVm . Script , StringComparison . OrdinalIgnoreCase ) ;
}
}
2020-05-31 12:18:29 +02:00
[Fact]
[Trait("Fast", "Fast")]
public void HasCurrencyDataForNetworks ( )
{
var btcPayNetworkProvider = new BTCPayNetworkProvider ( NetworkType . Regtest ) ;
foreach ( var network in btcPayNetworkProvider . GetAll ( ) )
{
var cd = CurrencyNameTable . Instance . GetCurrencyData ( network . CryptoCode , false ) ;
Assert . NotNull ( cd ) ;
Assert . Equal ( network . Divisibility , cd . Divisibility ) ;
Assert . True ( cd . Crypto ) ;
}
}
2018-01-11 09:29:48 +01:00
2018-03-25 18:57:44 +02:00
[Fact]
2018-10-28 12:59:59 +01:00
[Trait("Fast", "Fast")]
2018-03-25 18:57:44 +02:00
public void CanParseCurrencyValue ( )
{
Assert . True ( CurrencyValue . TryParse ( "1.50USD" , out var result ) ) ;
Assert . Equal ( "1.50 USD" , result . ToString ( ) ) ;
Assert . True ( CurrencyValue . TryParse ( "1.50 USD" , out result ) ) ;
Assert . Equal ( "1.50 USD" , result . ToString ( ) ) ;
Assert . True ( CurrencyValue . TryParse ( "1.50 usd" , out result ) ) ;
Assert . Equal ( "1.50 USD" , result . ToString ( ) ) ;
Assert . True ( CurrencyValue . TryParse ( "1 usd" , out result ) ) ;
Assert . Equal ( "1 USD" , result . ToString ( ) ) ;
Assert . True ( CurrencyValue . TryParse ( "1usd" , out result ) ) ;
Assert . Equal ( "1 USD" , result . ToString ( ) ) ;
Assert . True ( CurrencyValue . TryParse ( "1.501 usd" , out result ) ) ;
Assert . Equal ( "1.50 USD" , result . ToString ( ) ) ;
Assert . False ( CurrencyValue . TryParse ( "1.501 WTFF" , out result ) ) ;
Assert . False ( CurrencyValue . TryParse ( "1,501 usd" , out result ) ) ;
Assert . False ( CurrencyValue . TryParse ( "1.501" , out result ) ) ;
}
2019-12-26 06:22:36 +01:00
[Fact]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2018-10-28 14:46:03 +01:00
public async Task CanSetPaymentMethodLimits ( )
2018-04-03 10:39:28 +02:00
{
using ( var tester = ServerTester . Create ( ) )
{
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2018-04-03 10:39:28 +02:00
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
2020-04-05 13:48:00 +02:00
var vm = Assert . IsType < CheckoutExperienceViewModel > ( Assert
. IsType < ViewResult > ( user . GetController < StoresController > ( ) . CheckoutExperience ( ) ) . Model ) ;
2018-04-03 10:39:28 +02:00
vm . OnChainMinValue = "5 USD" ;
2020-04-05 13:48:00 +02:00
Assert . IsType < RedirectToActionResult > ( user . GetController < StoresController > ( ) . CheckoutExperience ( vm )
. Result ) ;
2018-04-03 10:39:28 +02:00
2020-04-05 13:48:00 +02:00
var invoice = user . BitPay . CreateInvoice (
new Invoice ( )
{
Price = 5.5 m ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
2018-04-03 10:39:28 +02:00
Assert . Single ( invoice . CryptoInfo ) ;
2019-12-24 10:11:21 +01:00
Assert . Equal ( PaymentTypes . BTCLike . ToString ( ) , invoice . CryptoInfo [ 0 ] . PaymentType ) ;
}
}
2018-04-03 10:39:28 +02:00
2019-12-24 10:11:21 +01:00
[Fact(Timeout = 60 * 2 * 1000)]
[Trait("Integration", "Integration")]
[Trait("Lightning", "Lightning")]
public async Task CanSetPaymentMethodLimitsLightning ( )
{
using ( var tester = ServerTester . Create ( ) )
{
tester . ActivateLightning ( ) ;
await tester . StartAsync ( ) ;
await tester . EnsureChannelsSetup ( ) ;
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
user . RegisterLightningNode ( "BTC" , LightningConnectionType . Charge ) ;
2020-04-05 13:48:00 +02:00
var vm = Assert . IsType < CheckoutExperienceViewModel > ( Assert
. IsType < ViewResult > ( user . GetController < StoresController > ( ) . CheckoutExperience ( ) ) . Model ) ;
2019-12-24 10:11:21 +01:00
vm . LightningMaxValue = "2 USD" ;
2020-04-05 13:48:00 +02:00
Assert . IsType < RedirectToActionResult > ( user . GetController < StoresController > ( ) . CheckoutExperience ( vm )
. Result ) ;
2019-12-24 10:11:21 +01:00
2020-04-05 13:48:00 +02:00
var invoice = user . BitPay . CreateInvoice (
new Invoice ( )
{
Price = 1.5 m ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
2018-04-03 10:39:28 +02:00
Assert . Single ( invoice . CryptoInfo ) ;
2019-12-24 10:11:21 +01:00
Assert . Equal ( PaymentTypes . LightningLike . ToString ( ) , invoice . CryptoInfo [ 0 ] . PaymentType ) ;
2018-04-03 10:39:28 +02:00
}
}
2019-01-16 11:14:45 +01:00
[Fact]
[Trait("Fast", "Fast")]
2019-04-05 08:16:36 +02:00
public async Task CanScheduleBackgroundTasks ( )
2019-01-16 11:14:45 +01:00
{
BackgroundJobClient client = new BackgroundJobClient ( ) ;
MockDelay mockDelay = new MockDelay ( ) ;
client . Delay = mockDelay ;
bool [ ] jobs = new bool [ 4 ] ;
Logs . Tester . LogInformation ( "Start Job[0] in 5 sec" ) ;
2020-04-05 13:48:00 +02:00
client . Schedule ( ( _ ) = >
{
Logs . Tester . LogInformation ( "Job[0]" ) ;
jobs [ 0 ] = true ;
return Task . CompletedTask ;
} , TimeSpan . FromSeconds ( 5.0 ) ) ;
2019-01-16 11:14:45 +01:00
Logs . Tester . LogInformation ( "Start Job[1] in 2 sec" ) ;
2020-04-05 13:48:00 +02:00
client . Schedule ( ( _ ) = >
{
Logs . Tester . LogInformation ( "Job[1]" ) ;
jobs [ 1 ] = true ;
return Task . CompletedTask ;
} , TimeSpan . FromSeconds ( 2.0 ) ) ;
2019-01-16 11:14:45 +01:00
Logs . Tester . LogInformation ( "Start Job[2] fails in 6 sec" ) ;
2020-04-05 13:48:00 +02:00
client . Schedule ( ( _ ) = >
{
jobs [ 2 ] = true ;
throw new Exception ( "Job[2]" ) ;
} , TimeSpan . FromSeconds ( 6.0 ) ) ;
2019-01-16 11:14:45 +01:00
Logs . Tester . LogInformation ( "Start Job[3] starts in in 7 sec" ) ;
2020-04-05 13:48:00 +02:00
client . Schedule ( ( _ ) = >
{
Logs . Tester . LogInformation ( "Job[3]" ) ;
jobs [ 3 ] = true ;
return Task . CompletedTask ;
} , TimeSpan . FromSeconds ( 7.0 ) ) ;
2019-01-16 11:14:45 +01:00
2020-06-24 03:34:09 +02:00
Assert . True ( new [ ] { false , false , false , false } . SequenceEqual ( jobs ) ) ;
2019-01-16 11:14:45 +01:00
CancellationTokenSource cts = new CancellationTokenSource ( ) ;
var processing = client . ProcessJobs ( cts . Token ) ;
Assert . Equal ( 4 , client . GetExecutingCount ( ) ) ;
var waitJobsFinish = client . WaitAllRunning ( default ) ;
2019-04-05 08:16:36 +02:00
await mockDelay . Advance ( TimeSpan . FromSeconds ( 2.0 ) ) ;
2020-06-24 03:34:09 +02:00
Assert . True ( new [ ] { false , true , false , false } . SequenceEqual ( jobs ) ) ;
2019-01-16 11:14:45 +01:00
2019-04-05 08:16:36 +02:00
await mockDelay . Advance ( TimeSpan . FromSeconds ( 3.0 ) ) ;
2020-06-24 03:34:09 +02:00
Assert . True ( new [ ] { true , true , false , false } . SequenceEqual ( jobs ) ) ;
2019-01-16 11:14:45 +01:00
2019-04-05 08:16:36 +02:00
await mockDelay . Advance ( TimeSpan . FromSeconds ( 1.0 ) ) ;
2020-06-24 03:34:09 +02:00
Assert . True ( new [ ] { true , true , true , false } . SequenceEqual ( jobs ) ) ;
2019-01-16 11:14:45 +01:00
Assert . Equal ( 1 , client . GetExecutingCount ( ) ) ;
2019-04-05 08:16:36 +02:00
Assert . False ( waitJobsFinish . Wait ( 1 ) ) ;
2019-01-16 11:14:45 +01:00
Assert . False ( waitJobsFinish . IsCompletedSuccessfully ) ;
2019-04-05 08:16:36 +02:00
await mockDelay . Advance ( TimeSpan . FromSeconds ( 1.0 ) ) ;
2020-06-24 03:34:09 +02:00
Assert . True ( new [ ] { true , true , true , true } . SequenceEqual ( jobs ) ) ;
2019-01-16 11:14:45 +01:00
2019-04-05 08:16:36 +02:00
await waitJobsFinish ;
2019-01-16 11:14:45 +01:00
Assert . True ( waitJobsFinish . IsCompletedSuccessfully ) ;
Assert . True ( ! waitJobsFinish . IsFaulted ) ;
Assert . Equal ( 0 , client . GetExecutingCount ( ) ) ;
bool jobExecuted = false ;
Logs . Tester . LogInformation ( "This job will be cancelled" ) ;
2020-04-05 13:48:00 +02:00
client . Schedule ( ( _ ) = >
{
jobExecuted = true ;
return Task . CompletedTask ;
} , TimeSpan . FromSeconds ( 1.0 ) ) ;
2019-04-05 08:16:36 +02:00
await mockDelay . Advance ( TimeSpan . FromSeconds ( 0.5 ) ) ;
2019-01-16 11:14:45 +01:00
Assert . False ( jobExecuted ) ;
2019-04-05 08:16:36 +02:00
TestUtils . Eventually ( ( ) = > Assert . Equal ( 1 , client . GetExecutingCount ( ) ) ) ;
2019-01-16 11:14:45 +01:00
waitJobsFinish = client . WaitAllRunning ( default ) ;
Assert . False ( waitJobsFinish . Wait ( 100 ) ) ;
cts . Cancel ( ) ;
2019-04-05 08:16:36 +02:00
await waitJobsFinish ;
Assert . True ( waitJobsFinish . Wait ( 1 ) ) ;
2019-01-16 11:14:45 +01:00
Assert . True ( waitJobsFinish . IsCompletedSuccessfully ) ;
2019-04-05 08:16:36 +02:00
Assert . False ( waitJobsFinish . IsFaulted ) ;
2019-01-16 11:14:45 +01:00
Assert . False ( jobExecuted ) ;
2019-04-05 08:16:36 +02:00
await mockDelay . Advance ( TimeSpan . FromSeconds ( 1.0 ) ) ;
2019-01-16 11:14:45 +01:00
Assert . False ( jobExecuted ) ;
Assert . Equal ( 0 , client . GetExecutingCount ( ) ) ;
2019-04-05 08:31:34 +02:00
await Assert . ThrowsAnyAsync < OperationCanceledException > ( async ( ) = > await processing ) ;
2019-01-16 11:14:45 +01:00
Assert . True ( processing . IsCanceled ) ;
Assert . True ( client . WaitAllRunning ( default ) . Wait ( 100 ) ) ;
}
2019-10-06 15:49:28 +02:00
[Fact(Timeout = TestTimeout)]
2018-11-27 07:13:09 +01:00
[Trait("Fast", "Fast")]
public void PosDataParser_ParsesCorrectly ( )
{
var testCases =
2019-01-26 05:26:49 +01:00
new List < ( string input , Dictionary < string , object > expectedOutput ) > ( )
2018-11-27 07:13:09 +01:00
{
2020-04-05 13:48:00 +02:00
{ ( null , new Dictionary < string , object > ( ) ) } ,
2019-01-26 05:26:49 +01:00
{ ( "" , new Dictionary < string , object > ( ) ) } ,
{ ( "{}" , new Dictionary < string , object > ( ) ) } ,
2020-04-05 13:48:00 +02:00
{ ( "non-json-content" , new Dictionary < string , object > ( ) { { string . Empty , "non-json-content" } } ) } ,
{ ( "[1,2,3]" , new Dictionary < string , object > ( ) { { string . Empty , "[1,2,3]" } } ) } ,
{ ( "{ \"key\": \"value\"}" , new Dictionary < string , object > ( ) { { "key" , "value" } } ) } ,
{ ( "{ \"key\": true}" , new Dictionary < string , object > ( ) { { "key" , "True" } } ) } ,
{
( "{ invalidjson file here}" ,
new Dictionary < string , object > ( ) { { String . Empty , "{ invalidjson file here}" } } )
}
2018-11-27 07:13:09 +01:00
} ;
2018-11-30 09:34:43 +01:00
2018-11-27 07:13:09 +01:00
testCases . ForEach ( tuple = >
{
Assert . Equal ( tuple . expectedOutput , InvoiceController . PosDataParser . ParsePosData ( tuple . input ) ) ;
} ) ;
}
2018-11-30 09:34:43 +01:00
2019-10-06 15:49:28 +02:00
[Fact(Timeout = TestTimeout)]
2018-11-27 07:13:09 +01:00
[Trait("Integration", "Integration")]
public async Task PosDataParser_ParsesCorrectly_Slower ( )
{
using ( var tester = ServerTester . Create ( ) )
{
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2018-11-27 07:13:09 +01:00
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
2018-11-30 09:34:43 +01:00
2018-11-27 07:13:09 +01:00
var controller = tester . PayTester . GetController < InvoiceController > ( null ) ;
var testCases =
2019-01-26 05:26:49 +01:00
new List < ( string input , Dictionary < string , object > expectedOutput ) > ( )
2018-11-27 07:13:09 +01:00
{
2020-04-05 13:48:00 +02:00
{ ( null , new Dictionary < string , object > ( ) ) } ,
2019-01-26 05:26:49 +01:00
{ ( "" , new Dictionary < string , object > ( ) ) } ,
{ ( "{}" , new Dictionary < string , object > ( ) ) } ,
2020-04-05 13:48:00 +02:00
{
( "non-json-content" ,
new Dictionary < string , object > ( ) { { string . Empty , "non-json-content" } } )
} ,
{ ( "[1,2,3]" , new Dictionary < string , object > ( ) { { string . Empty , "[1,2,3]" } } ) } ,
{ ( "{ \"key\": \"value\"}" , new Dictionary < string , object > ( ) { { "key" , "value" } } ) } ,
{ ( "{ \"key\": true}" , new Dictionary < string , object > ( ) { { "key" , "True" } } ) } ,
{
( "{ invalidjson file here}" ,
new Dictionary < string , object > ( ) { { String . Empty , "{ invalidjson file here}" } } )
}
2018-11-27 07:13:09 +01:00
} ;
var tasks = new List < Task > ( ) ;
foreach ( var valueTuple in testCases )
{
2020-06-24 03:34:09 +02:00
tasks . Add ( user . BitPay . CreateInvoiceAsync ( new Invoice ( 1 , "BTC" ) { PosData = valueTuple . input } )
2020-04-05 13:48:00 +02:00
. ContinueWith ( async task = >
{
var result = await controller . Invoice ( task . Result . Id ) ;
var viewModel =
Assert . IsType < InvoiceDetailsModel > (
Assert . IsType < ViewResult > ( result ) . Model ) ;
Assert . Equal ( valueTuple . expectedOutput , viewModel . PosData ) ;
} ) ) ;
2018-11-27 07:13:09 +01:00
}
await Task . WhenAll ( tasks ) ;
}
}
2019-10-06 15:49:28 +02:00
[Fact(Timeout = TestTimeout)]
2018-11-30 09:34:43 +01:00
[Trait("Integration", "Integration")]
2019-10-07 09:04:25 +02:00
public async Task CanExportInvoicesJson ( )
2018-11-30 09:34:43 +01:00
{
2019-01-04 16:37:09 +01:00
decimal GetFieldValue ( string input , string fieldName )
{
var match = Regex . Match ( input , $"\" { fieldName } \ ":([^,]*)" ) ;
Assert . True ( match . Success ) ;
return decimal . Parse ( match . Groups [ 1 ] . Value . Trim ( ) , CultureInfo . InvariantCulture ) ;
}
2020-04-05 13:48:00 +02:00
2018-11-30 09:34:43 +01:00
using ( var tester = ServerTester . Create ( ) )
{
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2018-11-30 09:34:43 +01:00
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
2019-01-04 16:37:09 +01:00
user . SetNetworkFeeMode ( NetworkFeeMode . Always ) ;
2020-04-05 13:48:00 +02:00
var invoice = user . BitPay . CreateInvoice (
new Invoice ( )
{
Price = 10 ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some \", description" ,
FullNotifications = true
} , Facade . Merchant ) ;
2018-11-30 09:34:43 +01:00
2019-01-04 16:37:09 +01:00
var networkFee = new FeeRate ( invoice . MinerFees [ "BTC" ] . SatoshiPerBytes ) . GetFee ( 100 ) ;
2018-11-30 09:34:43 +01:00
// ensure 0 invoices exported because there are no payments yet
var jsonResult = user . GetController < InvoiceController > ( ) . Export ( "json" ) . GetAwaiter ( ) . GetResult ( ) ;
var result = Assert . IsType < ContentResult > ( jsonResult ) ;
Assert . Equal ( "application/json" , result . ContentType ) ;
Assert . Equal ( "[]" , result . Content ) ;
var cashCow = tester . ExplorerNode ;
var invoiceAddress = BitcoinAddress . Create ( invoice . CryptoInfo [ 0 ] . Address , cashCow . Network ) ;
2020-02-13 06:44:31 +01:00
//
2019-01-04 16:37:09 +01:00
var firstPayment = invoice . CryptoInfo [ 0 ] . TotalDue - 3 * networkFee ;
2018-11-30 09:34:43 +01:00
cashCow . SendToAddress ( invoiceAddress , firstPayment ) ;
2018-12-18 18:56:12 +01:00
Thread . Sleep ( 1000 ) ; // prevent race conditions, ordering payments
// look if you can reduce thread sleep, this was min value for me
// should reduce invoice due by 0 USD because payment = network fee
cashCow . SendToAddress ( invoiceAddress , networkFee ) ;
Thread . Sleep ( 1000 ) ;
// pay remaining amount
2019-01-04 16:37:09 +01:00
cashCow . SendToAddress ( invoiceAddress , 4 * networkFee ) ;
2018-12-18 18:56:12 +01:00
Thread . Sleep ( 1000 ) ;
2018-11-30 09:34:43 +01:00
2019-01-05 19:47:39 +01:00
TestUtils . Eventually ( ( ) = >
2018-11-30 09:34:43 +01:00
{
2020-04-05 13:48:00 +02:00
var jsonResultPaid =
user . GetController < InvoiceController > ( ) . Export ( "json" ) . GetAwaiter ( ) . GetResult ( ) ;
2018-11-30 09:34:43 +01:00
var paidresult = Assert . IsType < ContentResult > ( jsonResultPaid ) ;
Assert . Equal ( "application/json" , paidresult . ContentType ) ;
2018-12-18 18:56:12 +01:00
var parsedJson = JsonConvert . DeserializeObject < object [ ] > ( paidresult . Content ) ;
Assert . Equal ( 3 , parsedJson . Length ) ;
2019-01-04 16:37:09 +01:00
var invoiceDueAfterFirstPayment = ( 3 * networkFee ) . ToDecimal ( MoneyUnit . BTC ) * invoice . Rate ;
2018-12-18 18:56:12 +01:00
var pay1str = parsedJson [ 0 ] . ToString ( ) ;
Assert . Contains ( "\"InvoiceItemDesc\": \"Some \\\", description\"" , pay1str ) ;
2019-01-04 16:37:09 +01:00
Assert . Equal ( invoiceDueAfterFirstPayment , GetFieldValue ( pay1str , "InvoiceDue" ) ) ;
2018-12-18 18:56:12 +01:00
Assert . Contains ( "\"InvoicePrice\": 10.0" , pay1str ) ;
Assert . Contains ( "\"ConversionRate\": 5000.0" , pay1str ) ;
Assert . Contains ( $"\" InvoiceId \ ": \"{invoice.Id}\"," , pay1str ) ;
var pay2str = parsedJson [ 1 ] . ToString ( ) ;
2019-01-04 16:37:09 +01:00
Assert . Equal ( invoiceDueAfterFirstPayment , GetFieldValue ( pay2str , "InvoiceDue" ) ) ;
2018-12-18 18:56:12 +01:00
var pay3str = parsedJson [ 2 ] . ToString ( ) ;
Assert . Contains ( "\"InvoiceDue\": 0" , pay3str ) ;
2018-11-30 09:34:43 +01:00
} ) ;
}
}
2020-04-05 13:48:00 +02:00
2019-10-06 15:49:28 +02:00
[Fact(Timeout = TestTimeout)]
2019-01-04 16:37:09 +01:00
[Trait("Integration", "Integration")]
2019-10-07 09:04:25 +02:00
public async Task CanChangeNetworkFeeMode ( )
2019-01-04 16:37:09 +01:00
{
using ( var tester = ServerTester . Create ( ) )
{
2020-04-05 13:00:28 +02:00
var btc = new PaymentMethodId ( "BTC" , PaymentTypes . BTCLike ) ;
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2019-01-04 16:37:09 +01:00
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
foreach ( var networkFeeMode in Enum . GetValues ( typeof ( NetworkFeeMode ) ) . Cast < NetworkFeeMode > ( ) )
{
Logs . Tester . LogInformation ( $"Trying with {nameof(networkFeeMode)}={networkFeeMode}" ) ;
user . SetNetworkFeeMode ( networkFeeMode ) ;
2020-04-05 13:48:00 +02:00
var invoice = user . BitPay . CreateInvoice (
new Invoice ( )
{
Price = 10 ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some \", description" ,
FullNotifications = true
} , Facade . Merchant ) ;
2020-04-05 13:00:28 +02:00
var nextNetworkFee = ( await tester . PayTester . InvoiceRepository . GetInvoice ( invoice . Id ) )
. GetPaymentMethods ( ) [ btc ]
. GetPaymentMethodDetails ( )
. AssertType < BitcoinLikeOnChainPaymentMethod > ( )
. GetNextNetworkFee ( ) ;
var firstPaymentFee = nextNetworkFee ;
2019-01-04 16:37:09 +01:00
switch ( networkFeeMode )
{
2020-04-05 13:48:00 +02:00
case NetworkFeeMode . Never :
case NetworkFeeMode . MultiplePaymentsOnly :
2020-04-05 13:00:28 +02:00
Assert . Equal ( 0.0 m , nextNetworkFee ) ;
2019-01-04 16:37:09 +01:00
break ;
case NetworkFeeMode . Always :
2020-04-05 13:00:28 +02:00
Assert . NotEqual ( 0.0 m , nextNetworkFee ) ;
2019-01-04 16:37:09 +01:00
break ;
}
2020-04-05 13:48:00 +02:00
2020-04-05 13:00:28 +02:00
var missingMoney = Money . Satoshis ( 5000 ) . ToDecimal ( MoneyUnit . BTC ) ;
var cashCow = tester . ExplorerNode ;
var invoiceAddress = BitcoinAddress . Create ( invoice . CryptoInfo [ 0 ] . Address , cashCow . Network ) ;
var due = Money . Parse ( invoice . CryptoInfo [ 0 ] . Due ) ;
var productPartDue = ( invoice . Price / invoice . Rate ) ;
2020-04-05 13:48:00 +02:00
Logs . Tester . LogInformation (
$"Product part due is {productPartDue} and due {due} with network fee {nextNetworkFee}" ) ;
2020-04-05 13:00:28 +02:00
Assert . Equal ( productPartDue + nextNetworkFee , due . ToDecimal ( MoneyUnit . BTC ) ) ;
2019-01-04 16:37:09 +01:00
var firstPayment = productPartDue - missingMoney ;
cashCow . SendToAddress ( invoiceAddress , Money . Coins ( firstPayment ) ) ;
2020-04-05 13:00:28 +02:00
await TestUtils . EventuallyAsync ( async ( ) = >
{
2019-01-04 16:37:09 +01:00
invoice = user . BitPay . GetInvoice ( invoice . Id ) ;
due = Money . Parse ( invoice . CryptoInfo [ 0 ] . Due ) ;
2020-04-05 11:54:12 +02:00
Logs . Tester . LogInformation ( $"Remaining due after first payment: {due}" ) ;
2019-01-04 16:37:09 +01:00
Assert . Equal ( Money . Coins ( firstPayment ) , Money . Parse ( invoice . CryptoInfo [ 0 ] . Paid ) ) ;
2020-04-05 13:00:28 +02:00
nextNetworkFee = ( await tester . PayTester . InvoiceRepository . GetInvoice ( invoice . Id ) )
. GetPaymentMethods ( ) [ btc ]
. GetPaymentMethodDetails ( )
. AssertType < BitcoinLikeOnChainPaymentMethod > ( )
. GetNextNetworkFee ( ) ;
2019-01-04 16:37:09 +01:00
switch ( networkFeeMode )
{
2020-04-05 13:48:00 +02:00
case NetworkFeeMode . Never :
2020-04-05 13:00:28 +02:00
Assert . Equal ( 0.0 m , nextNetworkFee ) ;
2019-01-04 16:37:09 +01:00
break ;
2020-04-05 13:00:28 +02:00
case NetworkFeeMode . MultiplePaymentsOnly :
2019-01-04 16:37:09 +01:00
case NetworkFeeMode . Always :
2020-04-05 13:00:28 +02:00
Assert . NotEqual ( 0.0 m , nextNetworkFee ) ;
2019-01-04 16:37:09 +01:00
break ;
}
2020-04-05 13:48:00 +02:00
2020-04-05 13:00:28 +02:00
Assert . Equal ( missingMoney + firstPaymentFee + nextNetworkFee , due . ToDecimal ( MoneyUnit . BTC ) ) ;
2020-04-05 13:48:00 +02:00
Assert . Equal ( firstPayment + missingMoney + firstPaymentFee + nextNetworkFee ,
Money . Parse ( invoice . CryptoInfo [ 0 ] . TotalDue ) . ToDecimal ( MoneyUnit . BTC ) ) ;
2019-01-04 16:37:09 +01:00
} ) ;
cashCow . SendToAddress ( invoiceAddress , due ) ;
2020-04-05 11:54:12 +02:00
Logs . Tester . LogInformation ( $"After payment of {due}, the invoice should be paid" ) ;
2019-01-06 09:29:21 +01:00
TestUtils . Eventually ( ( ) = >
2019-01-04 16:37:09 +01:00
{
invoice = user . BitPay . GetInvoice ( invoice . Id ) ;
Assert . Equal ( "paid" , invoice . Status ) ;
} ) ;
}
}
}
2018-11-30 09:34:43 +01:00
2019-10-06 15:49:28 +02:00
[Fact(Timeout = TestTimeout)]
2018-11-30 09:34:43 +01:00
[Trait("Integration", "Integration")]
2019-10-07 09:04:25 +02:00
public async Task CanExportInvoicesCsv ( )
2018-11-30 09:34:43 +01:00
{
using ( var tester = ServerTester . Create ( ) )
{
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2018-11-30 09:34:43 +01:00
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
2019-01-04 16:37:09 +01:00
user . SetNetworkFeeMode ( NetworkFeeMode . Always ) ;
2020-04-05 13:48:00 +02:00
var invoice = user . BitPay . CreateInvoice (
new Invoice ( )
{
Price = 500 ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some \", description" ,
FullNotifications = true
} , Facade . Merchant ) ;
2018-11-30 09:34:43 +01:00
var cashCow = tester . ExplorerNode ;
var invoiceAddress = BitcoinAddress . Create ( invoice . CryptoInfo [ 0 ] . Address , cashCow . Network ) ;
2019-01-10 05:47:21 +01:00
var firstPayment = invoice . CryptoInfo [ 0 ] . TotalDue - Money . Coins ( 0.001 m ) ;
2018-11-30 09:34:43 +01:00
cashCow . SendToAddress ( invoiceAddress , firstPayment ) ;
2019-01-05 19:47:39 +01:00
TestUtils . Eventually ( ( ) = >
2018-11-30 09:34:43 +01:00
{
2020-04-05 13:48:00 +02:00
var exportResultPaid =
user . GetController < InvoiceController > ( ) . Export ( "csv" ) . GetAwaiter ( ) . GetResult ( ) ;
2018-11-30 09:34:43 +01:00
var paidresult = Assert . IsType < ContentResult > ( exportResultPaid ) ;
Assert . Equal ( "application/csv" , paidresult . ContentType ) ;
2020-07-30 03:01:56 +02:00
Assert . Contains ( $",orderId,{invoice.Id}," , paidresult . Content ) ;
Assert . Contains ( $",On-Chain,BTC,0.0991,0.0001,5000.0" , paidresult . Content ) ;
Assert . Contains ( $",USD,5.00" , paidresult . Content ) ; // Seems hacky but some plateform does not render this decimal the same
Assert . Contains ( "0,,\"Some \"\", description\",new (paidPartial),new,paidPartial" ,
2020-04-05 13:48:00 +02:00
paidresult . Content ) ;
2018-11-30 09:34:43 +01:00
} ) ;
}
}
2019-10-06 15:49:28 +02:00
[Fact(Timeout = TestTimeout)]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2019-10-07 09:04:25 +02:00
public async Task CanCreateAndDeleteApps ( )
2018-04-03 09:53:55 +02:00
{
using ( var tester = ServerTester . Create ( ) )
{
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2018-04-03 09:53:55 +02:00
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
var user2 = tester . NewAccount ( ) ;
user2 . GrantAccess ( ) ;
var apps = user . GetController < AppsController > ( ) ;
var apps2 = user2 . GetController < AppsController > ( ) ;
var vm = Assert . IsType < CreateAppViewModel > ( Assert . IsType < ViewResult > ( apps . CreateApp ( ) . Result ) . Model ) ;
Assert . NotNull ( vm . SelectedAppType ) ;
Assert . Null ( vm . Name ) ;
vm . Name = "test" ;
2018-07-08 08:33:42 +02:00
vm . SelectedAppType = AppType . PointOfSale . ToString ( ) ;
2018-04-03 09:53:55 +02:00
var redirectToAction = Assert . IsType < RedirectToActionResult > ( apps . CreateApp ( vm ) . Result ) ;
2018-07-08 08:33:42 +02:00
Assert . Equal ( nameof ( apps . UpdatePointOfSale ) , redirectToAction . ActionName ) ;
2018-04-03 09:53:55 +02:00
var appList = Assert . IsType < ListAppsViewModel > ( Assert . IsType < ViewResult > ( apps . ListApps ( ) . Result ) . Model ) ;
2020-04-05 13:48:00 +02:00
var appList2 =
Assert . IsType < ListAppsViewModel > ( Assert . IsType < ViewResult > ( apps2 . ListApps ( ) . Result ) . Model ) ;
2018-04-03 09:53:55 +02:00
Assert . Single ( appList . Apps ) ;
Assert . Empty ( appList2 . Apps ) ;
Assert . Equal ( "test" , appList . Apps [ 0 ] . AppName ) ;
2018-10-09 16:38:56 +02:00
Assert . Equal ( apps . CreatedAppId , appList . Apps [ 0 ] . Id ) ;
2018-04-03 09:53:55 +02:00
Assert . True ( appList . Apps [ 0 ] . IsOwner ) ;
Assert . Equal ( user . StoreId , appList . Apps [ 0 ] . StoreId ) ;
Assert . IsType < NotFoundResult > ( apps2 . DeleteApp ( appList . Apps [ 0 ] . Id ) . Result ) ;
Assert . IsType < ViewResult > ( apps . DeleteApp ( appList . Apps [ 0 ] . Id ) . Result ) ;
redirectToAction = Assert . IsType < RedirectToActionResult > ( apps . DeleteAppPost ( appList . Apps [ 0 ] . Id ) . Result ) ;
Assert . Equal ( nameof ( apps . ListApps ) , redirectToAction . ActionName ) ;
appList = Assert . IsType < ListAppsViewModel > ( Assert . IsType < ViewResult > ( apps . ListApps ( ) . Result ) . Model ) ;
Assert . Empty ( appList . Apps ) ;
}
}
2019-10-06 15:49:28 +02:00
[Fact(Timeout = TestTimeout)]
2019-01-15 14:12:29 +01:00
[Trait("Integration", "Integration")]
2019-10-07 09:04:25 +02:00
public async Task CanCreateStrangeInvoice ( )
2019-01-15 14:12:29 +01:00
{
using ( var tester = ServerTester . Create ( ) )
{
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2019-01-15 14:12:29 +01:00
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
2020-03-10 09:11:15 +01:00
DateTimeOffset expiration = DateTimeOffset . UtcNow + TimeSpan . FromMinutes ( 21 ) ;
2020-04-05 13:48:00 +02:00
var invoice1 = user . BitPay . CreateInvoice (
new Invoice ( )
{
Price = 0.000000012 m ,
Currency = "USD" ,
FullNotifications = true ,
ExpirationTime = expiration
} , Facade . Merchant ) ;
2020-03-10 09:11:15 +01:00
Assert . Equal ( expiration . ToUnixTimeSeconds ( ) , invoice1 . ExpirationTime . ToUnixTimeSeconds ( ) ) ;
2020-06-24 03:34:09 +02:00
var invoice2 = user . BitPay . CreateInvoice ( new Invoice ( ) { Price = 0.000000019 m , Currency = "USD" } ,
2020-04-05 13:48:00 +02:00
Facade . Merchant ) ;
2019-02-22 14:48:39 +01:00
Assert . Equal ( 0.000000012 m , invoice1 . Price ) ;
Assert . Equal ( 0.000000019 m , invoice2 . Price ) ;
// Should round up to 1 because 0.000000019 is unsignificant
2020-04-05 13:48:00 +02:00
var invoice3 = user . BitPay . CreateInvoice (
2020-06-24 03:34:09 +02:00
new Invoice ( ) { Price = 1.000000019 m , Currency = "USD" , FullNotifications = true } , Facade . Merchant ) ;
2019-02-22 14:48:39 +01:00
Assert . Equal ( 1 m , invoice3 . Price ) ;
2019-02-22 14:52:43 +01:00
// Should not round up at 8 digit because the 9th is insignificant
2020-04-05 13:48:00 +02:00
var invoice4 = user . BitPay . CreateInvoice (
2020-06-24 03:34:09 +02:00
new Invoice ( ) { Price = 1.000000019 m , Currency = "BTC" , FullNotifications = true } , Facade . Merchant ) ;
2019-02-22 14:52:43 +01:00
Assert . Equal ( 1.00000002 m , invoice4 . Price ) ;
// But not if the 9th is insignificant
2020-04-05 13:48:00 +02:00
invoice4 = user . BitPay . CreateInvoice (
2020-06-24 03:34:09 +02:00
new Invoice ( ) { Price = 0.000000019 m , Currency = "BTC" , FullNotifications = true } , Facade . Merchant ) ;
2019-02-22 14:52:43 +01:00
Assert . Equal ( 0.000000019 m , invoice4 . Price ) ;
2019-01-15 14:12:29 +01:00
2020-04-05 13:48:00 +02:00
var invoice = user . BitPay . CreateInvoice (
2020-06-24 03:34:09 +02:00
new Invoice ( ) { Price = - 0.1 m , Currency = "BTC" , FullNotifications = true } , Facade . Merchant ) ;
2019-01-15 14:12:29 +01:00
Assert . Equal ( 0.0 m , invoice . Price ) ;
}
}
2019-10-06 15:49:28 +02:00
[Fact(Timeout = TestTimeout)]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2019-10-07 09:04:25 +02:00
public async Task InvoiceFlowThroughDifferentStatesCorrectly ( )
2018-01-07 18:36:41 +01:00
{
using ( var tester = ServerTester . Create ( ) )
{
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2018-01-07 18:36:41 +01:00
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
2018-02-23 07:21:42 +01:00
user . RegisterDerivationScheme ( "BTC" ) ;
2020-04-05 13:48:00 +02:00
var invoice = user . BitPay . CreateInvoice (
new Invoice ( )
{
Price = 5000.0 m ,
TaxIncluded = 1000.0 m ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
2017-10-27 10:53:04 +02:00
var repo = tester . PayTester . GetService < InvoiceRepository > ( ) ;
var ctx = tester . PayTester . GetService < ApplicationDbContextFactory > ( ) . CreateContext ( ) ;
2018-02-25 16:48:12 +01:00
Assert . Equal ( 0 , invoice . CryptoInfo [ 0 ] . TxCount ) ;
2018-05-25 15:49:49 +02:00
Assert . True ( invoice . MinerFees . ContainsKey ( "BTC" ) ) ;
2020-06-24 03:34:09 +02:00
Assert . Contains ( invoice . MinerFees [ "BTC" ] . SatoshiPerBytes , new [ ] { 100.0 m , 20.0 m } ) ;
2019-01-05 19:47:39 +01:00
TestUtils . Eventually ( ( ) = >
2017-10-27 10:53:04 +02:00
{
2017-12-16 17:04:20 +01:00
var textSearchResult = tester . PayTester . InvoiceRepository . GetInvoices ( new InvoiceQuery ( )
2017-11-12 15:03:33 +01:00
{
2020-06-24 03:34:09 +02:00
StoreId = new [ ] { user . StoreId } ,
TextSearch = invoice . OrderId
2017-11-12 15:03:33 +01:00
} ) . GetAwaiter ( ) . GetResult ( ) ;
2017-12-21 07:52:04 +01:00
Assert . Single ( textSearchResult ) ;
2017-12-16 17:04:20 +01:00
textSearchResult = tester . PayTester . InvoiceRepository . GetInvoices ( new InvoiceQuery ( )
2017-11-12 15:03:33 +01:00
{
2020-06-24 03:34:09 +02:00
StoreId = new [ ] { user . StoreId } ,
TextSearch = invoice . Id
2017-11-12 15:03:33 +01:00
} ) . GetAwaiter ( ) . GetResult ( ) ;
2017-10-27 10:53:04 +02:00
2017-12-21 07:52:04 +01:00
Assert . Single ( textSearchResult ) ;
2017-11-12 15:03:33 +01:00
} ) ;
2017-10-27 10:53:04 +02:00
invoice = user . BitPay . GetInvoice ( invoice . Id , Facade . Merchant ) ;
2019-01-24 12:53:29 +01:00
Assert . Equal ( 1000.0 m , invoice . TaxIncluded ) ;
Assert . Equal ( 5000.0 m , invoice . Price ) ;
2017-10-27 10:53:04 +02:00
Assert . Equal ( Money . Coins ( 0 ) , invoice . BtcPaid ) ;
Assert . Equal ( "new" , invoice . Status ) ;
2017-12-21 07:52:04 +01:00
Assert . False ( ( bool ) ( ( JValue ) invoice . ExceptionStatus ) . Value ) ;
2017-10-27 10:53:04 +02:00
2018-02-16 17:34:40 +01:00
Assert . Single ( user . BitPay . GetInvoices ( invoice . InvoiceTime . UtcDateTime ) ) ;
Assert . Empty ( user . BitPay . GetInvoices ( invoice . InvoiceTime . UtcDateTime + TimeSpan . FromDays ( 2 ) ) ) ;
Assert . Single ( user . BitPay . GetInvoices ( invoice . InvoiceTime . UtcDateTime - TimeSpan . FromDays ( 5 ) ) ) ;
2020-04-05 13:48:00 +02:00
Assert . Single ( user . BitPay . GetInvoices ( invoice . InvoiceTime . UtcDateTime - TimeSpan . FromDays ( 5 ) ,
invoice . InvoiceTime . DateTime + TimeSpan . FromDays ( 1.0 ) ) ) ;
Assert . Empty ( user . BitPay . GetInvoices ( invoice . InvoiceTime . UtcDateTime - TimeSpan . FromDays ( 5 ) ,
invoice . InvoiceTime . DateTime - TimeSpan . FromDays ( 1 ) ) ) ;
2017-10-27 10:53:04 +02:00
var firstPayment = Money . Coins ( 0.04 m ) ;
var txFee = Money . Zero ;
var cashCow = tester . ExplorerNode ;
var invoiceAddress = BitcoinAddress . Create ( invoice . BitcoinAddress , cashCow . Network ) ;
var iii = ctx . AddressInvoices . ToArray ( ) ;
Assert . True ( IsMapped ( invoice , ctx ) ) ;
cashCow . SendToAddress ( invoiceAddress , firstPayment ) ;
2018-12-06 09:05:27 +01:00
var invoiceEntity = repo . GetInvoice ( invoice . Id , true ) . GetAwaiter ( ) . GetResult ( ) ;
2017-12-21 07:52:04 +01:00
Assert . Single ( invoiceEntity . HistoricalAddresses ) ;
2017-10-27 10:53:04 +02:00
Assert . Null ( invoiceEntity . HistoricalAddresses [ 0 ] . UnAssigned ) ;
Money secondPayment = Money . Zero ;
2019-01-05 19:47:39 +01:00
TestUtils . Eventually ( ( ) = >
2017-10-27 10:53:04 +02:00
{
var localInvoice = user . BitPay . GetInvoice ( invoice . Id , Facade . Merchant ) ;
Assert . Equal ( "new" , localInvoice . Status ) ;
Assert . Equal ( firstPayment , localInvoice . BtcPaid ) ;
txFee = localInvoice . BtcDue - invoice . BtcDue ;
2017-12-21 07:52:04 +01:00
Assert . Equal ( "paidPartial" , localInvoice . ExceptionStatus . ToString ( ) ) ;
2018-02-25 16:48:12 +01:00
Assert . Equal ( 1 , localInvoice . CryptoInfo [ 0 ] . TxCount ) ;
2017-10-27 10:53:04 +02:00
Assert . NotEqual ( localInvoice . BitcoinAddress , invoice . BitcoinAddress ) ; //New address
Assert . True ( IsMapped ( invoice , ctx ) ) ;
Assert . True ( IsMapped ( localInvoice , ctx ) ) ;
2018-12-06 09:05:27 +01:00
invoiceEntity = repo . GetInvoice ( invoice . Id , true ) . GetAwaiter ( ) . GetResult ( ) ;
2020-04-05 13:48:00 +02:00
var historical1 =
invoiceEntity . HistoricalAddresses . FirstOrDefault ( h = > h . GetAddress ( ) = = invoice . BitcoinAddress ) ;
2017-10-27 10:53:04 +02:00
Assert . NotNull ( historical1 . UnAssigned ) ;
2020-04-05 13:48:00 +02:00
var historical2 =
invoiceEntity . HistoricalAddresses . FirstOrDefault ( h = >
h . GetAddress ( ) = = localInvoice . BitcoinAddress ) ;
2017-10-27 10:53:04 +02:00
Assert . Null ( historical2 . UnAssigned ) ;
invoiceAddress = BitcoinAddress . Create ( localInvoice . BitcoinAddress , cashCow . Network ) ;
secondPayment = localInvoice . BtcDue ;
} ) ;
cashCow . SendToAddress ( invoiceAddress , secondPayment ) ;
2019-01-05 19:47:39 +01:00
TestUtils . Eventually ( ( ) = >
2017-10-27 10:53:04 +02:00
{
var localInvoice = user . BitPay . GetInvoice ( invoice . Id , Facade . Merchant ) ;
Assert . Equal ( "paid" , localInvoice . Status ) ;
2018-02-25 16:48:12 +01:00
Assert . Equal ( 2 , localInvoice . CryptoInfo [ 0 ] . TxCount ) ;
2017-10-27 10:53:04 +02:00
Assert . Equal ( firstPayment + secondPayment , localInvoice . BtcPaid ) ;
Assert . Equal ( Money . Zero , localInvoice . BtcDue ) ;
Assert . Equal ( localInvoice . BitcoinAddress , invoiceAddress . ToString ( ) ) ; //no new address generated
Assert . True ( IsMapped ( localInvoice , ctx ) ) ;
2017-12-18 00:56:27 +01:00
Assert . False ( ( bool ) ( ( JValue ) localInvoice . ExceptionStatus ) . Value ) ;
2017-10-27 10:53:04 +02:00
} ) ;
cashCow . Generate ( 1 ) ; //The user has medium speed settings, so 1 conf is enough to be confirmed
2019-01-05 19:47:39 +01:00
TestUtils . Eventually ( ( ) = >
2017-10-27 10:53:04 +02:00
{
var localInvoice = user . BitPay . GetInvoice ( invoice . Id , Facade . Merchant ) ;
Assert . Equal ( "confirmed" , localInvoice . Status ) ;
} ) ;
cashCow . Generate ( 5 ) ; //Now should be complete
2019-01-05 19:47:39 +01:00
TestUtils . Eventually ( ( ) = >
2017-10-27 10:53:04 +02:00
{
var localInvoice = user . BitPay . GetInvoice ( invoice . Id , Facade . Merchant ) ;
Assert . Equal ( "complete" , localInvoice . Status ) ;
2018-05-11 15:38:31 +02:00
Assert . NotEqual ( 0.0 m , localInvoice . Rate ) ;
2017-10-27 10:53:04 +02:00
} ) ;
invoice = user . BitPay . CreateInvoice ( new Invoice ( )
{
2018-05-11 15:38:31 +02:00
Price = 5000.0 m ,
2017-10-27 10:53:04 +02:00
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
//RedirectURL = redirect + "redirect",
//NotificationURL = CallbackUri + "/notification",
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
invoiceAddress = BitcoinAddress . Create ( invoice . BitcoinAddress , cashCow . Network ) ;
2018-11-02 06:26:13 +01:00
var txId = cashCow . SendToAddress ( invoiceAddress , invoice . BtcDue + Money . Coins ( 1 ) ) ;
2017-10-27 10:53:04 +02:00
2019-01-05 19:47:39 +01:00
TestUtils . Eventually ( ( ) = >
2017-10-27 10:53:04 +02:00
{
var localInvoice = user . BitPay . GetInvoice ( invoice . Id , Facade . Merchant ) ;
Assert . Equal ( "paid" , localInvoice . Status ) ;
Assert . Equal ( Money . Zero , localInvoice . BtcDue ) ;
Assert . Equal ( "paidOver" , ( string ) ( ( JValue ) localInvoice . ExceptionStatus ) . Value ) ;
2018-11-02 06:26:13 +01:00
var textSearchResult = tester . PayTester . InvoiceRepository . GetInvoices ( new InvoiceQuery ( )
{
2020-06-24 03:34:09 +02:00
StoreId = new [ ] { user . StoreId } ,
TextSearch = txId . ToString ( )
2018-11-02 06:26:13 +01:00
} ) . GetAwaiter ( ) . GetResult ( ) ;
Assert . Single ( textSearchResult ) ;
2017-10-27 10:53:04 +02:00
} ) ;
cashCow . Generate ( 1 ) ;
2019-01-05 19:47:39 +01:00
TestUtils . Eventually ( ( ) = >
2017-10-27 10:53:04 +02:00
{
var localInvoice = user . BitPay . GetInvoice ( invoice . Id , Facade . Merchant ) ;
Assert . Equal ( "confirmed" , localInvoice . Status ) ;
Assert . Equal ( Money . Zero , localInvoice . BtcDue ) ;
Assert . Equal ( "paidOver" , ( string ) ( ( JValue ) localInvoice . ExceptionStatus ) . Value ) ;
} ) ;
}
}
2019-10-06 15:49:28 +02:00
[Fact(Timeout = TestTimeout)]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2018-05-04 08:35:39 +02:00
public void CanQueryDirectProviders ( )
{
2018-08-22 09:53:40 +02:00
var factory = CreateBTCPayRateFactory ( ) ;
2020-04-05 13:48:00 +02:00
var directlySupported = factory . GetSupportedExchanges ( ) . Where ( s = > s . Source = = RateSource . Direct )
. Select ( s = > s . Id ) . ToHashSet ( ) ;
2020-01-13 14:20:45 +01:00
var all = string . Join ( "\r\n" , factory . GetSupportedExchanges ( ) . Select ( e = > e . Id ) . ToArray ( ) ) ;
2018-05-04 08:35:39 +02:00
foreach ( var result in factory
2018-08-22 17:24:33 +02:00
. Providers
2020-04-05 13:48:00 +02:00
. 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 ) )
2018-05-04 08:35:39 +02:00
. ToList ( ) )
{
2019-01-29 10:33:56 +01:00
Logs . Tester . LogInformation ( $"Testing {result.ExpectedName}" ) ;
2020-03-17 05:19:55 +01:00
if ( result . ExpectedName = = "ndax" )
{
Logs . Tester . LogInformation ( $"Skipping (currently crashing)" ) ;
continue ;
}
2020-04-05 13:48:00 +02:00
2018-08-22 17:24:33 +02:00
result . Fetcher . InvalidateCache ( ) ;
2020-01-17 10:11:05 +01:00
var exchangeRates = new ExchangeRates ( result . ExpectedName , result . ResultAsync . Result ) ;
2018-08-22 17:24:33 +02:00
result . Fetcher . InvalidateCache ( ) ;
2018-05-04 08:35:39 +02:00
Assert . NotNull ( exchangeRates ) ;
Assert . NotEmpty ( exchangeRates ) ;
Assert . NotEmpty ( exchangeRates . ByExchange [ result . ExpectedName ] ) ;
2020-03-25 16:48:01 +01:00
if ( result . ExpectedName = = "bitbank" | | result . ExpectedName = = "bitflyer" )
2019-03-19 16:49:44 +01:00
{
Assert . Contains ( exchangeRates . ByExchange [ result . ExpectedName ] ,
2020-04-05 13:48:00 +02:00
e = > e . CurrencyPair = = new CurrencyPair ( "BTC" , "JPY" ) & &
e . BidAsk . Bid > 100 m ) ; // 1BTC will always be more than 100JPY
2019-03-19 16:49:44 +01:00
}
2020-02-13 06:44:31 +01:00
else if ( result . ExpectedName = = "polispay" )
{
Assert . Contains ( exchangeRates . ByExchange [ result . ExpectedName ] ,
2020-04-05 13:48:00 +02:00
e = > e . CurrencyPair = = new CurrencyPair ( "BTC" , "POLIS" ) & &
e . BidAsk . Bid > 1.0 m ) ; // 1BTC will always be more than 1 POLIS
2020-02-13 06:44:31 +01:00
}
2020-04-17 01:46:45 +02:00
else if ( result . ExpectedName = = "argoneum" )
{
Assert . Contains ( exchangeRates . ByExchange [ result . ExpectedName ] ,
e = > e . CurrencyPair = = new CurrencyPair ( "BTC" , "AGM" ) & &
e . BidAsk . Bid > 1.0 m ) ; // 1 BTC will always be more than 1 AGM
}
2019-03-19 16:49:44 +01:00
else
{
// This check if the currency pair is using right currency pair
Assert . Contains ( exchangeRates . ByExchange [ result . ExpectedName ] ,
2018-05-13 08:09:17 +02:00
e = > ( e . CurrencyPair = = new CurrencyPair ( "BTC" , "USD" ) | |
2020-04-05 13:48:00 +02:00
e . CurrencyPair = = new CurrencyPair ( "BTC" , "EUR" ) | |
e . CurrencyPair = = new CurrencyPair ( "BTC" , "USDT" ) | |
e . CurrencyPair = = new CurrencyPair ( "BTC" , "CAD" ) )
& & e . BidAsk . Bid > 1.0 m // 1BTC will always be more than 1USD
) ;
2019-03-19 16:49:44 +01:00
}
2020-01-18 11:23:40 +01:00
// We are not showing a directly implemented exchange as directly implemented in the UI
// we need to modify the AvailableRateProvider
2020-01-18 13:48:04 +01:00
// There are some exception we stopped supporting but don't want to break backward compat
if ( result . ExpectedName ! = "coinaverage" & & result . ExpectedName ! = "gdax" )
2020-01-18 11:23:40 +01:00
Assert . Contains ( result . ExpectedName , directlySupported ) ;
2018-05-04 08:35:39 +02:00
}
2020-04-05 13:48:00 +02:00
2018-08-21 08:59:57 +02:00
// Kraken emit one request only after first GetRates
2019-03-05 09:09:17 +01:00
factory . Providers [ "kraken" ] . GetRatesAsync ( default ) . GetAwaiter ( ) . GetResult ( ) ;
2018-05-04 08:35:39 +02:00
}
2019-12-26 06:22:36 +01:00
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
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" ) ) ;
2020-01-18 11:42:46 +01:00
var provider2 = new BackgroundFetcherRateProvider ( provider . Inner )
2019-12-26 08:04:39 +01:00
{
2020-06-24 03:34:09 +02:00
RefreshRate = provider . RefreshRate ,
ValidatyTime = provider . ValidatyTime
2019-12-26 08:04:39 +01:00
} ;
2019-12-26 06:22:36 +01:00
using ( var cts = new CancellationTokenSource ( ) )
{
cts . Cancel ( ) ;
// Should throw
2020-04-05 13:48:00 +02:00
await Assert . ThrowsAsync < OperationCanceledException > ( async ( ) = >
await provider2 . GetRatesAsync ( cts . Token ) ) ;
2019-12-26 06:22:36 +01:00
}
2020-04-05 13:48:00 +02:00
2019-12-26 06:22:36 +01:00
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 ) ;
}
2020-04-05 13:48:00 +02:00
2019-12-26 06:22:36 +01:00
Assert . Equal ( provider . NextUpdate , provider2 . NextUpdate ) ;
Assert . NotEqual ( provider . LastRequested , provider2 . LastRequested ) ;
2019-12-26 08:04:39 +01:00
Assert . Equal ( provider . Expiration , provider2 . Expiration ) ;
2019-12-26 06:22:36 +01:00
var str = JsonConvert . SerializeObject ( state ) ;
var state2 = JsonConvert . DeserializeObject < BackgroundFetcherState > ( str ) ;
var str2 = JsonConvert . SerializeObject ( state2 ) ;
Assert . Equal ( str , str2 ) ;
}
2019-10-06 15:49:28 +02:00
[Fact(Timeout = TestTimeout)]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2018-05-02 20:40:10 +02:00
public void CanGetRateCryptoCurrenciesByDefault ( )
{
var provider = new BTCPayNetworkProvider ( NetworkType . Mainnet ) ;
2018-08-22 09:53:40 +02:00
var factory = CreateBTCPayRateFactory ( ) ;
var fetcher = new RateFetcher ( factory ) ;
2018-05-02 20:40:10 +02:00
var pairs =
2020-04-05 13:48:00 +02:00
provider . GetAll ( )
2018-05-02 20:40:10 +02:00
. Select ( c = > new CurrencyPair ( c . CryptoCode , "USD" ) )
. ToHashSet ( ) ;
var rules = new StoreBlob ( ) . GetDefaultRateRules ( provider ) ;
2019-03-05 09:09:17 +01:00
var result = fetcher . FetchRates ( pairs , rules , default ) ;
2018-05-04 08:35:39 +02:00
foreach ( var value in result )
2018-05-02 20:40:10 +02:00
{
var rateResult = value . Value . GetAwaiter ( ) . GetResult ( ) ;
2018-10-31 05:31:03 +01:00
Logs . Tester . LogInformation ( $"Testing {value.Key.ToString()}" ) ;
Assert . True ( rateResult . BidAsk ! = null , $"Impossible to get the rate {rateResult.EvaluatedRule}" ) ;
2018-05-02 20:40:10 +02:00
}
}
2018-11-02 06:26:13 +01:00
public static RateProviderFactory CreateBTCPayRateFactory ( )
2018-05-04 08:35:39 +02:00
{
2020-01-18 11:23:40 +01:00
return new RateProviderFactory ( TestUtils . CreateHttpFactory ( ) ) ;
2018-08-22 17:24:33 +02:00
}
class SpyRateProvider : IRateProvider
{
public bool Hit { get ; set ; }
2020-04-05 13:48:00 +02:00
2020-01-17 10:11:05 +01:00
public Task < PairRate [ ] > GetRatesAsync ( CancellationToken cancellationToken )
2018-08-22 17:24:33 +02:00
{
Hit = true ;
2020-01-17 10:11:05 +01:00
var rates = new List < PairRate > ( ) ;
rates . Add ( new PairRate ( CurrencyPair . Parse ( "BTC_USD" ) , new BidAsk ( 5000 ) ) ) ;
return Task . FromResult ( rates . ToArray ( ) ) ;
2018-08-22 17:24:33 +02:00
}
public void AssertHit ( )
{
Assert . True ( Hit , "Should have hit the provider" ) ;
Hit = false ;
}
2020-04-05 13:48:00 +02:00
2018-08-22 17:24:33 +02:00
public void AssertNotHit ( )
{
Assert . False ( Hit , "Should have not hit the provider" ) ;
Hit = false ;
}
2018-05-04 08:35:39 +02:00
}
2019-10-06 15:49:28 +02:00
[Fact(Timeout = TestTimeout)]
2018-11-07 14:29:35 +01:00
[Trait("Integration", "Integration")]
public async Task CheckLogsRoute ( )
{
using ( var tester = ServerTester . Create ( ) )
{
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2018-11-07 14:29:35 +01:00
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
2018-11-30 09:34:43 +01:00
2018-11-07 14:29:35 +01:00
var serverController = user . GetController < ServerController > ( ) ;
2020-04-05 13:48:00 +02:00
var vm = Assert . IsType < LogsViewModel > (
Assert . IsType < ViewResult > ( await serverController . LogsView ( ) ) . Model ) ;
2018-11-07 14:29:35 +01:00
}
2018-11-30 09:34:43 +01:00
}
2019-03-01 06:33:32 +01:00
[Fact]
[Trait("Fast", "Fast")]
public async Task CanExpandExternalConnectionString ( )
{
var unusedUri = new Uri ( "https://toto.com" ) ;
Assert . True ( ExternalConnectionString . TryParse ( "server=/test" , out var connStr , out var error ) ) ;
2020-04-05 13:48:00 +02:00
var expanded = await connStr . Expand ( new Uri ( "https://toto.com" ) , ExternalServiceTypes . Charge ,
NetworkType . Mainnet ) ;
2019-03-01 06:33:32 +01:00
Assert . Equal ( new Uri ( "https://toto.com/test" ) , expanded . Server ) ;
2020-04-05 13:48:00 +02:00
expanded = await connStr . Expand ( new Uri ( "http://toto.onion" ) , ExternalServiceTypes . Charge ,
NetworkType . Mainnet ) ;
2019-03-01 06:33:32 +01:00
Assert . Equal ( new Uri ( "http://toto.onion/test" ) , expanded . Server ) ;
2020-04-05 13:48:00 +02:00
await Assert . ThrowsAsync < SecurityException > ( ( ) = >
connStr . Expand ( new Uri ( "http://toto.com" ) , ExternalServiceTypes . Charge , NetworkType . Mainnet ) ) ;
2019-06-10 11:16:12 +02:00
await connStr . Expand ( new Uri ( "http://toto.com" ) , ExternalServiceTypes . Charge , NetworkType . Testnet ) ;
2019-03-01 06:33:32 +01:00
// Make sure absolute paths are not expanded
Assert . True ( ExternalConnectionString . TryParse ( "server=https://tow/test" , out connStr , out error ) ) ;
2020-04-05 13:48:00 +02:00
expanded = await connStr . Expand ( new Uri ( "https://toto.com" ) , ExternalServiceTypes . Charge ,
NetworkType . Mainnet ) ;
2019-03-01 06:33:32 +01:00
Assert . Equal ( new Uri ( "https://tow/test" ) , expanded . Server ) ;
2019-03-17 12:49:26 +01:00
2019-03-01 06:33:32 +01:00
// Error if directory not exists
2020-04-05 13:48:00 +02:00
Assert . True ( ExternalConnectionString . TryParse ( $"server={unusedUri};macaroondirectorypath=pouet" ,
out connStr , out error ) ) ;
await Assert . ThrowsAsync < DirectoryNotFoundException > ( ( ) = >
connStr . Expand ( unusedUri , ExternalServiceTypes . LNDGRPC , NetworkType . Mainnet ) ) ;
await Assert . ThrowsAsync < DirectoryNotFoundException > ( ( ) = >
connStr . Expand ( unusedUri , ExternalServiceTypes . LNDRest , NetworkType . Mainnet ) ) ;
2019-06-10 11:16:12 +02:00
await connStr . Expand ( unusedUri , ExternalServiceTypes . Charge , NetworkType . Mainnet ) ;
2019-03-01 06:33:32 +01:00
var macaroonDirectory = CreateDirectory ( ) ;
2020-04-05 13:48:00 +02:00
Assert . True ( ExternalConnectionString . TryParse (
$"server={unusedUri};macaroondirectorypath={macaroonDirectory}" , out connStr , out error ) ) ;
2019-06-10 11:16:12 +02:00
await connStr . Expand ( unusedUri , ExternalServiceTypes . LNDGRPC , NetworkType . Mainnet ) ;
expanded = await connStr . Expand ( unusedUri , ExternalServiceTypes . LNDRest , NetworkType . Mainnet ) ;
2019-03-01 06:33:32 +01:00
Assert . NotNull ( expanded . Macaroons ) ;
Assert . Null ( expanded . MacaroonFilePath ) ;
Assert . Null ( expanded . Macaroons . AdminMacaroon ) ;
Assert . Null ( expanded . Macaroons . InvoiceMacaroon ) ;
Assert . Null ( expanded . Macaroons . ReadonlyMacaroon ) ;
2020-06-24 03:34:09 +02:00
File . WriteAllBytes ( $"{macaroonDirectory}/admin.macaroon" , new byte [ ] { 0xaa } ) ;
File . WriteAllBytes ( $"{macaroonDirectory}/invoice.macaroon" , new byte [ ] { 0xab } ) ;
File . WriteAllBytes ( $"{macaroonDirectory}/readonly.macaroon" , new byte [ ] { 0xac } ) ;
2019-06-10 11:16:12 +02:00
expanded = await connStr . Expand ( unusedUri , ExternalServiceTypes . LNDRest , NetworkType . Mainnet ) ;
2019-03-01 06:33:32 +01:00
Assert . NotNull ( expanded . Macaroons . AdminMacaroon ) ;
Assert . NotNull ( expanded . Macaroons . InvoiceMacaroon ) ;
Assert . Equal ( "ab" , expanded . Macaroons . InvoiceMacaroon . Hex ) ;
Assert . Equal ( 0xab , expanded . Macaroons . InvoiceMacaroon . Bytes [ 0 ] ) ;
Assert . NotNull ( expanded . Macaroons . ReadonlyMacaroon ) ;
2020-04-05 13:48:00 +02:00
Assert . True ( ExternalConnectionString . TryParse (
$"server={unusedUri};cookiefilepath={macaroonDirectory}/charge.cookie" , out connStr , out error ) ) ;
2019-03-01 06:33:32 +01:00
File . WriteAllText ( $"{macaroonDirectory}/charge.cookie" , "apitoken" ) ;
2019-06-10 11:16:12 +02:00
expanded = await connStr . Expand ( unusedUri , ExternalServiceTypes . Charge , NetworkType . Mainnet ) ;
2019-03-01 06:33:32 +01:00
Assert . Equal ( "apitoken" , expanded . APIToken ) ;
}
private string CreateDirectory ( [ CallerMemberName ] string caller = null )
{
var name = $"{caller}-{NBitcoin.RandomUtils.GetUInt32()}" ;
Directory . CreateDirectory ( name ) ;
return name ;
}
2020-01-19 14:10:05 +01:00
[Fact(Timeout = TestTimeout)]
[Trait("Fast", "Fast")]
public async Task CanCreateSqlitedb ( )
{
if ( File . Exists ( "temp.db" ) )
File . Delete ( "temp.db" ) ;
// This test sqlite can migrate
var builder = new DbContextOptionsBuilder < ApplicationDbContext > ( ) ;
builder . UseSqlite ( "Data Source=temp.db" ) ;
await new ApplicationDbContext ( builder . Options ) . Database . MigrateAsync ( ) ;
}
2019-10-06 15:49:28 +02:00
[Fact(Timeout = TestTimeout)]
2020-03-19 11:11:15 +01:00
[Trait("Fast", "Fast")]
public void CanUsePermission ( )
{
2020-04-05 13:48:00 +02:00
Assert . True ( Permission . Create ( Policies . CanModifyServerSettings )
. Contains ( Permission . Create ( Policies . CanModifyServerSettings ) ) ) ;
Assert . True ( Permission . Create ( Policies . CanModifyProfile )
. Contains ( Permission . Create ( Policies . CanViewProfile ) ) ) ;
Assert . True ( Permission . Create ( Policies . CanModifyStoreSettings )
. Contains ( Permission . Create ( Policies . CanViewStoreSettings ) ) ) ;
Assert . False ( Permission . Create ( Policies . CanViewStoreSettings )
. Contains ( Permission . Create ( Policies . CanModifyStoreSettings ) ) ) ;
Assert . False ( Permission . Create ( Policies . CanModifyServerSettings )
. Contains ( Permission . Create ( Policies . CanModifyStoreSettings ) ) ) ;
Assert . True ( Permission . Create ( Policies . Unrestricted )
. Contains ( Permission . Create ( Policies . CanModifyStoreSettings ) ) ) ;
Assert . True ( Permission . Create ( Policies . Unrestricted )
. Contains ( Permission . Create ( Policies . CanModifyStoreSettings , "abc" ) ) ) ;
Assert . True ( Permission . Create ( Policies . CanViewStoreSettings )
. Contains ( Permission . Create ( Policies . CanViewStoreSettings , "abcd" ) ) ) ;
Assert . False ( Permission . Create ( Policies . CanModifyStoreSettings , "abcd" )
. Contains ( Permission . Create ( Policies . CanModifyStoreSettings ) ) ) ;
2020-03-19 11:11:15 +01:00
}
[Fact(Timeout = TestTimeout)]
2018-10-28 12:59:59 +01:00
[Trait("Fast", "Fast")]
2017-10-27 10:53:04 +02:00
public void CheckRatesProvider ( )
{
2018-08-22 17:24:33 +02:00
var spy = new SpyRateProvider ( ) ;
2020-01-17 06:42:02 +01:00
RateRules . TryParse ( "X_X = bittrex(X_X);" , out var rateRules ) ;
2018-05-02 20:32:42 +02:00
2018-08-22 09:53:40 +02:00
var factory = CreateBTCPayRateFactory ( ) ;
2018-08-22 17:24:33 +02:00
factory . Providers . Clear ( ) ;
var fetcher = new RateFetcher ( factory ) ;
factory . Providers . Clear ( ) ;
2020-01-18 11:42:46 +01:00
var fetch = new BackgroundFetcherRateProvider ( spy ) ;
2018-08-25 08:49:04 +02:00
fetch . DoNotAutoFetchIfExpired = true ;
2018-08-22 17:24:33 +02:00
factory . Providers . Add ( "bittrex" , fetch ) ;
2020-04-05 13:48:00 +02:00
var fetchedRate = fetcher . FetchRate ( CurrencyPair . Parse ( "BTC_USD" ) , rateRules , default ) . GetAwaiter ( )
. GetResult ( ) ;
2018-08-22 17:24:33 +02:00
spy . AssertHit ( ) ;
2019-03-05 09:09:17 +01:00
fetchedRate = fetcher . FetchRate ( CurrencyPair . Parse ( "BTC_USD" ) , rateRules , default ) . GetAwaiter ( ) . GetResult ( ) ;
2018-08-22 17:24:33 +02:00
spy . AssertNotHit ( ) ;
2019-03-05 09:09:17 +01:00
fetch . UpdateIfNecessary ( default ) . GetAwaiter ( ) . GetResult ( ) ;
2018-08-22 17:24:33 +02:00
spy . AssertNotHit ( ) ;
fetch . RefreshRate = TimeSpan . FromSeconds ( 1.0 ) ;
Thread . Sleep ( 1020 ) ;
2019-03-05 09:09:17 +01:00
fetchedRate = fetcher . FetchRate ( CurrencyPair . Parse ( "BTC_USD" ) , rateRules , default ) . GetAwaiter ( ) . GetResult ( ) ;
2018-08-22 17:24:33 +02:00
spy . AssertNotHit ( ) ;
2018-08-23 06:47:56 +02:00
fetch . ValidatyTime = TimeSpan . FromSeconds ( 1.0 ) ;
2019-03-05 09:09:17 +01:00
fetch . UpdateIfNecessary ( default ) . GetAwaiter ( ) . GetResult ( ) ;
2018-08-22 17:24:33 +02:00
spy . AssertHit ( ) ;
2019-03-05 09:09:17 +01:00
fetch . GetRatesAsync ( default ) . GetAwaiter ( ) . GetResult ( ) ;
2018-08-23 06:47:56 +02:00
Thread . Sleep ( 1000 ) ;
2019-03-05 09:09:17 +01:00
Assert . Throws < InvalidOperationException > ( ( ) = > fetch . GetRatesAsync ( default ) . GetAwaiter ( ) . GetResult ( ) ) ;
2017-10-27 10:53:04 +02:00
}
2019-05-08 17:40:30 +02:00
[Fact]
[Trait("Fast", "Fast")]
public void ParseDerivationSchemeSettings ( )
{
2019-05-29 11:43:50 +02:00
var mainnet = new BTCPayNetworkProvider ( NetworkType . Mainnet ) . GetNetwork < BTCPayNetwork > ( "BTC" ) ;
2020-04-05 13:48:00 +02:00
var root = new Mnemonic (
"usage fever hen zero slide mammal silent heavy donate budget pulse say brain thank sausage brand craft about save attract muffin advance illegal cabbage" )
. DeriveExtKey ( ) ;
2020-06-22 08:39:29 +02:00
Assert . True ( DerivationSchemeSettings . TryParseFromWalletFile (
2020-04-05 13:48:00 +02:00
"{\"keystore\": {\"ckcc_xpub\": \"xpub661MyMwAqRbcGVBsTGeNZN6QGVHmMHLdSA4FteGsRrEriu4pnVZMZWnruFFFXkMnyoBjyHndD3Qwcfz4MPzBUxjSevweNFQx7SAYZATtcDw\", \"xpub\": \"ypub6WWc2gWwHbdnAAyJDnR4SPL1phRh7REqrPBfZeizaQ1EmTshieRXJC3Z5YoU4wkcdKHEjQGkh6AYEzCQC1Kz3DNaWSwdc1pc8416hAjzqyD\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/49'/0'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}" ,
mainnet , out var settings ) ) ;
2019-05-12 17:35:06 +02:00
Assert . Equal ( root . GetPublicKey ( ) . GetHDFingerPrint ( ) , settings . AccountKeySettings [ 0 ] . RootFingerprint ) ;
2020-04-05 13:48:00 +02:00
Assert . Equal ( settings . AccountKeySettings [ 0 ] . RootFingerprint ,
HDFingerprint . TryParse ( "8bafd160" , out var hd ) ? hd : default ) ;
2019-05-08 17:40:30 +02:00
Assert . Equal ( "Coldcard Import 0x60d1af8b" , settings . Label ) ;
2019-05-12 17:35:06 +02:00
Assert . Equal ( "49'/0'/0'" , settings . AccountKeySettings [ 0 ] . AccountKeyPath . ToString ( ) ) ;
2020-04-05 13:48:00 +02:00
Assert . Equal (
"ypub6WWc2gWwHbdnAAyJDnR4SPL1phRh7REqrPBfZeizaQ1EmTshieRXJC3Z5YoU4wkcdKHEjQGkh6AYEzCQC1Kz3DNaWSwdc1pc8416hAjzqyD" ,
settings . AccountOriginal ) ;
Assert . Equal ( root . Derive ( new KeyPath ( "m/49'/0'/0'" ) ) . Neuter ( ) . PubKey . WitHash . ScriptPubKey . Hash . ScriptPubKey ,
settings . AccountDerivation . GetDerivation ( ) . ScriptPubKey ) ;
2019-05-09 11:38:25 +02:00
2019-05-29 11:43:50 +02:00
var testnet = new BTCPayNetworkProvider ( NetworkType . Testnet ) . GetNetwork < BTCPayNetwork > ( "BTC" ) ;
2019-05-09 11:38:25 +02:00
// Should be legacy
2020-06-22 08:39:29 +02:00
Assert . True ( DerivationSchemeSettings . TryParseFromWalletFile (
2020-04-05 13:48:00 +02:00
"{\"keystore\": {\"ckcc_xpub\": \"tpubD6NzVbkrYhZ4YHNiuTdTmHRmbcPRLfqgyneZFCL1mkzkUBjXriQShxTh9HL34FK2mhieasJVk9EzJrUfkFqRNQBjiXgx3n5BhPkxKBoFmaS\", \"xpub\": \"tpubDDWYqT3P24znfsaGX7kZcQhNc5LAjnQiKQvUCHF2jS6dsgJBRtymopEU5uGpMaR5YChjuiExZG1X2aTbqXkp82KqH5qnqwWHp6EWis9ZvKr\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/44'/1'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}" ,
testnet , out settings ) ) ;
2019-05-09 11:38:25 +02:00
Assert . True ( settings . AccountDerivation is DirectDerivationStrategy s & & ! s . Segwit ) ;
// Should be segwit p2sh
2020-06-22 08:39:29 +02:00
Assert . True ( DerivationSchemeSettings . TryParseFromWalletFile (
2020-04-05 13:48:00 +02:00
"{\"keystore\": {\"ckcc_xpub\": \"tpubD6NzVbkrYhZ4YHNiuTdTmHRmbcPRLfqgyneZFCL1mkzkUBjXriQShxTh9HL34FK2mhieasJVk9EzJrUfkFqRNQBjiXgx3n5BhPkxKBoFmaS\", \"xpub\": \"upub5DSddA9NoRUyJrQ4p86nsCiTSY7kLHrSxx3joEJXjHd4HPARhdXUATuk585FdWPVC2GdjsMePHb6BMDmf7c6KG4K4RPX6LVqBLtDcWpQJmh\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/49'/1'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}" ,
testnet , out settings ) ) ;
Assert . True ( settings . AccountDerivation is P2SHDerivationStrategy p & &
p . Inner is DirectDerivationStrategy s2 & & s2 . Segwit ) ;
2019-05-09 11:38:25 +02:00
// Should be segwit
2020-06-22 08:39:29 +02:00
Assert . True ( DerivationSchemeSettings . TryParseFromWalletFile (
2020-04-05 13:48:00 +02:00
"{\"keystore\": {\"ckcc_xpub\": \"tpubD6NzVbkrYhZ4YHNiuTdTmHRmbcPRLfqgyneZFCL1mkzkUBjXriQShxTh9HL34FK2mhieasJVk9EzJrUfkFqRNQBjiXgx3n5BhPkxKBoFmaS\", \"xpub\": \"vpub5YjYxTemJ39tFRnuAhwduyxG2tKGjoEpmvqVQRPqdYrqa6YGoeSzBtHXaJUYB19zDbXs3JjbEcVWERjQBPf9bEfUUMZNMv1QnMyHV8JPqyf\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/84'/1'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}" ,
testnet , out settings ) ) ;
2019-05-09 11:38:25 +02:00
Assert . True ( settings . AccountDerivation is DirectDerivationStrategy s3 & & s3 . Segwit ) ;
2019-05-08 17:40:30 +02:00
}
2020-02-13 06:44:31 +01:00
2019-10-06 15:49:28 +02:00
[Fact(Timeout = TestTimeout)]
2019-05-02 14:01:08 +02:00
[Trait("Integration", "Integration")]
public async Task CanLoginWithNoSecondaryAuthSystemsOrRequestItWhenAdded ( )
{
using ( var tester = ServerTester . Create ( ) )
{
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2019-05-02 14:01:08 +02:00
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
2020-04-05 13:48:00 +02:00
var accountController = tester . PayTester . GetController < AccountController > ( ) ;
2019-05-02 14:01:08 +02:00
2020-04-05 13:48:00 +02:00
//no 2fa or u2f enabled, login should work
Assert . Equal ( nameof ( HomeController . Index ) ,
Assert . IsType < RedirectToActionResult > ( await accountController . Login ( new LoginViewModel ( )
{
2020-06-24 03:34:09 +02:00
Email = user . RegisterDetails . Email ,
Password = user . RegisterDetails . Password
2020-04-05 13:48:00 +02:00
} ) ) . ActionName ) ;
2019-05-02 14:01:08 +02:00
2020-04-05 13:48:00 +02:00
var manageController = user . GetController < ManageController > ( ) ;
2020-02-13 06:44:31 +01:00
2020-04-05 13:48:00 +02:00
//by default no u2f devices available
Assert . Empty ( Assert
. IsType < U2FAuthenticationViewModel > ( Assert
. IsType < ViewResult > ( await manageController . U2FAuthentication ( ) ) . Model ) . Devices ) ;
var addRequest =
Assert . IsType < AddU2FDeviceViewModel > ( Assert
. IsType < ViewResult > ( manageController . AddU2FDevice ( "label" ) ) . Model ) ;
//name should match the one provided in beginning
Assert . Equal ( "label" , addRequest . Name ) ;
2019-11-07 10:35:47 +01:00
//sending an invalid response model back to server, should error out
Assert . IsType < RedirectToActionResult > ( await manageController . AddU2FDevice ( addRequest ) ) ;
var statusModel = manageController . TempData . GetStatusMessageModel ( ) ;
2020-04-05 13:48:00 +02:00
Assert . Equal ( StatusMessageModel . StatusSeverity . Error , statusModel . Severity ) ;
var contextFactory = tester . PayTester . GetService < ApplicationDbContextFactory > ( ) ;
//add a fake u2f device in db directly since emulating a u2f device is hard and annoying
using ( var context = contextFactory . CreateContext ( ) )
{
var newDevice = new U2FDevice ( )
{
Id = Guid . NewGuid ( ) . ToString ( ) ,
Name = "fake" ,
Counter = 0 ,
KeyHandle = UTF8Encoding . UTF8 . GetBytes ( "fake" ) ,
PublicKey = UTF8Encoding . UTF8 . GetBytes ( "fake" ) ,
AttestationCert = UTF8Encoding . UTF8 . GetBytes ( "fake" ) ,
ApplicationUserId = user . UserId
} ;
await context . U2FDevices . AddAsync ( newDevice ) ;
await context . SaveChangesAsync ( ) ;
Assert . NotNull ( newDevice . Id ) ;
Assert . NotEmpty ( Assert
. IsType < U2FAuthenticationViewModel > ( Assert
. IsType < ViewResult > ( await manageController . U2FAuthentication ( ) ) . Model ) . Devices ) ;
}
//check if we are showing the u2f login screen now
var secondLoginResult = Assert . IsType < ViewResult > ( await accountController . Login ( new LoginViewModel ( )
{
2020-06-24 03:34:09 +02:00
Email = user . RegisterDetails . Email ,
Password = user . RegisterDetails . Password
2020-04-05 13:48:00 +02:00
} ) ) ;
Assert . Equal ( "SecondaryLogin" , secondLoginResult . ViewName ) ;
var vm = Assert . IsType < SecondaryLoginViewModel > ( secondLoginResult . Model ) ;
//2fa was never enabled for user so this should be empty
Assert . Null ( vm . LoginWith2FaViewModel ) ;
Assert . NotNull ( vm . LoginWithU2FViewModel ) ;
2019-05-02 14:01:08 +02:00
}
}
2020-02-13 06:44:31 +01:00
2020-06-04 08:53:55 +02:00
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async void CheckOnionlocationForNonOnionHtmlRequests ( )
{
using ( var tester = ServerTester . Create ( ) )
{
await tester . StartAsync ( ) ;
var url = tester . PayTester . ServerUri . AbsoluteUri ;
// check onion location is present for HTML page request
using var htmlRequest = new HttpRequestMessage ( HttpMethod . Get , new Uri ( url ) ) ;
htmlRequest . Headers . TryAddWithoutValidation ( "Accept" , "text/html,*/*" ) ;
2020-06-05 11:38:55 +02:00
var htmlResponse = await tester . PayTester . HttpClient . SendAsync ( htmlRequest ) ;
2020-06-04 08:53:55 +02:00
htmlResponse . EnsureSuccessStatusCode ( ) ;
Assert . True ( htmlResponse . Headers . TryGetValues ( "Onion-Location" , out var onionLocation ) ) ;
Assert . StartsWith ( "http://wsaxew3qa5ljfuenfebmaf3m5ykgatct3p6zjrqwoouj3foererde3id.onion" , onionLocation . FirstOrDefault ( ) ? ? "no-onion-location-header" ) ;
// no onion location for other mime types
using var otherRequest = new HttpRequestMessage ( HttpMethod . Get , new Uri ( url ) ) ;
otherRequest . Headers . TryAddWithoutValidation ( "Accept" , "*/*" ) ;
2020-06-05 11:38:55 +02:00
var otherResponse = await tester . PayTester . HttpClient . SendAsync ( otherRequest ) ;
2020-06-04 08:53:55 +02:00
otherResponse . EnsureSuccessStatusCode ( ) ;
Assert . False ( otherResponse . Headers . Contains ( "Onion-Location" ) ) ;
}
}
2017-10-27 10:53:04 +02:00
private static bool IsMapped ( Invoice invoice , ApplicationDbContext ctx )
{
2018-04-10 12:07:57 +02:00
var h = BitcoinAddress . Create ( invoice . BitcoinAddress , Network . RegTest ) . ScriptPubKey . Hash . ToString ( ) ;
2020-04-05 13:48:00 +02:00
return ( ctx . AddressInvoices . Where ( i = > i . InvoiceDataId = = invoice . Id ) . ToArrayAsync ( ) . GetAwaiter ( )
. GetResult ( ) )
2019-10-06 08:47:46 +02:00
. Where ( i = > i . GetAddress ( ) = = h ) . Any ( ) ;
2017-10-27 10:53:04 +02:00
}
2020-07-31 03:52:33 +02:00
class MockVersionFetcher : IVersionFetcher
{
public const string MOCK_NEW_VERSION = "9.9.9.9" ;
public Task < string > Fetch ( CancellationToken cancellation )
{
return Task . FromResult ( MOCK_NEW_VERSION ) ;
}
}
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async Task CanCheckForNewVersion ( )
{
using ( var tester = ServerTester . Create ( newDb : true ) )
{
await tester . StartAsync ( ) ;
var acc = tester . NewAccount ( ) ;
acc . GrantAccess ( true ) ;
var settings = tester . PayTester . GetService < SettingsRepository > ( ) ;
await settings . UpdateSetting < PoliciesSettings > ( new PoliciesSettings ( ) { CheckForNewVersions = true } ) ;
2020-07-31 07:20:17 +02:00
var mockEnv = tester . PayTester . GetService < BTCPayServerEnvironment > ( ) ;
var mockSender = tester . PayTester . GetService < Services . Notifications . NotificationSender > ( ) ;
2020-07-31 03:52:33 +02:00
2020-07-31 07:20:17 +02:00
var svc = new NewVersionCheckerHostedService ( settings , mockEnv , mockSender , new MockVersionFetcher ( ) ) ;
2020-07-31 03:52:33 +02:00
await svc . ProcessVersionCheck ( ) ;
// since last version present in database was null, it should've been updated with version mock returned
var lastVersion = await settings . GetSettingAsync < NewVersionCheckerDataHolder > ( ) ;
Assert . Equal ( MockVersionFetcher . MOCK_NEW_VERSION , lastVersion . LastVersion ) ;
// we should also have notification in UI
var ctrl = acc . GetController < NotificationsController > ( ) ;
var newVersion = MockVersionFetcher . MOCK_NEW_VERSION ;
var vm = Assert . IsType < Models . NotificationViewModels . IndexViewModel > (
Assert . IsType < ViewResult > ( ctrl . Index ( ) ) . Model ) ;
Assert . True ( vm . Skip = = 0 ) ;
Assert . True ( vm . Count = = 50 ) ;
Assert . True ( vm . Total = = 1 ) ;
Assert . True ( vm . Items . Count = = 1 ) ;
var fn = vm . Items . First ( ) ;
var now = DateTimeOffset . UtcNow ;
Assert . True ( fn . Created > = now . AddSeconds ( - 3 ) ) ;
Assert . True ( fn . Created < = now ) ;
Assert . Equal ( $"New version {newVersion} released!" , fn . Body ) ;
Assert . Equal ( $"https://github.com/btcpayserver/btcpayserver/releases/tag/v{newVersion}" , fn . ActionLink ) ;
Assert . False ( fn . Seen ) ;
}
}
2017-10-27 10:53:04 +02:00
}
2017-09-13 08:47:34 +02:00
}