2018-12-17 16:25:17 +01:00
using BTCPayServer.Tests.Logging ;
2017-09-13 08:47:34 +02:00
using System.Linq ;
using NBitcoin ;
using NBitcoin.DataEncoders ;
using NBitcoin.Payment ;
using NBitpayClient ;
using System ;
using System.Threading ;
using Xunit ;
using Xunit.Abstractions ;
using Xunit.Sdk ;
2017-10-20 21:06:37 +02:00
using BTCPayServer.Services.Invoices ;
2017-09-25 18:31:43 +02:00
using Newtonsoft.Json ;
using System.IO ;
using Newtonsoft.Json.Linq ;
2017-10-11 05:20:44 +02:00
using BTCPayServer.Controllers ;
using Microsoft.AspNetCore.Mvc ;
2017-10-13 11:06:46 +02:00
using BTCPayServer.Authentication ;
2017-10-20 21:00:38 +02:00
using System.Diagnostics ;
2017-10-24 18:41:01 +02:00
using Microsoft.EntityFrameworkCore.Extensions ;
using BTCPayServer.Data ;
using Microsoft.EntityFrameworkCore ;
2017-10-27 04:39:11 +02:00
using BTCPayServer.Services.Rates ;
using Microsoft.Extensions.Caching.Memory ;
2018-01-09 02:54:19 +01:00
using System.Collections.Generic ;
2018-01-17 07:59:31 +01:00
using BTCPayServer.Models.StoreViewModels ;
2018-01-18 10:12:01 +01:00
using System.Threading.Tasks ;
2018-02-17 05:18:16 +01:00
using System.Globalization ;
2018-02-18 18:38:03 +01:00
using BTCPayServer.Payments ;
2018-02-19 10:54:21 +01:00
using BTCPayServer.Payments.Bitcoin ;
using BTCPayServer.HostedServices ;
2018-02-25 16:48:12 +01:00
using BTCPayServer.Payments.Lightning ;
2018-04-03 09:53:55 +02:00
using BTCPayServer.Models.AppViewModels ;
using BTCPayServer.Services.Apps ;
2018-04-29 11:28:04 +02:00
using BTCPayServer.Services.Stores ;
using System.Net.Http ;
using System.Text ;
2018-05-29 17:12:07 +02:00
using BTCPayServer.Models ;
2018-05-02 20:32:42 +02:00
using BTCPayServer.Rating ;
2018-05-14 09:32:04 +02:00
using BTCPayServer.Validation ;
2018-05-04 08:35:39 +02:00
using ExchangeSharp ;
2018-07-08 13:58:37 +02:00
using System.Security.Cryptography.X509Certificates ;
2018-08-30 04:50:39 +02:00
using BTCPayServer.Lightning ;
2018-10-26 16:07:39 +02:00
using BTCPayServer.Models.WalletViewModels ;
using System.Security.Claims ;
2018-11-27 07:13:09 +01:00
using BTCPayServer.Models.InvoicingModels ;
2018-11-07 14:29:35 +01:00
using BTCPayServer.Models.ServerViewModels ;
2018-10-26 16:07:39 +02:00
using BTCPayServer.Security ;
2018-10-28 15:43:48 +01:00
using NBXplorer.Models ;
2018-11-07 14:29:35 +01:00
using RatesViewModel = BTCPayServer . Models . StoreViewModels . RatesViewModel ;
2018-12-07 06:34:07 +01:00
using NBitpayClient.Extensions ;
2019-01-04 16:37:09 +01:00
using BTCPayServer.Services ;
using System.Text.RegularExpressions ;
2019-01-06 10:12:45 +01:00
using BTCPayServer.Events ;
2019-03-01 06:33:32 +01:00
using BTCPayServer.Configuration ;
using System.Security ;
using System.Runtime.CompilerServices ;
2019-03-17 16:03:02 +01:00
using System.Net ;
2019-05-02 14:01:08 +02:00
using BTCPayServer.Models.AccountViewModels ;
using BTCPayServer.Services.U2F.Models ;
2019-05-09 09:11:09 +02:00
using Microsoft.AspNetCore.Http.Internal ;
using Microsoft.AspNetCore.Http ;
2019-05-29 16:33:31 +02:00
using Microsoft.Extensions.DependencyInjection ;
2019-05-09 11:38:25 +02:00
using NBXplorer.DerivationStrategy ;
2017-09-13 08:47:34 +02:00
namespace BTCPayServer.Tests
{
2017-10-27 10:53:04 +02:00
public class UnitTest1
{
public UnitTest1 ( ITestOutputHelper helper )
{
Logs . Tester = new XUnitLog ( helper ) { Name = "Tests" } ;
Logs . LogProvider = new XUnitLogProvider ( helper ) ;
}
2019-09-02 09:32:38 +02:00
[Fact]
[Trait("Fast", "Fast")]
public async Task CheckNoDeadLink ( )
{
var views = Path . Combine ( LanguageService . TryGetSolutionDirectoryInfo ( ) . FullName , "BTCPayServer" , "Views" ) ;
var viewFiles = Directory . EnumerateFiles ( views , "*.cshtml" , SearchOption . AllDirectories ) . ToArray ( ) ;
Assert . NotEmpty ( viewFiles ) ;
2019-09-02 11:05:40 +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 ) ) ;
}
await Task . WhenAll ( checkLinks ) ;
}
private static async Task CheckLinks ( Regex regex , HttpClient httpClient , string file )
{
List < Task > checkLinks = new List < Task > ( ) ;
var text = await File . ReadAllTextAsync ( file ) ;
foreach ( var match in regex . Matches ( text ) . OfType < Match > ( ) )
{
checkLinks . Add ( AssertLinkNotDead ( httpClient , match , file ) ) ;
}
await Task . WhenAll ( checkLinks ) ;
}
private static async Task AssertLinkNotDead ( HttpClient httpClient , Match match , string file )
{
var url = match . Groups [ 1 ] . Value ;
try
{
Assert . Equal ( HttpStatusCode . OK , ( await httpClient . GetAsync ( url ) ) . StatusCode ) ;
Logs . Tester . LogInformation ( $"OK: {url} ({file})" ) ;
}
catch
{
Logs . Tester . LogInformation ( $"FAILED: {url} ({file})" ) ;
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" ) ) ;
2018-05-14 09:32:04 +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" ) ) ;
Assert . False ( attribute . IsValid ( 2 ) ) ;
Assert . False ( attribute . IsValid ( "http://" ) ) ;
Assert . False ( attribute . IsValid ( "httpdsadsa.com" ) ) ;
}
2018-11-30 09:34:43 +01:00
2018-02-19 10:54:21 +01:00
[Fact]
2018-10-28 12:59:59 +01:00
[Trait("Fast", "Fast")]
2018-02-19 10:54:21 +01:00
public void CanCalculateCryptoDue2 ( )
{
#pragma warning disable CS0618
2019-05-29 16:33:31 +02:00
var dummy = new Key ( ) . PubKey . GetAddress ( ScriptPubKeyType . Legacy , Network . RegTest ) . ToString ( ) ;
var networkProvider = new BTCPayNetworkProvider ( NetworkType . Regtest ) ;
var paymentMethodHandlerDictionary = new PaymentMethodHandlerDictionary ( new IPaymentMethodHandler [ ]
{
new BitcoinLikePaymentHandler ( null , networkProvider , null , null ) ,
new LightningLikePaymentHandler ( null , null , networkProvider , null ) ,
} ) ;
2019-06-04 03:11:52 +02:00
InvoiceEntity invoiceEntity = new InvoiceEntity ( ) ;
2018-02-19 10:54:21 +01:00
invoiceEntity . Payments = new System . Collections . Generic . List < PaymentEntity > ( ) ;
2019-05-29 16:33:31 +02:00
invoiceEntity . ProductInformation = new ProductInformation ( ) { Price = 100 } ;
2018-02-19 10:54:21 +01:00
PaymentMethodDictionary paymentMethods = new PaymentMethodDictionary ( ) ;
2019-05-29 16:33:31 +02:00
paymentMethods . Add ( new PaymentMethod ( ) { CryptoCode = "BTC" , Rate = 10513.44 m , } . SetPaymentMethodDetails (
new BTCPayServer . Payments . Bitcoin . BitcoinLikeOnChainPaymentMethod ( )
{
NextNetworkFee = Money . Coins ( 0.00000100 m ) , DepositAddress = dummy
} ) ) ;
paymentMethods . Add ( new PaymentMethod ( ) { CryptoCode = "LTC" , Rate = 216.79 m } . SetPaymentMethodDetails (
new BTCPayServer . Payments . Bitcoin . BitcoinLikeOnChainPaymentMethod ( )
{
NextNetworkFee = Money . Coins ( 0.00010000 m ) , DepositAddress = dummy
} ) ) ;
2018-02-19 10:54:21 +01:00
invoiceEntity . SetPaymentMethods ( paymentMethods ) ;
2019-06-07 06:24:36 +02:00
var btc = invoiceEntity . GetPaymentMethod ( new PaymentMethodId ( "BTC" , PaymentTypes . BTCLike ) ) ;
2018-02-19 10:54:21 +01:00
var accounting = btc . Calculate ( ) ;
2019-05-29 16:33:31 +02:00
invoiceEntity . Payments . Add (
new PaymentEntity ( )
{
Accounted = true ,
CryptoCode = "BTC" ,
2019-06-04 03:11:52 +02:00
NetworkFee = 0.00000100 m
2019-05-29 16:33:31 +02:00
}
. SetCryptoPaymentData ( new BitcoinLikePaymentData ( )
{
Output = new TxOut ( ) { Value = Money . Coins ( 0.00151263 m ) }
} ) ) ;
2018-02-19 10:54:21 +01:00
accounting = btc . Calculate ( ) ;
2019-05-29 16:33:31 +02:00
invoiceEntity . Payments . Add (
new PaymentEntity ( )
{
Accounted = true ,
CryptoCode = "BTC" ,
2019-06-04 03:11:52 +02:00
NetworkFee = 0.00000100 m
2019-05-29 16:33:31 +02:00
}
. SetCryptoPaymentData ( new BitcoinLikePaymentData ( )
{
Output = new TxOut ( ) { Value = accounting . Due }
} ) ) ;
2018-02-19 10:54:21 +01:00
accounting = btc . Calculate ( ) ;
Assert . Equal ( Money . Zero , accounting . Due ) ;
Assert . Equal ( Money . Zero , accounting . DueUncapped ) ;
2019-06-07 06:24:36 +02:00
var ltc = invoiceEntity . GetPaymentMethod ( new PaymentMethodId ( "LTC" , PaymentTypes . BTCLike ) ) ;
2018-02-19 10:54:21 +01:00
accounting = ltc . Calculate ( ) ;
Assert . Equal ( Money . Zero , accounting . Due ) ;
// LTC might have over paid due to BTC paying above what it should (round 1 satoshi up)
Assert . True ( accounting . DueUncapped < Money . Zero ) ;
2019-06-07 06:46:02 +02:00
var paymentMethod = InvoiceWatcher . GetNearestClearedPayment ( paymentMethods , out var accounting2 ) ;
2018-02-19 10:54:21 +01:00
Assert . Equal ( btc . CryptoCode , paymentMethod . CryptoCode ) ;
#pragma warning restore CS0618
}
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 +
"HiddenServiceDir /var/lib/tor/hidden_services/BTCPayServer" + nl +
"# Redirecting to nginx" + nl +
"HiddenServicePort 80 172.19.0.10:81" ;
Assert . True ( Torrc . TryParse ( input , out torrc ) ) ;
Assert . Equal ( expected , torrc . ToString ( ) ) ;
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:80" + nl +
"HiddenServiceDir /var/lib/tor/hidden_services/Woocommerce" + nl +
"# Redirecting to nginx" + nl +
"HiddenServicePort 80 172.19.0.11:80" ;
nl = Environment . NewLine ;
expected = "HiddenServiceDir /var/lib/tor/hidden_services/BTCPayServer" + nl +
"HiddenServicePort 80 172.19.0.10:80" + nl +
"HiddenServiceDir /var/lib/tor/hidden_services/Woocommerce" + nl +
"HiddenServicePort 80 172.19.0.11:80" + nl ;
Assert . True ( Torrc . TryParse ( input , out torrc ) ) ;
Assert . Equal ( expected , torrc . ToString ( ) ) ;
}
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 [ ]
{
new BitcoinLikePaymentHandler ( null , networkProvider , null , null ) ,
new LightningLikePaymentHandler ( null , null , networkProvider , null ) ,
} ) ;
2019-06-04 03:11:52 +02:00
var entity = new InvoiceEntity ( ) ;
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 ( )
{
CryptoCode = "BTC" , Rate = 5000 , NextNetworkFee = Money . Coins ( 0.1 m )
} ) ;
entity . ProductInformation = new ProductInformation ( ) { 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 ( )
{
Output = new TxOut ( Money . Coins ( 0.5 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
} ) ;
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 ( )
{
Output = new TxOut ( Money . Coins ( 0.2 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
} ) ;
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 ( )
{
Output = new TxOut ( Money . Coins ( 0.6 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
} ) ;
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 (
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-05-29 16:33:31 +02:00
entity . ProductInformation = new ProductInformation ( ) { Price = 5000 } ;
2018-02-19 07:09:05 +01:00
PaymentMethodDictionary paymentMethods = new PaymentMethodDictionary ( ) ;
2019-05-29 16:33:31 +02:00
paymentMethods . Add (
2019-07-01 05:39:25 +02:00
new PaymentMethod ( )
{
CryptoCode = "BTC" ,
Rate = 1000 ,
NextNetworkFee = Money . Coins ( 0.1 m )
} ) ;
2019-05-29 16:33:31 +02:00
paymentMethods . Add (
2019-07-01 05:39:25 +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 ( )
{
CryptoCode = "BTC" ,
Output = new TxOut ( remaining , 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 . 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
}
2019-05-14 17:46:43 +02:00
[Fact]
[Trait("Integration", "Integration")]
public async Task CanUseTestWebsiteUI ( )
{
using ( var tester = ServerTester . Create ( ) )
{
tester . Start ( ) ;
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 ) ;
}
}
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 [ ]
{
new BitcoinLikePaymentHandler ( null , networkProvider , null , null ) ,
new LightningLikePaymentHandler ( null , null , networkProvider , null ) ,
} ) ;
2019-06-04 03:11:52 +02:00
var entity = new InvoiceEntity ( ) ;
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 ( )
{
CryptoCode = "BTC" , Rate = 5000 , NextNetworkFee = Money . Coins ( 0.1 m )
} ) ;
entity . ProductInformation = new ProductInformation ( ) { 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 ) ;
2019-05-29 16:33:31 +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
2019-05-29 16:33:31 +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")]
2018-05-05 17:40:44 +02:00
public void CanAcceptInvoiceWithTolerance2 ( )
{
using ( var tester = ServerTester . Create ( ) )
{
tester . Start ( ) ;
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 ) ;
var invoice = user . BitPay . CreateInvoice ( new Invoice ( )
{
Buyer = new Buyer ( ) { email = "test@fwf.com" } ,
2018-05-11 15:38:31 +02:00
Price = 5000.0 m ,
2018-05-05 17:40:44 +02:00
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
// Pays 75%
var invoiceAddress = BitcoinAddress . Create ( invoice . CryptoInfo [ 0 ] . Address , tester . ExplorerNode . Network ) ;
tester . ExplorerNode . SendToAddress ( invoiceAddress , Money . Satoshis ( ( decimal ) invoice . BtcDue . Satoshi * 0.75 m ) ) ;
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 ) ;
} ) ;
}
}
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
{
2018-12-17 16:25:17 +01: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" ) ,
2018-12-18 11:09:55 +01:00
( 0.1 m , "0,10 € (EUR)" , "EUR" ) ,
2018-12-18 15:26:35 +01:00
( 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
} )
{
2019-01-04 16:37:09 +01:00
var actual = new CurrencyNameTable ( ) . 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-04-05 09:28:18 +02:00
[Fact(Timeout = 60 * 2 * 1000)]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
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 ( ) )
{
tester . Start ( ) ;
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 ( )
{
2018-07-19 07:49:30 +02:00
ConnectionString = "type=charge;server=" + tester . MerchantCharge . Client . Uri . AbsoluteUri ,
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 ( ) ;
2018-02-25 16:48:12 +01:00
Assert . DoesNotContain ( "Error" , ( ( LightningNodeViewModel ) Assert . IsType < ViewResult > ( testResult ) . Model ) . StatusMessage , StringComparison . OrdinalIgnoreCase ) ;
Assert . True ( storeController . ModelState . IsValid ) ;
Assert . IsType < RedirectToActionResult > ( storeController . AddLightningNode ( user . StoreId , new LightningNodeViewModel ( )
{
2018-07-01 09:10:17 +02:00
ConnectionString = "type=charge;server=" + tester . MerchantCharge . Client . Uri . AbsoluteUri
} , "save" , "BTC" ) . GetAwaiter ( ) . GetResult ( ) ) ;
// Make sure old connection string format does not work
Assert . IsType < ViewResult > ( storeController . AddLightningNode ( user . StoreId , new LightningNodeViewModel ( )
2018-02-25 16:48:12 +01:00
{
2018-07-01 08:45:08 +02:00
ConnectionString = tester . MerchantCharge . Client . Uri . AbsoluteUri
2018-03-20 18:48:11 +01:00
} , "save" , "BTC" ) . GetAwaiter ( ) . GetResult ( ) ) ;
2018-02-25 16:48:12 +01:00
2018-05-03 18:46:52 +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")]
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")]
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")]
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 ( ) )
{
tester . Start ( ) ;
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
}
}
2017-12-13 07:49:19 +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 ) ;
Assert . Equal ( "False" , localInvoice . ExceptionStatus . ToString ( ) ) ;
} ) ;
}
2017-10-27 10:53:04 +02:00
[Fact]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2017-10-27 10:53:04 +02:00
public void CanUseServerInitiatedPairingCode ( )
{
using ( var tester = ServerTester . Create ( ) )
{
tester . Start ( ) ;
var acc = tester . NewAccount ( ) ;
acc . Register ( ) ;
acc . CreateStore ( ) ;
2018-04-29 19:33:42 +02:00
var controller = acc . GetController < StoresController > ( ) ;
2018-04-30 15:00:43 +02:00
var token = ( RedirectToActionResult ) controller . CreateToken ( new Models . StoreViewModels . CreateTokenViewModel ( )
2017-10-27 10:53:04 +02:00
{
Label = "bla" ,
PublicKey = null
} ) . GetAwaiter ( ) . GetResult ( ) ;
var pairingCode = ( string ) token . RouteValues [ "pairingCode" ] ;
acc . BitPay . AuthorizeClient ( new PairingCode ( pairingCode ) ) . GetAwaiter ( ) . GetResult ( ) ;
Assert . True ( acc . BitPay . TestAccess ( Facade . Merchant ) ) ;
}
}
[Fact]
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 ( ) )
{
tester . Start ( ) ;
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
} ) ;
BitcoinUrlBuilder url = new BitcoinUrlBuilder ( invoice . PaymentUrls . BIP21 ) ;
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 :
2019-01-06 07:04:30 +01:00
Assert . True ( paid ) ; //TODO: Fix, out of order event mean we can receive invoice_confirmed after invoice_complete
completed = true ;
break ;
default :
Assert . False ( true , $"{evtName} was not expected" ) ;
break ;
}
}
}
2017-10-27 10:53:04 +02:00
var invoice2 = acc . BitPay . GetInvoice ( invoice . Id ) ;
Assert . NotNull ( invoice2 ) ;
}
}
}
[Fact]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2017-10-27 10:53:04 +02:00
public void CantPairTwiceWithSamePubkey ( )
{
using ( var tester = ServerTester . Create ( ) )
{
tester . Start ( ) ;
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 ) ;
Assert . IsType < RedirectToActionResult > ( store . Pair ( pairingCode . ToString ( ) , acc . StoreId ) . GetAwaiter ( ) . GetResult ( ) ) ;
pairingCode = acc . BitPay . RequestClientAuthorization ( "test1" , Facade . Merchant ) ;
2018-04-30 15:28:00 +02:00
acc . CreateStore ( ) ;
var store2 = acc . GetController < StoresController > ( ) ;
store2 . Pair ( pairingCode . ToString ( ) , store2 . StoreData . Id ) . GetAwaiter ( ) . GetResult ( ) ;
2018-02-17 05:18:16 +01:00
Assert . Contains ( nameof ( PairingResult . ReusedKey ) , store2 . StatusMessage , StringComparison . CurrentCultureIgnoreCase ) ;
2017-10-27 10:53:04 +02:00
}
}
2018-11-05 04:14:39 +01:00
[Fact]
[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 ) ) ;
2018-11-30 09:34:43 +01: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 ) ;
}
}
2018-10-26 16:07:39 +02:00
[Fact]
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 ( ) )
{
tester . Start ( ) ;
var acc = tester . NewAccount ( ) ;
acc . GrantAccess ( ) ;
2018-11-04 06:59:28 +01:00
acc . RegisterDerivationScheme ( "BTC" , true ) ;
2018-10-26 16:07:39 +02:00
var btcDerivationScheme = acc . DerivationScheme ;
2018-11-04 06:59:28 +01:00
acc . RegisterDerivationScheme ( "LTC" , true ) ;
2018-11-02 06:26:13 +01:00
2018-10-26 16:07:39 +02:00
var walletController = tester . PayTester . GetController < WalletsController > ( acc . UserId ) ;
WalletId walletId = new WalletId ( acc . StoreId , "LTC" ) ;
var rescan = Assert . IsType < RescanWalletModel > ( Assert . IsType < ViewResult > ( walletController . WalletRescan ( walletId ) . Result ) . Model ) ;
Assert . False ( rescan . Ok ) ;
Assert . True ( rescan . IsFullySync ) ;
Assert . False ( rescan . IsSupportedByCurrency ) ;
Assert . False ( rescan . IsServerAdmin ) ;
walletId = new WalletId ( acc . StoreId , "BTC" ) ;
var serverAdminClaim = new [ ] { new Claim ( Policies . CanModifyServerSettings . Key , "true" ) } ;
walletController = tester . PayTester . GetController < WalletsController > ( acc . UserId , additionalClaims : serverAdminClaim ) ;
rescan = Assert . IsType < RescanWalletModel > ( Assert . IsType < ViewResult > ( walletController . WalletRescan ( walletId ) . Result ) . Model ) ;
Assert . True ( rescan . Ok ) ;
Assert . True ( rescan . IsFullySync ) ;
Assert . True ( rescan . IsSupportedByCurrency ) ;
Assert . True ( rescan . IsServerAdmin ) ;
rescan . GapLimit = 100 ;
// Sending a coin
2019-08-17 08:14:31 +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 ) ;
var transactions = Assert . IsType < ListTransactionsViewModel > ( Assert . IsType < ViewResult > ( walletController . WalletTransactions ( walletId ) . Result ) . Model ) ;
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
{
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 ) ;
}
}
Assert . Null ( rescan . PreviousError ) ;
Assert . NotNull ( rescan . TimeOfScan ) ;
Assert . Equal ( 1 , rescan . LastSuccess . Found ) ;
transactions = Assert . IsType < ListTransactionsViewModel > ( Assert . IsType < ViewResult > ( walletController . WalletTransactions ( walletId ) . Result ) . Model ) ;
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
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 ) ;
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 ( ) ) ;
Assert . IsType < RedirectToActionResult > ( await walletController . ModifyTransaction ( walletId , tx . Id , removelabel : "test2" ) ) ;
transactions = Assert . IsType < ListTransactionsViewModel > ( Assert . IsType < ViewResult > ( walletController . WalletTransactions ( walletId ) . Result ) . Model ) ;
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
}
}
2018-05-06 06:16:39 +02:00
[Fact]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2018-05-06 06:16:39 +02:00
public void CanListInvoices ( )
{
using ( var tester = ServerTester . Create ( ) )
{
tester . Start ( ) ;
var acc = tester . NewAccount ( ) ;
acc . GrantAccess ( ) ;
acc . RegisterDerivationScheme ( "BTC" ) ;
// First we try payment with a merchant having only BTC
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()}" ) ;
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
}
}
2018-05-29 17:12:07 +02:00
[Fact]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2018-05-29 17:12:07 +02:00
public void CanGetRates ( )
{
using ( var tester = ServerTester . Create ( ) )
{
tester . Start ( ) ;
var acc = tester . NewAccount ( ) ;
acc . GrantAccess ( ) ;
acc . RegisterDerivationScheme ( "BTC" ) ;
acc . RegisterDerivationScheme ( "LTC" ) ;
var rateController = acc . GetController < RateController > ( ) ;
2019-03-05 09:21:44 +01:00
var GetBaseCurrencyRatesResult = JObject . Parse ( ( ( JsonResult ) rateController . GetBaseCurrencyRates ( "BTC" , acc . StoreId , 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 ) ;
2018-07-30 16:22:26 +02:00
Assert . Equal ( 2 , GetBaseCurrencyRatesResult . Data . Length ) ;
Assert . Single ( GetBaseCurrencyRatesResult . Data . Where ( o = > o . Code = = "LTC" ) ) ;
2018-05-29 17:12:07 +02:00
2019-03-05 09:21:44 +01:00
var GetRatesResult = JObject . Parse ( ( ( JsonResult ) rateController . GetRates ( null , acc . StoreId , 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 > ( ) ;
var ratesVM = ( RatesViewModel ) ( Assert . IsType < ViewResult > ( store . Rates ( acc . StoreId ) ) . Model ) ;
ratesVM . DefaultCurrencyPairs = "BTC_USD,LTC_USD" ;
store . Rates ( ratesVM ) . Wait ( ) ;
store = acc . GetController < StoresController > ( ) ;
rateController = acc . GetController < RateController > ( ) ;
GetRatesResult = JObject . Parse ( ( ( JsonResult ) rateController . GetRates ( null , acc . StoreId , default )
. 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 ) ;
2019-03-05 09:21:44 +01:00
var GetCurrencyPairRateResult = JObject . Parse ( ( ( JsonResult ) rateController . GetCurrencyPairRate ( "BTC" , "LTC" , acc . StoreId , 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
var response = client . GetAsync ( $"http://127.0.0.1:{tester.PayTester.Port}/api/rates?storeId={acc.StoreId}" ) . GetAwaiter ( ) . GetResult ( ) ;
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 )
{
var result = ( Models . InvoicingModels . InvoicesModel ) ( ( ViewResult ) acc . GetController < InvoiceController > ( ) . ListInvoices ( filter ) . Result ) . Model ;
Assert . Equal ( expected , result . Invoices . Any ( i = > i . InvoiceId = = invoiceId ) ) ;
}
2017-11-06 09:31:02 +01:00
[Fact]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2017-11-06 09:31:02 +01:00
public void CanRBFPayment ( )
{
using ( var tester = ServerTester . Create ( ) )
{
tester . Start ( ) ;
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 ) ;
2017-11-06 09:31:02 +01:00
var invoice = user . BitPay . CreateInvoice ( new Invoice ( )
{
2018-05-11 15:38:31 +02:00
Price = 5000.0 m ,
2017-11-06 09:31:02 +01:00
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 [ ]
{
2018-02-17 05:18:16 +01:00
invoice . BitcoinAddress ,
2017-11-06 09:31:02 +01:00
payment1 . ToString ( ) ,
null , //comment
null , //comment_to
false , //subtractfeefromamount
true , //replaceable
} ) . ResultString ) ;
2018-10-28 15:43:48 +01:00
Logs . Tester . LogInformation ( $"Let's send a first payment of {payment1} for the {invoice.BtcDue} invoice ({tx1})" ) ;
2018-01-11 14:52:28 +01:00
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 ( ) ) ;
2018-01-11 14:52:28 +01: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
}
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 ) ;
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 ) ;
}
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
} ) ;
}
}
2017-12-03 15:35:52 +01:00
[Fact]
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
}
2018-08-13 02:43:59 +02:00
[Fact]
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-02-02 08:12:51 +01:00
[Fact]
[Trait("Integration", "Integration")]
public async void CheckCORSSetOnBitpayAPI ( )
{
using ( var tester = ServerTester . Create ( ) )
{
tester . Start ( ) ;
2019-03-17 12:49:26 +01:00
foreach ( var req in new [ ]
2019-02-02 08:12:51 +01:00
{
"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 ( ) )
{
await req ;
}
HttpClient client2 = new HttpClient ( ) ;
HttpRequestMessage message2 = new HttpRequestMessage ( HttpMethod . Options , tester . PayTester . ServerUri . AbsoluteUri + "rates" ) ;
var response2 = await client2 . SendAsync ( message2 ) ;
Assert . True ( response2 . Headers . TryGetValues ( "Access-Control-Allow-Origin" , out var val2 ) ) ;
Assert . Equal ( "*" , val2 . FirstOrDefault ( ) ) ;
}
}
2017-10-27 10:53:04 +02:00
[Fact]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2018-01-07 18:36:41 +01:00
public void TestAccessBitpayAPI ( )
2017-10-27 10:53:04 +02:00
{
using ( var tester = ServerTester . Create ( ) )
{
tester . Start ( ) ;
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 > ( ) ;
storeController . CreateToken ( new CreateTokenViewModel ( )
{
Label = "test2" ,
StoreId = user . StoreId
} ) . GetAwaiter ( ) . GetResult ( ) ;
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 ( ) ) ;
2018-04-30 15:00:43 +02:00
Assert . IsType < RedirectToActionResult > ( user . GetController < StoresController > ( ) . GenerateAPIKey ( ) . 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
2018-04-30 15:00:43 +02:00
Assert . IsType < RedirectToActionResult > ( user . GetController < StoresController > ( ) . GenerateAPIKey ( ) . 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 ( ) ;
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 ) ) ) ;
var invoice = new Invoice ( )
{
2018-05-11 15:38:31 +02:00
Price = 5000.0 m ,
2018-04-29 11:28:04 +02:00
Currency = "USD"
} ;
message . Content = new StringContent ( JsonConvert . SerializeObject ( invoice ) , Encoding . UTF8 , "application/json" ) ;
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 ( ) ;
HttpRequestMessage mess = new HttpRequestMessage ( HttpMethod . Get , tester . PayTester . ServerUri . AbsoluteUri + "tokens" ) ;
mess . Content = new StringContent ( string . Empty , Encoding . UTF8 , "application/json" ) ;
mess . Headers . Add ( "x-signature" , "3045022100caa123193afc22ef93d9c6b358debce6897c09dd9869fe6fe029c9cb43623fac022000b90c65c50ba8bbbc6ebee8878abe5659e17b9f2e1b27d95eda4423da5608fe" ) ;
mess . Headers . Add ( "x-identity" , "04b4d82095947262dd70f94c0a0e005ec3916e3f5f2181c176b8b22a52db22a8c436c4703f43a9e8884104854a11e1eb30df8fdf116e283807a1f1b8fe4c182b99" ) ;
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
2018-04-15 14:18:51 +02:00
[Fact]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2018-04-15 14:18:51 +02:00
public void CanUseExchangeSpecificRate ( )
{
using ( var tester = ServerTester . Create ( ) )
{
tester . PayTester . MockRates = false ;
tester . Start ( ) ;
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
List < decimal > rates = new List < decimal > ( ) ;
rates . Add ( CreateInvoice ( tester , user , "coinaverage" ) ) ;
2018-08-22 17:24:33 +02:00
var bitflyer = CreateInvoice ( tester , user , "bitflyer" , "JPY" ) ;
var bitflyer2 = 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 ) ) ;
}
}
}
2018-08-22 17:24:33 +02:00
private static 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 > ( ) ;
2019-03-11 10:39:21 +01:00
var vm = ( RatesViewModel ) ( ( ViewResult ) storeController . Rates ( user . StoreId ) ) . Model ;
2018-04-15 14:18:51 +02:00
vm . PreferredExchange = exchange ;
2018-05-03 18:46:52 +02:00
storeController . Rates ( vm ) . Wait ( ) ;
2018-04-15 14:18:51 +02:00
var invoice2 = user . BitPay . CreateInvoice ( new Invoice ( )
{
2018-05-11 15:38:31 +02:00
Price = 5000.0 m ,
2018-08-22 17:24:33 +02:00
Currency = currency ,
2018-04-15 14:18:51 +02:00
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
return invoice2 . CryptoInfo [ 0 ] . Rate ;
}
2018-01-17 07:59:31 +01:00
2019-03-25 04:59:42 +01:00
[Fact]
[Trait("Integration", "Integration")]
public async Task CanUseAnyoneCanCreateInvoice ( )
{
using ( var tester = ServerTester . Create ( ) )
{
tester . Start ( ) ;
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
Logs . Tester . LogInformation ( "StoreId without anyone can create invoice = 401" ) ;
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" ) ,
} ) ;
Assert . Equal ( 401 , ( int ) response . StatusCode ) ;
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" ) ,
} ) ;
Assert . Equal ( 404 , ( int ) response . StatusCode ) ;
user . ModifyStore ( s = > s . AnyoneCanCreateInvoice = true ) ;
Logs . Tester . LogInformation ( "Bad store with anyone can create invoice = 401" ) ;
response = await tester . PayTester . HttpClient . SendAsync ( new HttpRequestMessage ( HttpMethod . Post , $"invoices?storeId=badid" )
{
Content = new StringContent ( "{\"Price\": 5000, \"currency\": \"USD\"}" , Encoding . UTF8 , "application/json" ) ,
} ) ;
Assert . Equal ( 401 , ( int ) response . StatusCode ) ;
Logs . Tester . LogInformation ( "Good store with anyone can create invoice = 200" ) ;
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" ) ,
} ) ;
Assert . Equal ( 200 , ( int ) response . StatusCode ) ;
}
}
2018-01-17 07:59:31 +01:00
[Fact]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2018-01-17 07:59:31 +01:00
public void CanTweakRate ( )
{
using ( var tester = ServerTester . Create ( ) )
{
tester . Start ( ) ;
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
var invoice1 = user . BitPay . CreateInvoice ( new Invoice ( )
{
2018-05-11 15:38:31 +02:00
Price = 5000.0 m ,
2018-01-17 07:59:31 +01:00
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 > ( ) ;
2019-03-11 10:39:21 +01:00
var vm = ( RatesViewModel ) ( ( ViewResult ) storeController . Rates ( user . StoreId ) ) . Model ;
2018-08-01 11:38:46 +02:00
Assert . Equal ( 0.0 , vm . Spread ) ;
vm . Spread = 40 ;
2018-05-03 18:46:52 +02:00
storeController . Rates ( vm ) . Wait ( ) ;
2018-01-17 07:59:31 +01:00
var invoice2 = user . BitPay . CreateInvoice ( new Invoice ( )
{
2018-05-11 15:38:31 +02:00
Price = 5000.0 m ,
2018-01-17 07:59:31 +01:00
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
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
}
}
2018-01-13 14:01:09 +01:00
[Fact]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2018-01-13 14:01:09 +01:00
public void CanHaveLTCOnlyStore ( )
{
using ( var tester = ServerTester . Create ( ) )
{
tester . Start ( ) ;
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
2018-02-23 07:21:42 +01:00
user . RegisterDerivationScheme ( "LTC" ) ;
2018-01-13 14:01:09 +01:00
// First we try payment with a merchant having only BTC
var invoice = user . BitPay . CreateInvoice ( new Invoice ( )
{
Price = 500 ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
Assert . Single ( invoice . CryptoInfo ) ;
Assert . Equal ( "LTC" , invoice . CryptoInfo [ 0 ] . CryptoCode ) ;
2018-05-15 16:18:26 +02:00
Assert . True ( invoice . PaymentCodes . ContainsKey ( "LTC" ) ) ;
Assert . True ( invoice . SupportedTransactionCurrencies . ContainsKey ( "LTC" ) ) ;
Assert . True ( invoice . SupportedTransactionCurrencies [ "LTC" ] . Enabled ) ;
Assert . True ( invoice . PaymentSubtotals . ContainsKey ( "LTC" ) ) ;
Assert . True ( invoice . PaymentTotals . ContainsKey ( "LTC" ) ) ;
2018-01-13 14:01:09 +01:00
var cashCow = tester . LTCExplorerNode ;
var invoiceAddress = BitcoinAddress . Create ( invoice . CryptoInfo [ 0 ] . Address , cashCow . Network ) ;
var firstPayment = Money . Coins ( 0.1 m ) ;
cashCow . SendToAddress ( invoiceAddress , firstPayment ) ;
2019-01-05 19:47:39 +01:00
TestUtils . Eventually ( ( ) = >
2018-01-13 14:01:09 +01:00
{
invoice = user . BitPay . GetInvoice ( invoice . Id ) ;
Assert . Equal ( firstPayment , invoice . CryptoInfo [ 0 ] . Paid ) ;
} ) ;
Assert . Single ( invoice . CryptoInfo ) ; // Only BTC should be presented
var controller = tester . PayTester . GetController < InvoiceController > ( null ) ;
var checkout = ( Models . InvoicingModels . PaymentModel ) ( ( JsonResult ) controller . GetStatus ( invoice . Id , null ) . GetAwaiter ( ) . GetResult ( ) ) . Value ;
Assert . Single ( checkout . AvailableCryptos ) ;
Assert . Equal ( "LTC" , checkout . CryptoCode ) ;
//////////////////////
// Despite it is called BitcoinAddress it should be LTC because BTC is not available
Assert . Null ( invoice . BitcoinAddress ) ;
2018-05-11 15:38:31 +02:00
Assert . NotEqual ( 1.0 m , invoice . Rate ) ;
2018-01-13 14:01:09 +01:00
Assert . NotEqual ( invoice . BtcDue , invoice . CryptoInfo [ 0 ] . Due ) ; // Should be BTC rate
cashCow . SendToAddress ( invoiceAddress , invoice . CryptoInfo [ 0 ] . Due ) ;
2019-01-05 19:47:39 +01:00
TestUtils . Eventually ( ( ) = >
2018-01-13 14:01:09 +01:00
{
invoice = user . BitPay . GetInvoice ( invoice . Id ) ;
Assert . Equal ( "paid" , invoice . Status ) ;
checkout = ( Models . InvoicingModels . PaymentModel ) ( ( JsonResult ) controller . GetStatus ( invoice . Id , null ) . GetAwaiter ( ) . GetResult ( ) ) . Value ;
Assert . Equal ( "paid" , checkout . Status ) ;
} ) ;
}
}
2018-01-07 18:36:41 +01:00
2018-05-03 18:46:52 +02:00
[Fact]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2018-05-03 18:46:52 +02:00
public void CanModifyRates ( )
{
using ( var tester = ServerTester . Create ( ) )
{
tester . Start ( ) ;
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
var store = user . GetController < StoresController > ( ) ;
2019-03-11 10:39:21 +01:00
var rateVm = Assert . IsType < RatesViewModel > ( Assert . IsType < ViewResult > ( store . Rates ( user . StoreId ) ) . Model ) ;
2018-05-03 18:46:52 +02:00
Assert . False ( rateVm . ShowScripting ) ;
Assert . Equal ( "coinaverage" , 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" ;
Assert . IsType < RedirectToActionResult > ( store . Rates ( rateVm , "Save" ) . Result ) ;
2019-03-11 10:39:21 +01:00
rateVm = Assert . IsType < RatesViewModel > ( Assert . IsType < ViewResult > ( store . Rates ( user . StoreId ) ) . 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 > ( ) ;
rateVm = Assert . IsType < RatesViewModel > ( Assert . IsType < ViewResult > ( store . Rates ( rateVm , "Test" ) . Result ) . Model ) ;
Assert . NotNull ( rateVm . TestRateRules ) ;
Assert . Equal ( 2 , rateVm . TestRateRules . Count ) ;
Assert . False ( rateVm . TestRateRules [ 0 ] . Error ) ;
2018-08-01 11:38:46 +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 ) ;
Assert . IsType < RedirectToActionResult > ( store . Rates ( rateVm , "Save" ) . Result ) ;
Assert . IsType < RedirectToActionResult > ( store . ShowRateRulesPost ( true ) . Result ) ;
Assert . IsType < RedirectToActionResult > ( store . Rates ( rateVm , "Save" ) . Result ) ;
store = user . GetController < StoresController > ( ) ;
2019-03-11 10:39:21 +01:00
rateVm = Assert . IsType < RatesViewModel > ( Assert . IsType < ViewResult > ( store . Rates ( user . StoreId ) ) . Model ) ;
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" ;
rateVm = Assert . IsType < RatesViewModel > ( Assert . IsType < ViewResult > ( store . Rates ( rateVm , "Test" ) . Result ) . Model ) ;
Assert . True ( rateVm . ShowScripting ) ;
2018-08-01 11:38:46 +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" +
2018-08-22 17:24:33 +02:00
"X_X = coinaverage(X_X);" ;
2018-08-01 11:38:46 +02:00
rateVm . Spread = 50 ;
2018-05-03 18:46:52 +02:00
rateVm = Assert . IsType < RatesViewModel > ( Assert . IsType < ViewResult > ( store . Rates ( rateVm , "Test" ) . Result ) . Model ) ;
Assert . True ( rateVm . TestRateRules . All ( t = > ! t . Error ) ) ;
Assert . IsType < RedirectToActionResult > ( store . Rates ( rateVm , "Save" ) . Result ) ;
store = user . GetController < StoresController > ( ) ;
2019-03-11 10:39:21 +01:00
rateVm = Assert . IsType < RatesViewModel > ( Assert . IsType < ViewResult > ( store . Rates ( user . StoreId ) ) . 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 ) ;
}
}
2018-01-11 09:29:48 +01:00
[Fact]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2018-01-11 09:29:48 +01:00
public void CanPayWithTwoCurrencies ( )
{
using ( var tester = ServerTester . Create ( ) )
{
tester . Start ( ) ;
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
2018-02-23 07:21:42 +01:00
user . RegisterDerivationScheme ( "BTC" ) ;
2018-01-11 09:29:48 +01:00
// First we try payment with a merchant having only BTC
var invoice = user . BitPay . CreateInvoice ( new Invoice ( )
{
2018-05-11 15:38:31 +02:00
Price = 5000.0 m ,
2018-01-11 09:29:48 +01:00
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
var cashCow = tester . ExplorerNode ;
2018-01-20 06:09:57 +01:00
cashCow . Generate ( 2 ) ; // get some money in case
2018-01-11 09:29:48 +01:00
var invoiceAddress = BitcoinAddress . Create ( invoice . BitcoinAddress , cashCow . Network ) ;
var firstPayment = Money . Coins ( 0.04 m ) ;
cashCow . SendToAddress ( invoiceAddress , firstPayment ) ;
2019-01-05 19:47:39 +01:00
TestUtils . Eventually ( ( ) = >
2018-01-11 09:29:48 +01:00
{
invoice = user . BitPay . GetInvoice ( invoice . Id ) ;
Assert . True ( invoice . BtcPaid = = firstPayment ) ;
} ) ;
Assert . Single ( invoice . CryptoInfo ) ; // Only BTC should be presented
var controller = tester . PayTester . GetController < InvoiceController > ( null ) ;
var checkout = ( Models . InvoicingModels . PaymentModel ) ( ( JsonResult ) controller . GetStatus ( invoice . Id , null ) . GetAwaiter ( ) . GetResult ( ) ) . Value ;
Assert . Single ( checkout . AvailableCryptos ) ;
Assert . Equal ( "BTC" , checkout . CryptoCode ) ;
2018-05-15 16:25:43 +02:00
Assert . Single ( invoice . PaymentCodes ) ;
Assert . Single ( invoice . SupportedTransactionCurrencies ) ;
Assert . Single ( invoice . SupportedTransactionCurrencies ) ;
Assert . Single ( invoice . PaymentSubtotals ) ;
Assert . Single ( invoice . PaymentTotals ) ;
Assert . True ( invoice . PaymentCodes . ContainsKey ( "BTC" ) ) ;
Assert . True ( invoice . SupportedTransactionCurrencies . ContainsKey ( "BTC" ) ) ;
Assert . True ( invoice . SupportedTransactionCurrencies [ "BTC" ] . Enabled ) ;
Assert . True ( invoice . PaymentSubtotals . ContainsKey ( "BTC" ) ) ;
Assert . True ( invoice . PaymentTotals . ContainsKey ( "BTC" ) ) ;
2018-01-11 09:29:48 +01:00
//////////////////////
// Retry now with LTC enabled
user . RegisterDerivationScheme ( "LTC" ) ;
invoice = user . BitPay . CreateInvoice ( new Invoice ( )
{
2018-05-11 15:38:31 +02:00
Price = 5000.0 m ,
2018-01-11 09:29:48 +01:00
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
cashCow = tester . ExplorerNode ;
invoiceAddress = BitcoinAddress . Create ( invoice . BitcoinAddress , cashCow . Network ) ;
firstPayment = Money . Coins ( 0.04 m ) ;
cashCow . SendToAddress ( invoiceAddress , firstPayment ) ;
2018-01-20 06:09:57 +01:00
Logs . Tester . LogInformation ( "First payment sent to " + invoiceAddress ) ;
2019-01-05 19:47:39 +01:00
TestUtils . Eventually ( ( ) = >
2018-01-11 09:29:48 +01:00
{
invoice = user . BitPay . GetInvoice ( invoice . Id ) ;
Assert . True ( invoice . BtcPaid = = firstPayment ) ;
} ) ;
cashCow = tester . LTCExplorerNode ;
var ltcCryptoInfo = invoice . CryptoInfo . FirstOrDefault ( c = > c . CryptoCode = = "LTC" ) ;
Assert . NotNull ( ltcCryptoInfo ) ;
invoiceAddress = BitcoinAddress . Create ( ltcCryptoInfo . Address , cashCow . Network ) ;
2018-02-17 05:18:16 +01:00
var secondPayment = Money . Coins ( decimal . Parse ( ltcCryptoInfo . Due , CultureInfo . InvariantCulture ) ) ;
2018-01-11 09:29:48 +01:00
cashCow . Generate ( 2 ) ; // LTC is not worth a lot, so just to make sure we have money...
cashCow . SendToAddress ( invoiceAddress , secondPayment ) ;
2018-01-20 06:09:57 +01:00
Logs . Tester . LogInformation ( "Second payment sent to " + invoiceAddress ) ;
2019-01-05 19:47:39 +01:00
TestUtils . Eventually ( ( ) = >
2018-01-11 09:29:48 +01:00
{
invoice = user . BitPay . GetInvoice ( invoice . Id ) ;
Assert . Equal ( Money . Zero , invoice . BtcDue ) ;
var ltcPaid = invoice . CryptoInfo . First ( c = > c . CryptoCode = = "LTC" ) ;
Assert . Equal ( Money . Zero , ltcPaid . Due ) ;
Assert . Equal ( secondPayment , ltcPaid . CryptoPaid ) ;
Assert . Equal ( "paid" , invoice . Status ) ;
Assert . False ( ( bool ) ( ( JValue ) invoice . ExceptionStatus ) . Value ) ;
} ) ;
controller = tester . PayTester . GetController < InvoiceController > ( null ) ;
checkout = ( Models . InvoicingModels . PaymentModel ) ( ( JsonResult ) controller . GetStatus ( invoice . Id , "LTC" ) . GetAwaiter ( ) . GetResult ( ) ) . Value ;
Assert . Equal ( 2 , checkout . AvailableCryptos . Count ) ;
Assert . Equal ( "LTC" , checkout . CryptoCode ) ;
2018-05-15 16:25:43 +02:00
Assert . Equal ( 2 , invoice . PaymentCodes . Count ( ) ) ;
Assert . Equal ( 2 , invoice . SupportedTransactionCurrencies . Count ( ) ) ;
Assert . Equal ( 2 , invoice . SupportedTransactionCurrencies . Count ( ) ) ;
Assert . Equal ( 2 , invoice . PaymentSubtotals . Count ( ) ) ;
Assert . Equal ( 2 , invoice . PaymentTotals . Count ( ) ) ;
Assert . True ( invoice . PaymentCodes . ContainsKey ( "LTC" ) ) ;
Assert . True ( invoice . SupportedTransactionCurrencies . ContainsKey ( "LTC" ) ) ;
Assert . True ( invoice . SupportedTransactionCurrencies [ "LTC" ] . Enabled ) ;
Assert . True ( invoice . PaymentSubtotals . ContainsKey ( "LTC" ) ) ;
Assert . True ( invoice . PaymentTotals . ContainsKey ( "LTC" ) ) ;
2019-02-21 11:34:11 +01:00
// Check if we can disable LTC
invoice = user . BitPay . CreateInvoice ( new Invoice ( )
{
Price = 5000.0 m ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true ,
SupportedTransactionCurrencies = new Dictionary < string , InvoiceSupportedTransactionCurrency > ( )
{
{ "BTC" , new InvoiceSupportedTransactionCurrency ( ) { Enabled = true } }
}
} , Facade . Merchant ) ;
Assert . Single ( invoice . CryptoInfo . Where ( c = > c . CryptoCode = = "BTC" ) ) ;
Assert . Empty ( invoice . CryptoInfo . Where ( c = > c . CryptoCode = = "LTC" ) ) ;
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 ) ) ;
}
2018-03-24 12:40:26 +01:00
[Fact]
2018-10-28 12:59:59 +01:00
[Trait("Fast", "Fast")]
2018-03-24 12:40:26 +01:00
public void CanParseDerivationScheme ( )
{
2019-05-09 09:05:18 +02:00
var testnetNetworkProvider = new BTCPayNetworkProvider ( NetworkType . Testnet ) ;
var regtestNetworkProvider = new BTCPayNetworkProvider ( NetworkType . Regtest ) ;
2019-05-09 11:11:39 +02:00
var mainnetNetworkProvider = new BTCPayNetworkProvider ( NetworkType . Mainnet ) ;
2019-05-29 11:43:50 +02:00
var testnetParser = new DerivationSchemeParser ( testnetNetworkProvider . GetNetwork < BTCPayNetwork > ( "BTC" ) ) ;
var mainnetParser = new DerivationSchemeParser ( mainnetNetworkProvider . GetNetwork < BTCPayNetwork > ( "BTC" ) ) ;
2018-03-24 12:40:26 +01:00
NBXplorer . DerivationStrategy . DerivationStrategyBase result ;
// Passing electrum stuff
2019-05-09 12:14:01 +02:00
// Passing a native segwit from mainnet to a testnet parser, means the testnet parser will try to convert it into segwit
2019-05-09 11:11:39 +02:00
result = testnetParser . Parse ( "zpub6nL6PUGurpU3DfPDSZaRS6WshpbNc9ctCFFzrCn54cssnheM31SZJZUcFHKtjJJNhAueMbh6ptFMfy1aeiMQJr3RJ4DDt1hAPx7sMTKV48t" ) ;
2018-03-24 12:40:26 +01:00
Assert . Equal ( "tpubD93CJNkmGjLXnsBqE2zGDqfEh1Q8iJ8wueordy3SeWt1RngbbuxXCsqASuVWFywmfoCwUE1rSfNJbaH4cBNcbp8WcyZgPiiRSTazLGL8U9w" , result . ToString ( ) ) ;
2019-05-09 12:14:01 +02:00
result = mainnetParser . Parse ( "zpub6nL6PUGurpU3DfPDSZaRS6WshpbNc9ctCFFzrCn54cssnheM31SZJZUcFHKtjJJNhAueMbh6ptFMfy1aeiMQJr3RJ4DDt1hAPx7sMTKV48t" ) ;
Assert . Equal ( "xpub68fZn8w5ZTP5X4zymr1B1vKsMtJUiudtN2DZHQzJJc87gW1tXh7S4SALCsQijUzXstg2reVyuZYFuPnTDKXNiNgDZNpNiC4BrVzaaGEaRHj" , result . ToString ( ) ) ;
2018-03-24 12:40:26 +01:00
// P2SH
2019-05-09 11:11:39 +02:00
result = testnetParser . Parse ( "upub57Wa4MvRPNyAipy1MCpERxcFpHR2ZatyikppkyeWkoRL6QJvLVMo39jYdcaJVxyvBURyRVmErBEA5oGicKBgk1j72GAXSPFH5tUDoGZ8nEu" ) ;
2018-03-24 12:40:26 +01:00
Assert . Equal ( "tpubD6NzVbkrYhZ4YWjDJUACG9E8fJx2NqNY1iynTiPKEjJrzzRKAgha3nNnwGXr2BtvCJKJHW4nmG7rRqc2AGGy2AECgt16seMyV2FZivUmaJg-[p2sh]" , result . ToString ( ) ) ;
2019-05-09 11:11:39 +02:00
result = mainnetParser . Parse ( "ypub6QqdH2c5z79681jUgdxjGJzGW9zpL4ryPCuhtZE4GpvrJoZqM823XQN6iSQeVbbbp2uCRQ9UgpeMcwiyV6qjvxTWVcxDn2XEAnioMUwsrQ5" ) ;
Assert . Equal ( "xpub661MyMwAqRbcGiYMrHB74DtmLBrNPSsUU6PV7ALAtpYyFhkc6TrUuLhxhET4VgwgQPnPfvYvEAHojf7QmQRj8imudHFoC7hju4f9xxri8wR-[p2sh]" , result . ToString ( ) ) ;
// if prefix not recognize, assume it is segwit
result = testnetParser . Parse ( "xpub661MyMwAqRbcGeVGU5e5KBcau1HHEUGf9Wr7k4FyLa8yRPNQrrVa7Ndrgg8Afbe2UYXMSL6tJBFd2JewwWASsePPLjkcJFL1tTVEs3UQ23X" ) ;
Assert . Equal ( "tpubD6NzVbkrYhZ4YSg7vGdAX6wxE8NwDrmih9SR6cK7gUtsAg37w5LfFpJgviCxC6bGGT4G3uckqH5fiV9ZLN1gm5qgQLVuymzFUR5ed7U7ksu" , result . ToString ( ) ) ;
2018-03-24 12:40:26 +01:00
////////////////
2018-03-26 06:52:14 +02:00
var tpub = "tpubD6NzVbkrYhZ4Wc65tjhmcKdWFauAo7bGLRTxvggygkNyp6SMGutJp7iociwsinU33jyNBp1J9j2hJH5yQsayfiS3LEU2ZqXodAcnaygra8o" ;
2019-05-09 11:11:39 +02:00
result = testnetParser . Parse ( tpub ) ;
2018-03-26 06:52:14 +02:00
Assert . Equal ( tpub , result . ToString ( ) ) ;
2019-05-09 11:11:39 +02:00
testnetParser . HintScriptPubKey = BitcoinAddress . Create ( "tb1q4s33amqm8l7a07zdxcunqnn3gcsjcfz3xc573l" , testnetParser . Network ) . ScriptPubKey ;
result = testnetParser . Parse ( tpub ) ;
2018-03-26 06:52:14 +02:00
Assert . Equal ( tpub , result . ToString ( ) ) ;
2018-03-24 12:40:26 +01:00
2019-05-09 11:11:39 +02:00
testnetParser . HintScriptPubKey = BitcoinAddress . Create ( "2N2humNio3YTApSfY6VztQ9hQwDnhDvaqFQ" , testnetParser . Network ) . ScriptPubKey ;
result = testnetParser . Parse ( tpub ) ;
2018-03-26 06:52:14 +02:00
Assert . Equal ( $"{tpub}-[p2sh]" , result . ToString ( ) ) ;
2018-03-24 12:40:26 +01:00
2019-05-09 11:11:39 +02:00
testnetParser . HintScriptPubKey = BitcoinAddress . Create ( "mwD8bHS65cdgUf6rZUUSoVhi3wNQFu1Nfi" , testnetParser . Network ) . ScriptPubKey ;
result = testnetParser . Parse ( tpub ) ;
2018-03-26 06:52:14 +02:00
Assert . Equal ( $"{tpub}-[legacy]" , result . ToString ( ) ) ;
2018-03-24 12:40:26 +01:00
2019-05-09 11:11:39 +02:00
testnetParser . HintScriptPubKey = BitcoinAddress . Create ( "2N2humNio3YTApSfY6VztQ9hQwDnhDvaqFQ" , testnetParser . Network ) . ScriptPubKey ;
result = testnetParser . Parse ( $"{tpub}-[legacy]" ) ;
2018-03-26 06:52:14 +02:00
Assert . Equal ( $"{tpub}-[p2sh]" , result . ToString ( ) ) ;
2018-03-24 12:40:26 +01:00
2019-05-09 11:11:39 +02:00
result = testnetParser . Parse ( tpub ) ;
2018-03-26 06:52:14 +02:00
Assert . Equal ( $"{tpub}-[p2sh]" , result . ToString ( ) ) ;
2018-11-16 17:21:34 +01:00
2019-05-29 11:43:50 +02:00
var regtestParser = new DerivationSchemeParser ( regtestNetworkProvider . GetNetwork < BTCPayNetwork > ( "BTC" ) ) ;
2019-05-09 11:11:39 +02:00
var parsed = regtestParser . Parse ( "xpub6DG1rMYXiQtCc6CfdLFD9CtxqhzzRh7j6Sq6EdE9abgYy3cfDRrniLLv2AdwqHL1exiLnnKR5XXcaoiiexf3Y9R6J6rxkJtqJHzNzMW9QMZ-[p2sh]" ) ;
2018-11-16 17:21:34 +01:00
Assert . Equal ( "tpubDDdeNbNDRgqestPX5XEJM8ELAq6eR5cne5RPbBHHvWSSiLHNHehsrn1kGCijMnHFSsFFQMqHcdMfGzDL3pWHRasPMhcGRqZ4tFankQ3i4ok-[p2sh]" , parsed . ToString ( ) ) ;
2018-11-16 17:45:59 +01:00
// Let's make sure we can't generate segwit with dogecoin
2019-05-29 11:43:50 +02:00
regtestParser = new DerivationSchemeParser ( regtestNetworkProvider . GetNetwork < BTCPayNetwork > ( "DOGE" ) ) ;
2019-05-09 11:11:39 +02:00
parsed = regtestParser . Parse ( "xpub6DG1rMYXiQtCc6CfdLFD9CtxqhzzRh7j6Sq6EdE9abgYy3cfDRrniLLv2AdwqHL1exiLnnKR5XXcaoiiexf3Y9R6J6rxkJtqJHzNzMW9QMZ-[p2sh]" ) ;
2018-11-16 17:45:59 +01:00
Assert . Equal ( "tpubDDdeNbNDRgqestPX5XEJM8ELAq6eR5cne5RPbBHHvWSSiLHNHehsrn1kGCijMnHFSsFFQMqHcdMfGzDL3pWHRasPMhcGRqZ4tFankQ3i4ok-[legacy]" , parsed . ToString ( ) ) ;
2018-11-17 03:35:20 +01:00
2019-05-29 11:43:50 +02:00
regtestParser = new DerivationSchemeParser ( regtestNetworkProvider . GetNetwork < BTCPayNetwork > ( "DOGE" ) ) ;
2019-05-09 11:11:39 +02:00
parsed = regtestParser . Parse ( "tpubDDdeNbNDRgqestPX5XEJM8ELAq6eR5cne5RPbBHHvWSSiLHNHehsrn1kGCijMnHFSsFFQMqHcdMfGzDL3pWHRasPMhcGRqZ4tFankQ3i4ok-[p2sh]" ) ;
2018-11-17 03:35:20 +01:00
Assert . Equal ( "tpubDDdeNbNDRgqestPX5XEJM8ELAq6eR5cne5RPbBHHvWSSiLHNHehsrn1kGCijMnHFSsFFQMqHcdMfGzDL3pWHRasPMhcGRqZ4tFankQ3i4ok-[legacy]" , parsed . ToString ( ) ) ;
2018-03-24 12:40:26 +01:00
}
2018-07-30 17:18:58 +02:00
[Fact]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2019-05-09 09:11:09 +02:00
public void CanAddDerivationSchemes ( )
2018-07-30 17:18:58 +02:00
{
using ( var tester = ServerTester . Create ( ) )
{
tester . Start ( ) ;
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
user . RegisterDerivationScheme ( "LTC" ) ;
user . RegisterLightningNode ( "BTC" , LightningConnectionType . CLightning ) ;
2019-05-29 11:43:50 +02:00
var btcNetwork = tester . PayTester . Networks . GetNetwork < BTCPayNetwork > ( "BTC" ) ;
2018-07-30 17:18:58 +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 ) ;
Assert . Equal ( 3 , invoice . CryptoInfo . Length ) ;
var controller = user . GetController < StoresController > ( ) ;
var lightningVM = ( LightningNodeViewModel ) Assert . IsType < ViewResult > ( controller . AddLightningNode ( user . StoreId , "BTC" ) ) . Model ;
Assert . True ( lightningVM . Enabled ) ;
lightningVM . Enabled = false ;
controller . AddLightningNode ( user . StoreId , lightningVM , "save" , "BTC" ) . GetAwaiter ( ) . GetResult ( ) ;
lightningVM = ( LightningNodeViewModel ) Assert . IsType < ViewResult > ( controller . AddLightningNode ( user . StoreId , "BTC" ) ) . Model ;
Assert . False ( lightningVM . Enabled ) ;
2019-05-09 09:11:09 +02:00
// Only Enabling/Disabling the payment method must redirect to store page
2018-07-30 17:18:58 +02:00
var derivationVM = ( DerivationSchemeViewModel ) Assert . IsType < ViewResult > ( controller . AddDerivationScheme ( user . StoreId , "BTC" ) ) . Model ;
Assert . True ( derivationVM . Enabled ) ;
derivationVM . Enabled = false ;
2018-08-01 11:42:28 +02:00
Assert . IsType < RedirectToActionResult > ( controller . AddDerivationScheme ( user . StoreId , derivationVM , "BTC" ) . GetAwaiter ( ) . GetResult ( ) ) ;
derivationVM = ( DerivationSchemeViewModel ) Assert . IsType < ViewResult > ( controller . AddDerivationScheme ( user . StoreId , "BTC" ) ) . Model ;
2018-07-30 17:18:58 +02:00
Assert . False ( derivationVM . Enabled ) ;
2019-05-09 09:11:09 +02:00
// Clicking next without changing anything should send to the confirmation screen
2018-07-30 17:18:58 +02:00
derivationVM = ( DerivationSchemeViewModel ) Assert . IsType < ViewResult > ( controller . AddDerivationScheme ( user . StoreId , "BTC" ) ) . Model ;
2019-05-09 09:11:09 +02:00
derivationVM = ( DerivationSchemeViewModel ) Assert . IsType < ViewResult > ( controller . AddDerivationScheme ( user . StoreId , derivationVM , "BTC" ) . GetAwaiter ( ) . GetResult ( ) ) . Model ;
Assert . True ( derivationVM . Confirmation ) ;
2018-07-30 17:18:58 +02:00
invoice = user . BitPay . CreateInvoice ( new Invoice ( )
{
Price = 1.5 m ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
Assert . Single ( invoice . CryptoInfo ) ;
Assert . Equal ( "LTC" , invoice . CryptoInfo [ 0 ] . CryptoCode ) ;
2019-05-09 09:11:09 +02:00
// Removing the derivation scheme, should redirect to store page
var oldScheme = derivationVM . DerivationScheme ;
derivationVM = ( DerivationSchemeViewModel ) Assert . IsType < ViewResult > ( controller . AddDerivationScheme ( user . StoreId , "BTC" ) ) . Model ;
derivationVM . DerivationScheme = null ;
Assert . IsType < RedirectToActionResult > ( controller . AddDerivationScheme ( user . StoreId , derivationVM , "BTC" ) . GetAwaiter ( ) . GetResult ( ) ) ;
// Setting it again should redirect to the confirmation page
derivationVM = ( DerivationSchemeViewModel ) Assert . IsType < ViewResult > ( controller . AddDerivationScheme ( user . StoreId , "BTC" ) ) . Model ;
derivationVM . DerivationScheme = oldScheme ;
derivationVM = ( DerivationSchemeViewModel ) Assert . IsType < ViewResult > ( controller . AddDerivationScheme ( user . StoreId , derivationVM , "BTC" ) . GetAwaiter ( ) . GetResult ( ) ) . Model ;
Assert . True ( derivationVM . Confirmation ) ;
2019-05-09 10:51:46 +02:00
// Can we upload coldcard settings? (Should fail, we are giving a mainnet file to a testnet network)
2019-05-09 09:11:09 +02:00
derivationVM = ( DerivationSchemeViewModel ) Assert . IsType < ViewResult > ( controller . AddDerivationScheme ( user . StoreId , "BTC" ) ) . Model ;
string content = "{\"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}" ;
2019-05-09 10:51:46 +02:00
derivationVM . ColdcardPublicFile = TestUtils . GetFormFile ( "wallet.json" , content ) ;
derivationVM = ( DerivationSchemeViewModel ) Assert . IsType < ViewResult > ( controller . AddDerivationScheme ( user . StoreId , derivationVM , "BTC" ) . GetAwaiter ( ) . GetResult ( ) ) . Model ;
Assert . False ( derivationVM . Confirmation ) ; // Should fail, we are giving a mainnet file to a testnet network
// And with a good file? (upub)
content = "{\"keystore\": {\"ckcc_xpub\": \"tpubD6NzVbkrYhZ4YHNiuTdTmHRmbcPRLfqgyneZFCL1mkzkUBjXriQShxTh9HL34FK2mhieasJVk9EzJrUfkFqRNQBjiXgx3n5BhPkxKBoFmaS\", \"xpub\": \"upub5DBYp1qGgsTrkzCptMGZc2x18pquLwGrBw6nS59T4NViZ4cni1mGowQzziy85K8vzkp1jVtWrSkLhqk9KDfvrGeB369wGNYf39kX8rQfiLn\", \"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}" ;
derivationVM = ( DerivationSchemeViewModel ) Assert . IsType < ViewResult > ( controller . AddDerivationScheme ( user . StoreId , "BTC" ) ) . Model ;
derivationVM . ColdcardPublicFile = TestUtils . GetFormFile ( "wallet2.json" , content ) ;
2019-05-09 11:58:14 +02:00
derivationVM . Enabled = true ;
2019-05-09 09:11:09 +02:00
derivationVM = ( DerivationSchemeViewModel ) Assert . IsType < ViewResult > ( controller . AddDerivationScheme ( user . StoreId , derivationVM , "BTC" ) . GetAwaiter ( ) . GetResult ( ) ) . Model ;
Assert . True ( derivationVM . Confirmation ) ;
Assert . IsType < RedirectToActionResult > ( controller . AddDerivationScheme ( user . StoreId , derivationVM , "BTC" ) . GetAwaiter ( ) . GetResult ( ) ) ;
// Now let's check that no data has been lost in the process
var store = tester . PayTester . StoreRepository . FindStore ( user . StoreId ) . GetAwaiter ( ) . GetResult ( ) ;
var onchainBTC = store . GetSupportedPaymentMethods ( tester . PayTester . Networks ) . OfType < DerivationSchemeSettings > ( ) . First ( o = > o . PaymentId . IsBTCOnChain ) ;
DerivationSchemeSettings . TryParseFromColdcard ( content , onchainBTC . Network , out var expected ) ;
Assert . Equal ( expected . ToJson ( ) , onchainBTC . ToJson ( ) ) ;
2019-05-09 11:58:14 +02:00
// Let's check that the root hdkey and account key path are taken into account when making a PSBT
invoice = user . BitPay . CreateInvoice ( new Invoice ( )
{
Price = 1.5 m ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
tester . ExplorerNode . Generate ( 1 ) ;
var invoiceAddress = BitcoinAddress . Create ( invoice . CryptoInfo . First ( c = > c . CryptoCode = = "BTC" ) . Address , tester . ExplorerNode . Network ) ;
tester . ExplorerNode . SendToAddress ( invoiceAddress , Money . Coins ( 1 m ) ) ;
TestUtils . Eventually ( ( ) = >
{
invoice = user . BitPay . GetInvoice ( invoice . Id ) ;
Assert . Equal ( "paid" , invoice . Status ) ;
} ) ;
var wallet = tester . PayTester . GetController < WalletsController > ( ) ;
2019-05-11 13:02:32 +02:00
var psbt = wallet . CreatePSBT ( btcNetwork , onchainBTC , new WalletSendModel ( )
2019-05-09 11:58:14 +02:00
{
2019-05-21 10:10:07 +02:00
Outputs = new List < WalletSendModel . TransactionOutput > ( )
{
new WalletSendModel . TransactionOutput ( )
{
Amount = 0.5 m ,
DestinationAddress = new Key ( ) . PubKey . GetAddress ( btcNetwork . NBitcoinNetwork ) . ToString ( ) ,
}
} ,
2019-05-09 11:58:14 +02:00
FeeSatoshiPerByte = 1
} , default ) . GetAwaiter ( ) . GetResult ( ) ;
Assert . NotNull ( psbt ) ;
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 ( ) . AsHDKeyCache ( ) ;
2019-05-12 17:13:55 +02:00
var account = root . Derive ( new KeyPath ( "m/49'/0'/0'" ) ) ;
2019-05-09 11:58:14 +02:00
Assert . All ( psbt . PSBT . Inputs , input = >
{
var keyPath = input . HDKeyPaths . Single ( ) ;
2019-05-14 09:06:43 +02:00
Assert . False ( keyPath . Value . KeyPath . IsHardened ) ;
Assert . Equal ( account . Derive ( keyPath . Value . KeyPath ) . GetPublicKey ( ) , keyPath . Key ) ;
Assert . Equal ( keyPath . Value . MasterFingerprint , onchainBTC . AccountKeySettings [ 0 ] . AccountKey . GetPublicKey ( ) . GetHDFingerPrint ( ) ) ;
2019-05-09 11:58:14 +02:00
} ) ;
2018-07-30 17:18:58 +02:00
}
}
2019-04-05 09:28:18 +02:00
[Fact(Timeout = 60 * 2 * 1000)]
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 ( ) )
{
tester . Start ( ) ;
2018-10-28 14:46:03 +01:00
await tester . EnsureChannelsSetup ( ) ;
2018-04-03 10:39:28 +02:00
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
user . RegisterLightningNode ( "BTC" , LightningConnectionType . Charge ) ;
2018-04-30 15:00:43 +02:00
var vm = Assert . IsType < CheckoutExperienceViewModel > ( Assert . IsType < ViewResult > ( user . GetController < StoresController > ( ) . CheckoutExperience ( ) ) . Model ) ;
2018-04-03 10:39:28 +02:00
vm . LightningMaxValue = "2 USD" ;
vm . OnChainMinValue = "5 USD" ;
2018-04-30 15:00:43 +02:00
Assert . IsType < RedirectToActionResult > ( user . GetController < StoresController > ( ) . CheckoutExperience ( vm ) . Result ) ;
2018-04-03 10:39:28 +02:00
var invoice = user . BitPay . CreateInvoice ( new Invoice ( )
{
2018-05-11 15:38:31 +02:00
Price = 1.5 m ,
2018-04-03 10:39:28 +02:00
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
Assert . Single ( invoice . CryptoInfo ) ;
Assert . Equal ( PaymentTypes . LightningLike . ToString ( ) , invoice . CryptoInfo [ 0 ] . PaymentType ) ;
invoice = user . BitPay . CreateInvoice ( new Invoice ( )
{
2018-05-11 15:38:31 +02:00
Price = 5.5 m ,
2018-04-03 10:39:28 +02:00
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
Assert . Single ( invoice . CryptoInfo ) ;
Assert . Equal ( PaymentTypes . BTCLike . ToString ( ) , invoice . CryptoInfo [ 0 ] . PaymentType ) ;
}
}
2018-04-03 09:53:55 +02:00
[Fact]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2018-04-03 09:53:55 +02:00
public void CanUsePoSApp ( )
{
using ( var tester = ServerTester . Create ( ) )
{
tester . Start ( ) ;
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
var apps = user . GetController < AppsController > ( ) ;
var vm = Assert . IsType < CreateAppViewModel > ( Assert . IsType < ViewResult > ( apps . CreateApp ( ) . Result ) . Model ) ;
vm . Name = "test" ;
vm . SelectedAppType = AppType . PointOfSale . ToString ( ) ;
Assert . IsType < RedirectToActionResult > ( apps . CreateApp ( vm ) . Result ) ;
var appId = Assert . IsType < ListAppsViewModel > ( Assert . IsType < ViewResult > ( apps . ListApps ( ) . Result ) . Model ) . Apps [ 0 ] . Id ;
var vmpos = Assert . IsType < UpdatePointOfSaleViewModel > ( Assert . IsType < ViewResult > ( apps . UpdatePointOfSale ( appId ) . Result ) . Model ) ;
vmpos . Title = "hello" ;
vmpos . Currency = "CAD" ;
2018-11-17 03:39:43 +01:00
vmpos . ButtonText = "{0} Purchase" ;
vmpos . CustomButtonText = "Nicolas Sexy Hair" ;
2018-11-27 07:14:32 +01:00
vmpos . CustomTipText = "Wanna tip?" ;
2018-12-13 14:36:19 +01:00
vmpos . CustomTipPercentages = "15,18,20" ;
2018-11-17 03:39:43 +01:00
vmpos . Template = @ "
apple :
2018-11-24 05:38:23 +01:00
price : 5.0
title : good apple
2018-11-17 03:39:43 +01:00
orange :
2018-11-24 05:38:23 +01:00
price : 10.0
2018-11-17 03:39:43 +01:00
donation :
2018-11-24 05:38:23 +01:00
price : 1.02
custom : true
2018-11-17 03:39:43 +01:00
";
2018-04-03 09:53:55 +02:00
Assert . IsType < RedirectToActionResult > ( apps . UpdatePointOfSale ( appId , vmpos ) . Result ) ;
vmpos = Assert . IsType < UpdatePointOfSaleViewModel > ( Assert . IsType < ViewResult > ( apps . UpdatePointOfSale ( appId ) . Result ) . Model ) ;
Assert . Equal ( "hello" , vmpos . Title ) ;
2018-08-30 20:16:46 +02:00
var publicApps = user . GetController < AppsPublicController > ( ) ;
var vmview = Assert . IsType < ViewPointOfSaleViewModel > ( Assert . IsType < ViewResult > ( publicApps . ViewPointOfSale ( appId ) . Result ) . Model ) ;
2018-04-03 09:53:55 +02:00
Assert . Equal ( "hello" , vmview . Title ) ;
2018-11-17 03:39:43 +01:00
Assert . Equal ( 3 , vmview . Items . Length ) ;
2018-04-03 09:53:55 +02:00
Assert . Equal ( "good apple" , vmview . Items [ 0 ] . Title ) ;
Assert . Equal ( "orange" , vmview . Items [ 1 ] . Title ) ;
Assert . Equal ( 10.0 m , vmview . Items [ 1 ] . Price . Value ) ;
Assert . Equal ( "$5.00" , vmview . Items [ 0 ] . Price . Formatted ) ;
2018-11-17 03:39:43 +01:00
Assert . Equal ( "{0} Purchase" , vmview . ButtonText ) ;
Assert . Equal ( "Nicolas Sexy Hair" , vmview . CustomButtonText ) ;
2018-11-27 07:14:32 +01:00
Assert . Equal ( "Wanna tip?" , vmview . CustomTipText ) ;
2018-12-27 08:11:58 +01:00
Assert . Equal ( "15,18,20" , string . Join ( ',' , vmview . CustomTipPercentages ) ) ;
2018-11-23 05:09:30 +01:00
Assert . IsType < RedirectToActionResult > ( publicApps . ViewPointOfSale ( appId , 0 , null , null , null , null , "orange" ) . Result ) ;
2018-11-17 03:39:43 +01:00
//
var invoices = user . BitPay . GetInvoices ( ) ;
var orangeInvoice = invoices . First ( ) ;
Assert . Equal ( 10.00 m , orangeInvoice . Price ) ;
Assert . Equal ( "CAD" , orangeInvoice . Currency ) ;
Assert . Equal ( "orange" , orangeInvoice . ItemDesc ) ;
2019-04-24 15:36:35 +02:00
Assert . IsType < RedirectToActionResult > ( publicApps . ViewPointOfSale ( appId , 0 , null , null , null , null , "apple" ) . Result ) ;
invoices = user . BitPay . GetInvoices ( ) ;
var appleInvoice = invoices . SingleOrDefault ( invoice = > invoice . ItemCode . Equals ( "apple" ) ) ;
Assert . NotNull ( appleInvoice ) ;
Assert . Equal ( "good apple" , appleInvoice . ItemDesc ) ;
2018-11-17 03:39:43 +01:00
// testing custom amount
2019-04-26 01:14:06 +02:00
var action = Assert . IsType < RedirectToActionResult > ( publicApps . ViewPointOfSale ( appId , 6.6 m , null , null , null , null , "donation" ) . Result ) ;
2018-11-23 05:09:30 +01:00
Assert . Equal ( nameof ( InvoiceController . Checkout ) , action . ActionName ) ;
2018-11-17 03:39:43 +01:00
invoices = user . BitPay . GetInvoices ( ) ;
2019-04-26 01:14:06 +02:00
var donationInvoice = invoices . Single ( i = > i . Price = = 6.6 m ) ;
2018-11-23 05:09:30 +01:00
Assert . NotNull ( donationInvoice ) ;
2018-11-17 03:39:43 +01:00
Assert . Equal ( "CAD" , donationInvoice . Currency ) ;
Assert . Equal ( "donation" , donationInvoice . ItemDesc ) ;
2018-12-17 16:25:17 +01:00
2018-12-18 16:28:06 +01:00
foreach ( var test in new [ ]
2018-12-17 16:25:17 +01:00
{
( Code : "EUR" , ExpectedSymbol : "€" , ExpectedDecimalSeparator : "," , ExpectedDivisibility : 2 , ExpectedThousandSeparator : "\xa0" , ExpectedPrefixed : false , ExpectedSymbolSpace : true ) ,
( Code : "INR" , ExpectedSymbol : "₹" , ExpectedDecimalSeparator : "." , ExpectedDivisibility : 2 , ExpectedThousandSeparator : "," , ExpectedPrefixed : true , ExpectedSymbolSpace : true ) ,
( Code : "JPY" , ExpectedSymbol : "¥" , ExpectedDecimalSeparator : "." , ExpectedDivisibility : 0 , ExpectedThousandSeparator : "," , ExpectedPrefixed : true , ExpectedSymbolSpace : false ) ,
( Code : "BTC" , ExpectedSymbol : "BTC" , ExpectedDecimalSeparator : "." , ExpectedDivisibility : 8 , ExpectedThousandSeparator : "," , ExpectedPrefixed : false , ExpectedSymbolSpace : true ) ,
} )
{
2018-12-17 17:02:27 +01:00
Logs . Tester . LogInformation ( $"Testing for {test.Code}" ) ;
2018-12-17 16:25:17 +01:00
vmpos = Assert . IsType < UpdatePointOfSaleViewModel > ( Assert . IsType < ViewResult > ( apps . UpdatePointOfSale ( appId ) . Result ) . Model ) ;
vmpos . Title = "hello" ;
vmpos . Currency = test . Item1 ;
vmpos . ButtonText = "{0} Purchase" ;
vmpos . CustomButtonText = "Nicolas Sexy Hair" ;
vmpos . CustomTipText = "Wanna tip?" ;
vmpos . Template = @ "
apple :
price : 1000.0
title : good apple
orange :
price : 10.0
donation :
price : 1.02
custom : true
";
Assert . IsType < RedirectToActionResult > ( apps . UpdatePointOfSale ( appId , vmpos ) . Result ) ;
publicApps = user . GetController < AppsPublicController > ( ) ;
vmview = Assert . IsType < ViewPointOfSaleViewModel > ( Assert . IsType < ViewResult > ( publicApps . ViewPointOfSale ( appId ) . Result ) . Model ) ;
Assert . Equal ( test . Code , vmview . CurrencyCode ) ;
2018-12-18 16:28:06 +01:00
Assert . Equal ( test . ExpectedSymbol , vmview . CurrencySymbol . Replace ( "¥" , "¥" ) ) ; // Hack so JPY test pass on linux as well);
Assert . Equal ( test . ExpectedSymbol , vmview . CurrencyInfo . CurrencySymbol . Replace ( "¥" , "¥" ) ) ; // Hack so JPY test pass on linux as well);
2018-12-17 16:25:17 +01:00
Assert . Equal ( test . ExpectedDecimalSeparator , vmview . CurrencyInfo . DecimalSeparator ) ;
Assert . Equal ( test . ExpectedThousandSeparator , vmview . CurrencyInfo . ThousandSeparator ) ;
Assert . Equal ( test . ExpectedPrefixed , vmview . CurrencyInfo . Prefixed ) ;
Assert . Equal ( test . ExpectedDivisibility , vmview . CurrencyInfo . Divisibility ) ;
Assert . Equal ( test . ExpectedSymbolSpace , vmview . CurrencyInfo . SymbolSpace ) ;
}
2018-04-03 09:53:55 +02:00
}
}
2018-11-30 09:34:43 +01: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" ) ;
2019-04-05 08:16:36 +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" ) ;
2019-04-05 08:16:36 +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" ) ;
2019-04-05 08:16:36 +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" ) ;
2019-04-05 08:16:36 +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
Assert . True ( new [ ] { false , false , false , false } . SequenceEqual ( jobs ) ) ;
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 ) ) ;
2019-01-16 11:14:45 +01:00
Assert . True ( new [ ] { false , true , false , false } . SequenceEqual ( jobs ) ) ;
2019-04-05 08:16:36 +02:00
await mockDelay . Advance ( TimeSpan . FromSeconds ( 3.0 ) ) ;
2019-01-16 11:14:45 +01:00
Assert . True ( new [ ] { true , true , false , false } . SequenceEqual ( jobs ) ) ;
2019-04-05 08:16:36 +02:00
await mockDelay . Advance ( TimeSpan . FromSeconds ( 1.0 ) ) ;
2019-01-16 11:14:45 +01:00
Assert . True ( new [ ] { true , true , true , false } . SequenceEqual ( jobs ) ) ;
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 ) ) ;
2019-01-16 11:14:45 +01:00
Assert . True ( new [ ] { true , true , true , true } . SequenceEqual ( jobs ) ) ;
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" ) ;
2019-04-05 08:16:36 +02:00
client . Schedule ( ( _ ) = > { jobExecuted = true ; return Task . CompletedTask ; } , TimeSpan . FromSeconds ( 1.0 ) ) ;
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 ) ) ;
}
2018-11-27 07:13:09 +01:00
[Fact]
[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
{
2019-01-26 05:26:49 +01:00
{ ( null , new Dictionary < string , object > ( ) ) } ,
{ ( "" , new Dictionary < string , object > ( ) ) } ,
{ ( "{}" , new Dictionary < string , object > ( ) ) } ,
{ ( "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
2018-11-27 07:13:09 +01:00
[Fact]
[Trait("Integration", "Integration")]
public async Task PosDataParser_ParsesCorrectly_Slower ( )
{
using ( var tester = ServerTester . Create ( ) )
{
tester . Start ( ) ;
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
{
2019-01-26 05:26:49 +01:00
{ ( null , new Dictionary < string , object > ( ) ) } ,
{ ( "" , new Dictionary < string , object > ( ) ) } ,
{ ( "{}" , new Dictionary < string , object > ( ) ) } ,
{ ( "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 )
{
tasks . Add ( user . BitPay . CreateInvoiceAsync ( new Invoice ( 1 , "BTC" )
{
PosData = valueTuple . input
} ) . 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 ) ;
} ) ) ;
}
await Task . WhenAll ( tasks ) ;
}
}
2018-11-30 09:34:43 +01:00
[Fact]
[Trait("Integration", "Integration")]
public void CanExportInvoicesJson ( )
{
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 ) ;
}
2018-11-30 09:34:43 +01:00
using ( var tester = ServerTester . Create ( ) )
{
tester . Start ( ) ;
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
2019-01-04 16:37:09 +01:00
user . SetNetworkFeeMode ( NetworkFeeMode . Always ) ;
2018-11-30 09:34:43 +01:00
var invoice = user . BitPay . CreateInvoice ( new Invoice ( )
{
2018-12-18 18:56:12 +01:00
Price = 10 ,
2018-11-30 09:34:43 +01:00
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
2018-11-30 11:34:18 +01:00
ItemDesc = "Some \", description" ,
2018-11-30 09:34:43 +01:00
FullNotifications = true
} , Facade . Merchant ) ;
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 ) ;
2018-12-18 18:56:12 +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
{
var jsonResultPaid = user . GetController < InvoiceController > ( ) . Export ( "json" ) . GetAwaiter ( ) . GetResult ( ) ;
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
} ) ;
}
}
2019-01-04 16:37:09 +01:00
[Fact]
[Trait("Integration", "Integration")]
public void CanChangeNetworkFeeMode ( )
{
using ( var tester = ServerTester . Create ( ) )
{
tester . Start ( ) ;
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 ) ;
var invoice = user . BitPay . CreateInvoice ( new Invoice ( )
{
Price = 10 ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some \", description" ,
FullNotifications = true
} , Facade . Merchant ) ;
var networkFee = Money . Satoshis ( 10000 ) . ToDecimal ( MoneyUnit . BTC ) ;
var missingMoney = Money . Satoshis ( 5000 ) . ToDecimal ( MoneyUnit . BTC ) ;
var cashCow = tester . ExplorerNode ;
var invoiceAddress = BitcoinAddress . Create ( invoice . CryptoInfo [ 0 ] . Address , cashCow . Network ) ;
// Check that for the first payment, no network fee are included
var due = Money . Parse ( invoice . CryptoInfo [ 0 ] . Due ) ;
var productPartDue = ( invoice . Price / invoice . Rate ) ;
switch ( networkFeeMode )
{
case NetworkFeeMode . MultiplePaymentsOnly :
case NetworkFeeMode . Never :
Assert . Equal ( productPartDue , due . ToDecimal ( MoneyUnit . BTC ) ) ;
break ;
case NetworkFeeMode . Always :
Assert . Equal ( productPartDue + networkFee , due . ToDecimal ( MoneyUnit . BTC ) ) ;
break ;
default :
throw new NotSupportedException ( networkFeeMode . ToString ( ) ) ;
}
var firstPayment = productPartDue - missingMoney ;
cashCow . SendToAddress ( invoiceAddress , Money . Coins ( firstPayment ) ) ;
2019-05-12 07:51:24 +02:00
TestUtils . Eventually ( ( ) = >
{
2019-01-04 16:37:09 +01:00
invoice = user . BitPay . GetInvoice ( invoice . Id ) ;
// Check that for the second payment, network fee are included
due = Money . Parse ( invoice . CryptoInfo [ 0 ] . Due ) ;
Assert . Equal ( Money . Coins ( firstPayment ) , Money . Parse ( invoice . CryptoInfo [ 0 ] . Paid ) ) ;
switch ( networkFeeMode )
{
case NetworkFeeMode . MultiplePaymentsOnly :
Assert . Equal ( missingMoney + networkFee , due . ToDecimal ( MoneyUnit . BTC ) ) ;
Assert . Equal ( firstPayment + missingMoney + networkFee , Money . Parse ( invoice . CryptoInfo [ 0 ] . TotalDue ) . ToDecimal ( MoneyUnit . BTC ) ) ;
break ;
case NetworkFeeMode . Always :
Assert . Equal ( missingMoney + 2 * networkFee , due . ToDecimal ( MoneyUnit . BTC ) ) ;
Assert . Equal ( firstPayment + missingMoney + 2 * networkFee , Money . Parse ( invoice . CryptoInfo [ 0 ] . TotalDue ) . ToDecimal ( MoneyUnit . BTC ) ) ;
break ;
case NetworkFeeMode . Never :
Assert . Equal ( missingMoney , due . ToDecimal ( MoneyUnit . BTC ) ) ;
Assert . Equal ( firstPayment + missingMoney , Money . Parse ( invoice . CryptoInfo [ 0 ] . TotalDue ) . ToDecimal ( MoneyUnit . BTC ) ) ;
break ;
default :
throw new NotSupportedException ( networkFeeMode . ToString ( ) ) ;
}
} ) ;
cashCow . SendToAddress ( invoiceAddress , due ) ;
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
[Fact]
[Trait("Integration", "Integration")]
public void CanExportInvoicesCsv ( )
{
using ( var tester = ServerTester . Create ( ) )
{
tester . Start ( ) ;
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
2019-01-04 16:37:09 +01:00
user . SetNetworkFeeMode ( NetworkFeeMode . Always ) ;
2018-11-30 09:34:43 +01:00
var invoice = user . BitPay . CreateInvoice ( new Invoice ( )
{
Price = 500 ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
2018-11-30 10:15:23 +01:00
ItemDesc = "Some \", description" ,
2018-11-30 09:34:43 +01:00
FullNotifications = true
} , Facade . Merchant ) ;
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
{
var exportResultPaid = user . GetController < InvoiceController > ( ) . Export ( "csv" ) . GetAwaiter ( ) . GetResult ( ) ;
var paidresult = Assert . IsType < ContentResult > ( exportResultPaid ) ;
Assert . Equal ( "application/csv" , paidresult . ContentType ) ;
2018-11-30 11:34:18 +01:00
Assert . Contains ( $",\" orderId \ ",\"{invoice.Id}\"," , paidresult . Content ) ;
2019-06-03 18:06:03 +02:00
Assert . Contains ( $",\" On - Chain \ ",\"BTC\",\"0.0991\",\"0.0001\",\"5000.0\"" , paidresult . Content ) ;
2019-01-10 06:00:26 +01:00
Assert . Contains ( $",\" USD \ ",\"5.00" , paidresult . Content ) ; // Seems hacky but some plateform does not render this decimal the same
Assert . Contains ( $"0\" , \ "500.0\",\"\",\"Some ``, description\",\"new (paidPartial)\"" , paidresult . Content ) ;
2018-11-30 09:34:43 +01:00
} ) ;
}
}
2018-04-03 09:53:55 +02:00
[Fact]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2018-04-03 09:53:55 +02:00
public void CanCreateAndDeleteApps ( )
{
using ( var tester = ServerTester . Create ( ) )
{
tester . Start ( ) ;
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 ) ;
var appList2 = Assert . IsType < ListAppsViewModel > ( Assert . IsType < ViewResult > ( apps2 . ListApps ( ) . Result ) . Model ) ;
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-01-15 14:12:29 +01:00
[Fact]
[Trait("Integration", "Integration")]
public void CanCreateStrangeInvoice ( )
{
using ( var tester = ServerTester . Create ( ) )
{
tester . Start ( ) ;
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
var invoice1 = user . BitPay . CreateInvoice ( new Invoice ( )
{
Price = 0.000000012 m ,
2019-02-22 14:48:39 +01:00
Currency = "USD" ,
2019-01-15 14:12:29 +01:00
FullNotifications = true
} , Facade . Merchant ) ;
var invoice2 = user . BitPay . CreateInvoice ( new Invoice ( )
{
Price = 0.000000019 m ,
2019-02-22 14:48:39 +01:00
Currency = "USD"
} , Facade . Merchant ) ;
Assert . Equal ( 0.000000012 m , invoice1 . Price ) ;
Assert . Equal ( 0.000000019 m , invoice2 . Price ) ;
// Should round up to 1 because 0.000000019 is unsignificant
var invoice3 = user . BitPay . CreateInvoice ( new Invoice ( )
{
Price = 1.000000019 m ,
Currency = "USD" ,
FullNotifications = true
} , Facade . Merchant ) ;
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
2019-02-22 14:48:39 +01:00
var invoice4 = user . BitPay . CreateInvoice ( new Invoice ( )
{
Price = 1.000000019 m ,
2019-01-15 14:12:29 +01:00
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
invoice4 = user . BitPay . CreateInvoice ( new Invoice ( )
{
Price = 0.000000019 m ,
Currency = "BTC" ,
FullNotifications = true
} , Facade . Merchant ) ;
Assert . Equal ( 0.000000019 m , invoice4 . Price ) ;
2019-01-15 14:12:29 +01:00
var invoice = user . BitPay . CreateInvoice ( new Invoice ( )
{
Price = - 0.1 m ,
Currency = "BTC" ,
FullNotifications = true
} , Facade . Merchant ) ;
Assert . Equal ( 0.0 m , invoice . Price ) ;
}
}
2018-01-07 18:36:41 +01:00
[Fact]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2018-01-07 18:36:41 +01:00
public void InvoiceFlowThroughDifferentStatesCorrectly ( )
{
using ( var tester = ServerTester . Create ( ) )
{
tester . Start ( ) ;
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
2018-02-23 07:21:42 +01:00
user . RegisterDerivationScheme ( "BTC" ) ;
2017-10-27 10:53:04 +02:00
var invoice = user . BitPay . CreateInvoice ( new Invoice ( )
{
2018-05-11 15:38:31 +02:00
Price = 5000.0 m ,
2019-01-24 12:53:29 +01:00
TaxIncluded = 1000.0 m ,
2017-10-27 10:53:04 +02:00
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
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" ) ) ;
2018-11-02 06:26:13 +01: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
{
2018-04-26 04:01:59 +02:00
StoreId = new [ ] { user . StoreId } ,
2017-11-12 15:03:33 +01:00
TextSearch = invoice . OrderId
} ) . 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
{
2018-04-26 04:01:59 +02:00
StoreId = new [ ] { user . StoreId } ,
2017-11-12 15:03:33 +01:00
TextSearch = invoice . Id
} ) . 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 ) ) ) ;
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 ( ) ;
2018-02-17 05:18:16 +01:00
var historical1 = invoiceEntity . HistoricalAddresses . FirstOrDefault ( h = > h . GetAddress ( ) = = invoice . BitcoinAddress ) ;
2017-10-27 10:53:04 +02:00
Assert . NotNull ( historical1 . UnAssigned ) ;
2018-02-17 05:18:16 +01: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 ( )
{
StoreId = new [ ] { user . StoreId } ,
TextSearch = txId . ToString ( )
} ) . 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-01-29 10:33:56 +01:00
//[Fact]
//[Trait("Integration", "Integration")]
// 29 january, the exchange is down
2019-01-29 10:35:27 +01:00
//public void CheckQuadrigacxRateProvider()
//{
// var quadri = new QuadrigacxRateProvider();
// var rates = quadri.GetRatesAsync().GetAwaiter().GetResult();
// Assert.NotEmpty(rates);
// Assert.NotEqual(0.0m, rates.First().BidAsk.Bid);
// Assert.NotEqual(0.0m, rates.GetRate(QuadrigacxRateProvider.QuadrigacxName, CurrencyPair.Parse("BTC_CAD")).Bid);
// Assert.NotEqual(0.0m, rates.GetRate(QuadrigacxRateProvider.QuadrigacxName, CurrencyPair.Parse("BTC_USD")).Bid);
// Assert.NotEqual(0.0m, rates.GetRate(QuadrigacxRateProvider.QuadrigacxName, CurrencyPair.Parse("LTC_CAD")).Bid);
// Assert.Null(rates.GetRate(QuadrigacxRateProvider.QuadrigacxName, CurrencyPair.Parse("LTC_USD")));
//}
2018-04-23 09:44:59 +02:00
2018-05-04 08:35:39 +02:00
[Fact]
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 ( ) ;
2018-05-13 08:09:17 +02:00
2018-05-04 08:35:39 +02:00
foreach ( var result in factory
2018-08-22 17:24:33 +02:00
. Providers
. Where ( p = > p . Value is BackgroundFetcherRateProvider )
2019-03-05 09:09:17 +01:00
. 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}" ) ;
if ( result . ExpectedName = = "quadrigacx" )
continue ; // 29 january, the exchange is down
2018-08-22 17:24:33 +02:00
result . Fetcher . InvalidateCache ( ) ;
2018-05-04 08:35:39 +02:00
var exchangeRates = 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 ] ) ;
2019-03-19 16:49:44 +01:00
if ( result . ExpectedName = = "bitbank" )
{
Assert . Contains ( exchangeRates . ByExchange [ result . ExpectedName ] ,
e = > e . CurrencyPair = = new CurrencyPair ( "BTC" , "JPY" ) & & e . BidAsk . Bid > 100 m ) ; // 1BTC will always be more than 100JPY
}
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" ) | |
2018-05-04 08:35:39 +02:00
e . CurrencyPair = = new CurrencyPair ( "BTC" , "EUR" ) | |
2019-03-05 08:07:23 +01:00
e . CurrencyPair = = new CurrencyPair ( "BTC" , "USDT" ) | |
e . CurrencyPair = = new CurrencyPair ( "BTC" , "CAD" ) )
2018-05-22 19:18:38 +02:00
& & e . BidAsk . Bid > 1.0 m // 1BTC will always be more than 1USD
2018-05-04 08:35:39 +02:00
) ;
2019-03-19 16:49:44 +01:00
}
2018-05-04 08:35:39 +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
}
2018-05-02 20:40:10 +02:00
[Fact]
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 =
provider . GetAll ( )
. 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
{
2018-08-22 17:24:33 +02:00
return new RateProviderFactory ( CreateMemoryCache ( ) , null , new CoinAverageSettings ( ) ) ;
}
private static MemoryCacheOptions CreateMemoryCache ( )
{
return new MemoryCacheOptions ( ) { ExpirationScanFrequency = TimeSpan . FromSeconds ( 1.0 ) } ;
}
class SpyRateProvider : IRateProvider
{
public bool Hit { get ; set ; }
2019-03-05 09:09:17 +01:00
public Task < ExchangeRates > GetRatesAsync ( CancellationToken cancellationToken )
2018-08-22 17:24:33 +02:00
{
Hit = true ;
var rates = new ExchangeRates ( ) ;
rates . Add ( new ExchangeRate ( "coinaverage" , CurrencyPair . Parse ( "BTC_USD" ) , new BidAsk ( 5000 ) ) ) ;
return Task . FromResult ( rates ) ;
}
public void AssertHit ( )
{
Assert . True ( Hit , "Should have hit the provider" ) ;
Hit = false ;
}
public void AssertNotHit ( )
{
Assert . False ( Hit , "Should have not hit the provider" ) ;
Hit = false ;
}
2018-05-04 08:35:39 +02:00
}
2018-11-07 14:29:35 +01:00
[Fact]
[Trait("Integration", "Integration")]
public async Task CheckLogsRoute ( )
{
using ( var tester = ServerTester . Create ( ) )
{
tester . Start ( ) ;
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 > ( ) ;
var vm = Assert . IsType < LogsViewModel > ( Assert . IsType < ViewResult > ( await serverController . LogsView ( ) ) . Model ) ;
}
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 ) ) ;
2019-06-10 11:16:12 +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 ) ;
2019-06-10 11:16:12 +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 ) ;
2019-06-10 11:16:12 +02:00
await Assert . ThrowsAsync < SecurityException > ( ( ) = > connStr . Expand ( new Uri ( "http://toto.com" ) , ExternalServiceTypes . Charge , NetworkType . Mainnet ) ) ;
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 ) ) ;
2019-06-10 11:16:12 +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
Assert . True ( ExternalConnectionString . TryParse ( $"server={unusedUri};macaroondirectorypath=pouet" , out connStr , out error ) ) ;
2019-06-10 11:16:12 +02:00
await Assert . ThrowsAsync < DirectoryNotFoundException > ( ( ) = > connStr . Expand ( unusedUri , ExternalServiceTypes . LNDGRPC , NetworkType . Mainnet ) ) ;
await Assert . ThrowsAsync < DirectoryNotFoundException > ( ( ) = > connStr . Expand ( unusedUri , ExternalServiceTypes . LNDRest , NetworkType . Mainnet ) ) ;
await connStr . Expand ( unusedUri , ExternalServiceTypes . Charge , NetworkType . Mainnet ) ;
2019-03-01 06:33:32 +01:00
var macaroonDirectory = CreateDirectory ( ) ;
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 ) ;
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 ) ;
Assert . True ( ExternalConnectionString . TryParse ( $"server={unusedUri};cookiefilepath={macaroonDirectory}/charge.cookie" , out connStr , out error ) ) ;
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 ;
}
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 CheckRatesProvider ( )
{
2018-08-22 17:24:33 +02:00
var spy = new SpyRateProvider ( ) ;
2018-05-02 20:32:42 +02:00
RateRules . TryParse ( "X_X = coinaverage(X_X);" , out var rateRules ) ;
2018-08-22 09:53:40 +02:00
var factory = CreateBTCPayRateFactory ( ) ;
2018-08-22 17:24:33 +02:00
factory . Providers . Clear ( ) ;
factory . Providers . Add ( "coinaverage" , new CachedRateProvider ( "coinaverage" , spy , new MemoryCache ( CreateMemoryCache ( ) ) ) ) ;
factory . Providers . Add ( "bittrex" , new CachedRateProvider ( "bittrex" , spy , new MemoryCache ( CreateMemoryCache ( ) ) ) ) ;
factory . CacheSpan = TimeSpan . FromSeconds ( 1 ) ;
var fetcher = new RateFetcher ( factory ) ;
2018-05-02 20:32:42 +02:00
2019-03-05 09:09:17 +01: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 ( ) ;
2018-05-02 20:32:42 +02:00
2018-08-22 17:24:33 +02:00
Thread . Sleep ( 3000 ) ;
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 . 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 ( ) ;
2018-05-02 20:32:42 +02:00
// Should cache at exchange level so this should hit the cache
2019-03-05 09:09:17 +01:00
var fetchedRate2 = fetcher . FetchRate ( CurrencyPair . Parse ( "LTC_USD" ) , rateRules , default ) . GetAwaiter ( ) . GetResult ( ) ;
2018-08-22 17:24:33 +02:00
spy . AssertNotHit ( ) ;
Assert . Null ( fetchedRate2 . BidAsk ) ;
Assert . Equal ( RateRulesErrors . RateUnavailable , fetchedRate2 . Errors . First ( ) ) ;
2018-05-02 20:32:42 +02:00
// Should cache at exchange level this should not hit the cache as it is different exchange
RateRules . TryParse ( "X_X = bittrex(X_X);" , out rateRules ) ;
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 . AssertHit ( ) ;
2018-05-02 20:32:42 +02:00
2018-08-22 17:24:33 +02:00
factory . Providers . Clear ( ) ;
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 ) ;
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 . 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" ) ;
2019-05-08 17:40:30 +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 ( ) ;
Assert . True ( DerivationSchemeSettings . TryParseFromColdcard ( "{\"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 ) ;
2019-05-15 09:00:03 +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 ( ) ) ;
2019-05-08 17:40:30 +02:00
Assert . Equal ( "ypub6WWc2gWwHbdnAAyJDnR4SPL1phRh7REqrPBfZeizaQ1EmTshieRXJC3Z5YoU4wkcdKHEjQGkh6AYEzCQC1Kz3DNaWSwdc1pc8416hAjzqyD" , settings . AccountOriginal ) ;
2019-08-17 08:14:31 +02:00
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
Assert . True ( DerivationSchemeSettings . TryParseFromColdcard ( "{\"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 ) ) ;
Assert . True ( settings . AccountDerivation is DirectDerivationStrategy s & & ! s . Segwit ) ;
// Should be segwit p2sh
Assert . True ( DerivationSchemeSettings . TryParseFromColdcard ( "{\"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 ) ;
// Should be segwit
Assert . True ( DerivationSchemeSettings . TryParseFromColdcard ( "{\"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 ) ) ;
Assert . True ( settings . AccountDerivation is DirectDerivationStrategy s3 & & s3 . Segwit ) ;
2019-05-08 17:40:30 +02:00
}
2019-01-29 08:44:46 +01:00
[Fact]
[Trait("Fast", "Fast")]
public void CheckParseStatusMessageModel ( )
{
var legacyStatus = "Error: some bad shit happened" ;
var parsed = new StatusMessageModel ( legacyStatus ) ;
Assert . Equal ( legacyStatus , parsed . Message ) ;
Assert . Equal ( StatusMessageModel . StatusSeverity . Error , parsed . Severity ) ;
2019-02-02 08:12:51 +01:00
2019-01-29 08:44:46 +01:00
var legacyStatus2 = "Some normal shit happened" ;
parsed = new StatusMessageModel ( legacyStatus2 ) ;
Assert . Equal ( legacyStatus2 , parsed . Message ) ;
Assert . Equal ( StatusMessageModel . StatusSeverity . Success , parsed . Severity ) ;
var newStatus = new StatusMessageModel ( )
{
Html = "<a href='xxx'>something new</a>" ,
Severity = StatusMessageModel . StatusSeverity . Info
} ;
parsed = new StatusMessageModel ( newStatus . ToString ( ) ) ;
Assert . Null ( parsed . Message ) ;
Assert . Equal ( newStatus . Html , parsed . Html ) ;
Assert . Equal ( StatusMessageModel . StatusSeverity . Info , parsed . Severity ) ;
2019-02-02 08:12:51 +01:00
2019-01-29 08:44:46 +01:00
var newStatus2 = new StatusMessageModel ( )
{
Message = "something new" ,
Severity = StatusMessageModel . StatusSeverity . Success
} ;
parsed = new StatusMessageModel ( newStatus2 . ToString ( ) ) ;
Assert . Null ( parsed . Html ) ;
Assert . Equal ( newStatus2 . Message , parsed . Message ) ;
Assert . Equal ( StatusMessageModel . StatusSeverity . Success , parsed . Severity ) ;
}
2019-05-02 14:29:51 +02:00
[Fact]
[Trait("Integration", "Integration")]
public async Task CanCreateInvoiceWithSpecificPaymentMethods ( )
{
using ( var tester = ServerTester . Create ( ) )
{
tester . Start ( ) ;
await tester . EnsureChannelsSetup ( ) ;
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
user . RegisterLightningNode ( "BTC" , LightningConnectionType . Charge ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
user . RegisterDerivationScheme ( "LTC" ) ;
var invoice = await user . BitPay . CreateInvoiceAsync ( new Invoice ( 100 , "BTC" ) ) ;
Assert . Equal ( 2 , invoice . SupportedTransactionCurrencies . Count ) ;
invoice = await user . BitPay . CreateInvoiceAsync ( new Invoice ( 100 , "BTC" )
{
SupportedTransactionCurrencies = new Dictionary < string , InvoiceSupportedTransactionCurrency > ( )
{
{ "BTC" , new InvoiceSupportedTransactionCurrency ( )
{
Enabled = true
} }
}
} ) ;
2019-05-02 14:38:39 +02:00
Assert . Single ( invoice . SupportedTransactionCurrencies ) ;
2019-05-02 14:29:51 +02:00
}
}
2019-01-29 08:44:46 +01:00
2019-05-02 14:01:08 +02:00
[Fact]
[Trait("Integration", "Integration")]
public async Task CanLoginWithNoSecondaryAuthSystemsOrRequestItWhenAdded ( )
{
using ( var tester = ServerTester . Create ( ) )
{
tester . Start ( ) ;
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
var accountController = tester . PayTester . GetController < AccountController > ( ) ;
//no 2fa or u2f enabled, login should work
Assert . Equal ( nameof ( HomeController . Index ) , Assert . IsType < RedirectToActionResult > ( await accountController . Login ( new LoginViewModel ( )
{
Email = user . RegisterDetails . Email ,
Password = user . RegisterDetails . Password
} ) ) . ActionName ) ;
var manageController = user . GetController < ManageController > ( ) ;
//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 ) ;
//sending an invalid response model back to server, should error out
var statusMessage = Assert
. IsType < RedirectToActionResult > ( await manageController . AddU2FDevice ( addRequest ) )
. RouteValues [ "StatusMessage" ] . ToString ( ) ;
Assert . NotNull ( statusMessage ) ;
Assert . Equal ( StatusMessageModel . StatusSeverity . Error , new StatusMessageModel ( statusMessage ) . 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 ( )
{
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 ( )
{
Email = user . RegisterDetails . Email ,
Password = user . RegisterDetails . Password
} ) ) ;
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 ) ;
}
}
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 ( ) ;
2018-02-19 03:31:34 +01:00
return ctx . AddressInvoices . FirstOrDefault ( i = > i . InvoiceDataId = = invoice . Id & & i . GetAddress ( ) = = h ) ! = null ;
2017-10-27 10:53:04 +02:00
}
}
2017-09-13 08:47:34 +02:00
}