2020-06-29 04:44:35 +02:00
using System ;
2020-06-28 10:55:27 +02:00
using System.Collections.Generic ;
using System.Globalization ;
using System.IO ;
2017-09-13 08:47:34 +02:00
using System.Linq ;
2020-06-28 10:55:27 +02:00
using System.Net ;
using System.Net.Http ;
using System.Runtime.CompilerServices ;
using System.Security ;
using System.Text ;
using System.Text.RegularExpressions ;
2017-09-13 08:47:34 +02:00
using System.Threading ;
2020-06-28 10:55:27 +02:00
using System.Threading.Tasks ;
2020-12-29 09:58:35 +01:00
using BTCPayServer.Abstractions.Contracts ;
2020-11-17 13:46:23 +01:00
using BTCPayServer.Abstractions.Models ;
2020-06-28 10:55:27 +02:00
using BTCPayServer.Client ;
using BTCPayServer.Client.Models ;
using BTCPayServer.Configuration ;
2017-10-11 05:20:44 +02:00
using BTCPayServer.Controllers ;
2017-10-24 18:41:01 +02:00
using BTCPayServer.Data ;
2020-06-28 10:55:27 +02:00
using BTCPayServer.Events ;
2021-04-28 06:14:15 +02:00
using BTCPayServer.Fido2 ;
using BTCPayServer.Fido2.Models ;
2020-06-28 10:55:27 +02:00
using BTCPayServer.HostedServices ;
2020-12-29 09:58:35 +01:00
using BTCPayServer.Hosting ;
2020-06-28 10:55:27 +02:00
using BTCPayServer.Lightning ;
2021-10-25 08:18:02 +02:00
using BTCPayServer.Lightning.CLightning ;
2020-06-28 10:55:27 +02:00
using BTCPayServer.Models ;
using BTCPayServer.Models.AccountViewModels ;
using BTCPayServer.Models.AppViewModels ;
using BTCPayServer.Models.InvoicingModels ;
2021-09-13 03:16:52 +02:00
using BTCPayServer.Models.ManageViewModels ;
2020-06-28 10:55:27 +02:00
using BTCPayServer.Models.ServerViewModels ;
2018-01-17 07:59:31 +01:00
using BTCPayServer.Models.StoreViewModels ;
2020-06-28 10:55:27 +02:00
using BTCPayServer.Models.WalletViewModels ;
2018-02-18 18:38:03 +01:00
using BTCPayServer.Payments ;
2018-02-19 10:54:21 +01:00
using BTCPayServer.Payments.Bitcoin ;
2018-02-25 16:48:12 +01:00
using BTCPayServer.Payments.Lightning ;
2021-03-01 14:44:53 +01:00
using BTCPayServer.Payments.PayJoin.Sender ;
2020-06-28 10:55:27 +02:00
using BTCPayServer.Rating ;
using BTCPayServer.Security.Bitpay ;
using BTCPayServer.Services ;
2018-04-03 09:53:55 +02:00
using BTCPayServer.Services.Apps ;
2020-06-28 10:55:27 +02:00
using BTCPayServer.Services.Invoices ;
2020-12-12 06:10:47 +01:00
using BTCPayServer.Services.Labels ;
2020-12-08 08:12:29 +01:00
using BTCPayServer.Services.Mails ;
2020-06-28 10:55:27 +02:00
using BTCPayServer.Services.Rates ;
2021-11-23 04:53:05 +01:00
using BTCPayServer.Storage.Models ;
using BTCPayServer.Storage.Services.Providers.FileSystemStorage.Configuration ;
using BTCPayServer.Storage.ViewModels ;
2020-06-28 10:55:27 +02:00
using BTCPayServer.Tests.Logging ;
2018-05-14 09:32:04 +02:00
using BTCPayServer.Validation ;
2018-05-04 08:35:39 +02:00
using ExchangeSharp ;
2021-04-28 06:14:15 +02:00
using Fido2NetLib ;
2021-11-23 04:53:05 +01:00
using Microsoft.AspNetCore.Http ;
2020-06-28 10:55:27 +02:00
using Microsoft.AspNetCore.Mvc ;
using Microsoft.EntityFrameworkCore ;
2020-12-29 09:58:35 +01:00
using Microsoft.Extensions.DependencyInjection ;
2021-04-18 04:26:06 +02:00
using Microsoft.Extensions.Options ;
2020-06-28 10:55:27 +02:00
using NBitcoin ;
using NBitcoin.DataEncoders ;
using NBitcoin.Payment ;
using NBitpayClient ;
2020-12-29 09:58:35 +01:00
using NBXplorer ;
2019-05-09 11:38:25 +02:00
using NBXplorer.DerivationStrategy ;
2020-06-28 10:55:27 +02:00
using NBXplorer.Models ;
using Newtonsoft.Json ;
using Newtonsoft.Json.Linq ;
2020-03-18 12:08:09 +01:00
using Newtonsoft.Json.Schema ;
2020-06-28 10:55:27 +02:00
using Xunit ;
using Xunit.Abstractions ;
using Xunit.Sdk ;
using RatesViewModel = BTCPayServer . Models . StoreViewModels . RatesViewModel ;
2017-09-13 08:47:34 +02:00
namespace BTCPayServer.Tests
{
2021-11-23 05:57:45 +01:00
[Collection(nameof(NonParallelizableCollectionDefinition))]
2021-11-22 09:16:08 +01:00
public class UnitTest1 : UnitTestBase
2017-10-27 10:53:04 +02:00
{
2021-04-18 04:26:06 +02:00
public const int LongRunningTestTimeout = 60_000 ; // 60s
2020-04-05 13:48:00 +02:00
2021-11-22 09:16:08 +01:00
public UnitTest1 ( ITestOutputHelper helper ) : base ( helper )
2017-10-27 10:53:04 +02:00
{
}
2020-07-29 15:12:46 +02:00
class DockerImage
{
public string User { get ; private set ; }
public string Name { get ; private set ; }
public string Tag { get ; private set ; }
public string Source { get ; set ; }
public static DockerImage Parse ( string str )
{
//${BTCPAY_IMAGE: -btcpayserver / btcpayserver:1.0.3.21}
var variableMatch = Regex . Match ( str , @"\$\{[^-]+-([^\}]+)\}" ) ;
if ( variableMatch . Success )
{
str = variableMatch . Groups [ 1 ] . Value ;
}
DockerImage img = new DockerImage ( ) ;
var match = Regex . Match ( str , "([^/]*/)?([^:]+):?(.*)" ) ;
if ( ! match . Success )
throw new FormatException ( ) ;
img . User = match . Groups [ 1 ] . Length = = 0 ? string . Empty : match . Groups [ 1 ] . Value . Substring ( 0 , match . Groups [ 1 ] . Value . Length - 1 ) ;
img . Name = match . Groups [ 2 ] . Value ;
img . Tag = match . Groups [ 3 ] . Value ;
if ( img . Tag = = string . Empty )
img . Tag = "latest" ;
return img ;
}
public override string ToString ( )
{
return ToString ( true ) ;
}
public string ToString ( bool includeTag )
{
StringBuilder builder = new StringBuilder ( ) ;
if ( ! String . IsNullOrWhiteSpace ( User ) )
builder . Append ( $"{User}/" ) ;
builder . Append ( $"{Name}" ) ;
if ( includeTag )
{
if ( ! String . IsNullOrWhiteSpace ( Tag ) )
builder . Append ( $":{Tag}" ) ;
}
return builder . ToString ( ) ;
}
}
2020-03-18 12:08:09 +01:00
[Fact]
2020-04-05 09:43:49 +02:00
[Trait("Integration", "Integration")]
2020-03-18 12:08:09 +01:00
public async Task CheckSwaggerIsConformToSchema ( )
{
2021-11-22 09:16:08 +01:00
using ( var tester = CreateServerTester ( ) )
2020-03-20 17:37:39 +01:00
{
2020-04-05 09:43:49 +02:00
await tester . StartAsync ( ) ;
2020-05-23 21:18:51 +02:00
var acc = tester . NewAccount ( ) ;
2020-04-05 13:48:00 +02:00
2020-05-23 21:18:51 +02:00
var sresp = Assert
. IsType < JsonResult > ( await tester . PayTester . GetController < HomeController > ( acc . UserId , acc . StoreId )
. Swagger ( ) ) . Value . ToJson ( ) ;
JObject swagger = JObject . Parse ( sresp ) ;
2021-11-23 04:53:05 +01:00
var schema = JSchema . Parse ( File . ReadAllText ( TestUtils . GetTestDataFullPath ( "OpenAPI-Specification-schema.json" ) ) ) ;
2020-04-05 09:43:49 +02:00
IList < ValidationError > errors ;
bool valid = swagger . IsValid ( schema , out errors ) ;
//the schema is not fully compliant to the spec. We ARE allowed to have multiple security schemas.
2020-04-05 11:26:40 +02:00
var matchedError = errors . Where ( error = >
error . Path = = "components.securitySchemes.Basic" & & error . ErrorType = = ErrorType . OneOf ) . ToList ( ) ;
foreach ( ValidationError validationError in matchedError )
2020-04-05 09:43:49 +02:00
{
2020-04-05 11:26:40 +02:00
errors . Remove ( validationError ) ;
2020-04-05 09:43:49 +02:00
}
2020-04-05 11:26:40 +02:00
valid = ! errors . Any ( ) ;
2020-04-05 09:43:49 +02:00
Assert . Empty ( errors ) ;
Assert . True ( valid ) ;
2020-03-20 17:37:39 +01:00
}
2020-03-18 12:08:09 +01:00
}
2020-11-24 10:10:32 +01:00
[Fact]
[Trait("Integration", "Integration")]
public async Task EnsureSwaggerPermissionsDocumented ( )
{
2021-11-22 09:16:08 +01:00
using ( var tester = CreateServerTester ( ) )
2020-11-24 10:10:32 +01:00
{
await tester . StartAsync ( ) ;
var acc = tester . NewAccount ( ) ;
var description =
2021-07-08 07:34:10 +02:00
"BTCPay Server supports authenticating and authorizing users through an API Key that is generated by them. Send the API Key as a header value to Authorization with the format: `token {token}`. For a smoother experience, you can generate a url that redirects users to an API key creation screen.\n\n The following permissions are available to the context of the user creating the API Key:\n\n#OTHERPERMISSIONS#\n\nThe following permissions are available if the user is an administrator:\n\n#SERVERPERMISSIONS#\n\nThe following permissions applies to all stores of the user, you can limit to a specific store with the following format: `btcpay.store.cancreateinvoice:6HSHAEU4iYWtjxtyRs9KyPjM9GAQp8kw2T9VWbGG1FnZ`:\n\n#STOREPERMISSIONS#\n\nNote that API Keys only limits permission of a user and can never expand it. If an API Key has the permission `btcpay.server.canmodifyserversettings` but that the user account creating this API Key is not administrator, the API Key will not be able to modify the server settings.\nSome permissions may include other permissions, see [this operation](#operation/permissionsMetadata).\n" ;
2020-11-24 10:10:32 +01:00
var storePolicies =
ManageController . AddApiKeyViewModel . PermissionValueItem . PermissionDescriptions . Where ( pair = >
Policies . IsStorePolicy ( pair . Key ) & & ! pair . Key . EndsWith ( ":" , StringComparison . InvariantCulture ) ) ;
var serverPolicies =
ManageController . AddApiKeyViewModel . PermissionValueItem . PermissionDescriptions . Where ( pair = >
Policies . IsServerPolicy ( pair . Key ) ) ;
var otherPolicies =
ManageController . AddApiKeyViewModel . PermissionValueItem . PermissionDescriptions . Where ( pair = >
! Policies . IsStorePolicy ( pair . Key ) & & ! Policies . IsServerPolicy ( pair . Key ) ) ;
2021-01-19 09:19:10 +01:00
description = description . Replace ( "#OTHERPERMISSIONS#" ,
string . Join ( "\n" , otherPolicies . Select ( pair = > $"* `{pair.Key}`: {pair.Value.Title}" ) ) )
. Replace ( "#SERVERPERMISSIONS#" ,
string . Join ( "\n" , serverPolicies . Select ( pair = > $"* `{pair.Key}`: {pair.Value.Title}" ) ) )
. Replace ( "#STOREPERMISSIONS#" ,
string . Join ( "\n" , storePolicies . Select ( pair = > $"* `{pair.Key}`: {pair.Value.Title}" ) ) ) ;
2021-11-22 09:16:08 +01:00
TestLogs . LogInformation ( description ) ;
2020-11-24 10:10:32 +01:00
var sresp = Assert
. IsType < JsonResult > ( await tester . PayTester . GetController < HomeController > ( acc . UserId , acc . StoreId )
. Swagger ( ) ) . Value . ToJson ( ) ;
JObject json = JObject . Parse ( sresp ) ;
Assert . Equal ( description , json [ "components" ] [ "securitySchemes" ] [ "API Key" ] [ "description" ] . Value < string > ( ) ) ;
}
}
2020-03-18 12:08:09 +01:00
2021-11-22 09:16:08 +01:00
private async Task CheckDeadLinks ( Regex regex , HttpClient httpClient , string file )
2019-09-02 09:32:38 +02:00
{
List < Task > checkLinks = new List < Task > ( ) ;
var text = await File . ReadAllTextAsync ( file ) ;
2020-03-27 00:58:12 +01:00
var urlBlacklist = new string [ ]
{
2020-03-31 11:13:22 +02:00
"https://www.btse.com" , // not allowing to be hit from circleci
2021-06-14 11:32:04 +02:00
"https://www.bitpay.com" , // not allowing to be hit from circleci
2021-10-15 05:50:33 +02:00
"https://support.bitpay.com" ,
2021-06-14 11:32:04 +02:00
"https://www.pnxbet.com" //has geo blocking
2020-03-27 00:58:12 +01:00
} ;
2019-09-02 09:32:38 +02:00
foreach ( var match in regex . Matches ( text ) . OfType < Match > ( ) )
{
2020-03-27 00:58:12 +01:00
var url = match . Groups [ 1 ] . Value ;
2021-10-15 07:19:40 +02:00
if ( urlBlacklist . Any ( a = > url . StartsWith ( a . ToLowerInvariant ( ) ) ) )
2020-03-27 00:58:12 +01:00
continue ;
checkLinks . Add ( AssertLinkNotDead ( httpClient , url , file ) ) ;
2019-09-02 09:32:38 +02:00
}
2020-04-05 13:48:00 +02:00
2019-09-02 09:32:38 +02:00
await Task . WhenAll ( checkLinks ) ;
}
2021-11-22 09:16:08 +01:00
private async Task AssertLinkNotDead ( HttpClient httpClient , string url , string file )
2019-09-02 09:32:38 +02:00
{
2020-06-23 17:51:25 +02:00
var uri = new Uri ( url ) ;
2019-09-02 09:32:38 +02:00
try
{
2020-06-23 17:51:25 +02:00
using var request = new HttpRequestMessage ( HttpMethod . Get , uri ) ;
2020-04-05 13:48:00 +02:00
request . Headers . TryAddWithoutValidation ( "Accept" ,
"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8" ) ;
request . Headers . TryAddWithoutValidation ( "User-Agent" ,
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:75.0) Gecko/20100101 Firefox/75.0" ) ;
2020-06-23 17:51:25 +02:00
var response = await httpClient . SendAsync ( request ) ;
2021-11-04 16:36:05 +01:00
if ( response . StatusCode = = HttpStatusCode . ServiceUnavailable ) // Temporary issue
{
2021-11-22 09:16:08 +01:00
TestLogs . LogInformation ( $"Unavailable: {url} ({file})" ) ;
2021-11-04 16:36:05 +01:00
return ;
}
2020-06-23 17:51:25 +02:00
Assert . Equal ( HttpStatusCode . OK , response . StatusCode ) ;
if ( uri . Fragment . Length ! = 0 )
{
var fragment = uri . Fragment . Substring ( 1 ) ;
var contents = await response . Content . ReadAsStringAsync ( ) ;
Assert . Matches ( $"id=\" { fragment } \ "" , contents ) ;
}
2021-11-22 09:16:08 +01:00
TestLogs . LogInformation ( $"OK: {url} ({file})" ) ;
2019-09-02 09:32:38 +02:00
}
2020-06-23 17:51:25 +02:00
catch ( Exception ex ) when ( ex is MatchesException )
{
var details = ex . Message ;
2021-11-22 09:16:08 +01:00
TestLogs . LogInformation ( $"FAILED: {url} ({file}) – anchor not found: {uri.Fragment}" ) ;
2020-06-23 17:51:25 +02:00
throw ;
}
2020-04-18 19:33:53 +02:00
catch ( Exception ex )
2019-09-02 09:32:38 +02:00
{
2020-04-18 19:33:53 +02:00
var details = ex is EqualException ? ( ex as EqualException ) . Actual : ex . Message ;
2021-11-22 09:16:08 +01:00
TestLogs . LogInformation ( $"FAILED: {url} ({file}) {details}" ) ;
2020-06-24 03:34:09 +02:00
2019-09-02 09:32:38 +02:00
throw ;
}
}
2018-05-05 17:40:44 +02:00
[Fact]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2019-10-07 09:04:25 +02:00
public async Task CanAcceptInvoiceWithTolerance2 ( )
2018-05-05 17:40:44 +02:00
{
2021-11-22 09:16:08 +01:00
using ( var tester = CreateServerTester ( ) )
2018-05-05 17:40:44 +02:00
{
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2018-05-05 17:40:44 +02:00
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
2018-05-13 08:09:17 +02:00
2018-05-05 17:40:44 +02:00
// Set tolerance to 50%
var stores = user . GetController < StoresController > ( ) ;
2021-10-29 08:25:43 +02:00
var response = stores . PaymentMethods ( ) ;
var vm = Assert . IsType < PaymentMethodsViewModel > ( Assert . IsType < ViewResult > ( response ) . Model ) ;
2018-05-05 17:40:44 +02:00
Assert . Equal ( 0.0 , vm . PaymentTolerance ) ;
vm . PaymentTolerance = 50.0 ;
2021-10-29 08:25:43 +02:00
Assert . IsType < RedirectToActionResult > ( stores . PaymentMethods ( vm ) . Result ) ;
2018-05-05 17:40:44 +02:00
2020-04-05 13:48:00 +02:00
var invoice = user . BitPay . CreateInvoice (
new Invoice ( )
{
2020-06-24 03:34:09 +02:00
Buyer = new Buyer ( ) { email = "test@fwf.com" } ,
2020-04-05 13:48:00 +02:00
Price = 5000.0 m ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
2018-05-05 17:40:44 +02:00
// Pays 75%
var invoiceAddress = BitcoinAddress . Create ( invoice . CryptoInfo [ 0 ] . Address , tester . ExplorerNode . Network ) ;
2020-04-05 13:48:00 +02:00
tester . ExplorerNode . SendToAddress ( invoiceAddress ,
2020-06-29 05:07:48 +02:00
Money . Satoshis ( invoice . BtcDue . Satoshi * 0.75 m ) ) ;
2018-05-05 17:40:44 +02:00
2019-01-05 19:47:39 +01:00
TestUtils . Eventually ( ( ) = >
2018-05-05 17:40:44 +02:00
{
var localInvoice = user . BitPay . GetInvoice ( invoice . Id , Facade . Merchant ) ;
Assert . Equal ( "paid" , localInvoice . Status ) ;
} ) ;
}
}
2020-02-03 09:21:03 +01:00
[Fact]
[Trait("Integration", "Integration")]
public async Task CanThrowBitpay404Error ( )
{
2021-11-22 09:16:08 +01:00
using ( var tester = CreateServerTester ( ) )
2020-02-03 09:21:03 +01:00
{
await tester . StartAsync ( ) ;
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
2020-04-05 13:48:00 +02:00
var invoice = user . BitPay . CreateInvoice (
new Invoice ( )
{
2020-06-24 03:34:09 +02:00
Buyer = new Buyer ( ) { email = "test@fwf.com" } ,
2020-04-05 13:48:00 +02:00
Price = 5000.0 m ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
2020-02-03 09:21:03 +01:00
try
{
var throwsBitpay404Error = user . BitPay . GetInvoice ( invoice . Id + "123" ) ;
}
catch ( BitPayException ex )
{
Assert . Equal ( "Object not found" , ex . Errors . First ( ) ) ;
}
2020-05-01 14:33:27 +02:00
var req = new HttpRequestMessage ( HttpMethod . Get , "/invoices/Cy9jfK82eeEED1T3qhwF3Y" ) ;
req . Headers . TryAddWithoutValidation ( "Authorization" , "Basic dGVzdA==" ) ;
req . Content = new StringContent ( "{}" , Encoding . UTF8 , "application/json" ) ;
var result = await tester . PayTester . HttpClient . SendAsync ( req ) ;
Assert . Equal ( HttpStatusCode . Unauthorized , result . StatusCode ) ;
Assert . Equal ( 0 , result . Content . Headers . ContentLength . Value ) ;
2020-02-03 09:21:03 +01:00
}
}
2021-10-25 08:18:02 +02:00
2020-09-15 13:46:45 +02:00
[Fact(Timeout = 60 * 2 * 1000)]
[Trait("Integration", "Integration")]
[Trait("Lightning", "Lightning")]
public async Task EnsureNewLightningInvoiceOnPartialPayment ( )
{
2021-11-22 09:16:08 +01:00
using var tester = CreateServerTester ( ) ;
2020-09-15 13:46:45 +02:00
tester . ActivateLightning ( ) ;
await tester . StartAsync ( ) ;
await tester . EnsureChannelsSetup ( ) ;
var user = tester . NewAccount ( ) ;
2021-03-02 03:11:58 +01:00
await user . GrantAccessAsync ( true ) ;
2020-09-15 13:46:45 +02:00
await user . RegisterDerivationSchemeAsync ( "BTC" ) ;
await user . RegisterLightningNodeAsync ( "BTC" , LightningConnectionType . CLightning ) ;
2021-06-18 03:25:17 +02:00
await user . SetNetworkFeeMode ( NetworkFeeMode . Never ) ;
2021-10-29 08:25:43 +02:00
await user . ModifyWalletSettings ( p = > p . SpeedPolicy = SpeedPolicy . HighSpeed ) ;
2020-09-24 12:18:19 +02:00
var invoice = await user . BitPay . CreateInvoiceAsync ( new Invoice ( 0.0001 m , "BTC" ) ) ;
2020-10-01 13:15:46 +02:00
await tester . WaitForEvent < InvoiceNewPaymentDetailsEvent > ( async ( ) = >
2020-09-15 13:46:45 +02:00
{
await tester . ExplorerNode . SendToAddressAsync (
2020-09-24 12:18:19 +02:00
BitcoinAddress . Create ( invoice . BitcoinAddress , Network . RegTest ) , Money . Coins ( 0.00005 m ) ) ;
2020-12-08 10:02:03 +01:00
} , e = > e . InvoiceId = = invoice . Id & & e . PaymentMethodId . PaymentType = = LightningPaymentType . Instance ) ;
2020-09-24 12:18:19 +02:00
await tester . ExplorerNode . GenerateAsync ( 1 ) ;
2021-07-16 09:57:37 +02:00
Invoice newInvoice = null ;
await TestUtils . EventuallyAsync ( async ( ) = >
{
2021-08-23 12:34:36 +02:00
await Task . Delay ( 1000 ) ; // wait a bit for payment to process before fetching new invoice
2021-07-16 09:57:37 +02:00
newInvoice = await user . BitPay . GetInvoiceAsync ( invoice . Id ) ;
var newBolt11 = newInvoice . CryptoInfo . First ( o = > o . PaymentUrls . BOLT11 ! = null ) . PaymentUrls . BOLT11 ;
var oldBolt11 = invoice . CryptoInfo . First ( o = > o . PaymentUrls . BOLT11 ! = null ) . PaymentUrls . BOLT11 ;
Assert . NotEqual ( newBolt11 , oldBolt11 ) ;
Assert . Equal ( newInvoice . BtcDue . GetValue ( ) ,
BOLT11PaymentRequest . Parse ( newBolt11 , Network . RegTest ) . MinimumAmount . ToDecimal ( LightMoneyUnit . BTC ) ) ;
2021-08-23 12:34:36 +02:00
} , 40000 ) ;
2021-07-16 09:57:37 +02:00
2021-11-22 09:16:08 +01:00
TestLogs . LogInformation ( $"Paying invoice {newInvoice.Id} remaining due amount {newInvoice.BtcDue.GetValue()} via lightning" ) ;
2020-09-16 14:50:06 +02:00
var evt = await tester . WaitForEvent < InvoiceDataChangedEvent > ( async ( ) = >
{
await tester . SendLightningPaymentAsync ( newInvoice ) ;
2020-09-24 12:18:19 +02:00
} , evt = > evt . InvoiceId = = invoice . Id ) ;
var fetchedInvoice = await tester . PayTester . InvoiceRepository . GetInvoice ( evt . InvoiceId ) ;
2020-11-23 07:57:05 +01:00
Assert . Contains ( fetchedInvoice . Status , new [ ] { InvoiceStatusLegacy . Complete , InvoiceStatusLegacy . Confirmed } ) ;
2020-09-24 12:18:19 +02:00
Assert . Equal ( InvoiceExceptionStatus . None , fetchedInvoice . ExceptionStatus ) ;
2020-11-09 08:48:45 +01:00
2021-10-25 08:18:02 +02:00
//BTCPay will attempt to cancel previous bolt11 invoices so that there are less weird edge case scenarios
2021-11-22 09:16:08 +01:00
TestLogs . LogInformation ( $"Attempting to pay invoice {invoice.Id} original full amount bolt11 invoice " ) ;
2021-10-25 08:18:02 +02:00
await Assert . ThrowsAsync < LightningRPCException > ( async ( ) = >
2020-09-16 14:50:06 +02:00
{
await tester . SendLightningPaymentAsync ( invoice ) ;
2021-10-25 08:18:02 +02:00
} ) ;
//NOTE: Eclair does not support cancelling invoice so the below test case would make sense for it
2021-11-22 09:16:08 +01:00
// TestLogs.LogInformation($"Paying invoice {invoice.Id} original full amount bolt11 invoice ");
2021-10-25 08:18:02 +02:00
// evt = await tester.WaitForEvent<InvoiceDataChangedEvent>(async () =>
// {
// await tester.SendLightningPaymentAsync(invoice);
// }, evt => evt.InvoiceId == invoice.Id);
// Assert.Equal(evt.InvoiceId, invoice.Id);
// fetchedInvoice = await tester.PayTester.InvoiceRepository.GetInvoice(evt.InvoiceId);
// Assert.Equal(3, fetchedInvoice.Payments.Count);
2020-09-15 13:46:45 +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")]
2019-12-24 10:11:21 +01:00
[Trait("Lightning", "Lightning")]
2018-10-28 14:46:03 +01:00
public async Task CanSetLightningServer ( )
2018-02-25 16:48:12 +01:00
{
2021-11-22 09:16:08 +01:00
using ( var tester = CreateServerTester ( ) )
2018-02-25 16:48:12 +01:00
{
2019-12-24 10:11:21 +01:00
tester . ActivateLightning ( ) ;
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2018-10-28 14:46:03 +01:00
await tester . EnsureChannelsSetup ( ) ;
2018-02-25 16:48:12 +01:00
var user = tester . NewAccount ( ) ;
2021-03-02 03:11:58 +01:00
user . GrantAccess ( true ) ;
2018-04-29 19:33:42 +02:00
var storeController = user . GetController < StoresController > ( ) ;
2021-10-29 08:25:43 +02:00
var storeResponse = storeController . PaymentMethods ( ) ;
2021-06-18 03:25:17 +02:00
Assert . IsType < ViewResult > ( storeResponse ) ;
2021-07-27 14:08:54 +02:00
Assert . IsType < ViewResult > ( await storeController . SetupLightningNode ( user . StoreId , "BTC" ) ) ;
2018-02-25 16:48:12 +01:00
2021-04-19 16:21:50 +02:00
var testResult = storeController . SetupLightningNode ( user . StoreId , new LightningNodeViewModel
2018-02-25 16:48:12 +01:00
{
2020-08-16 15:09:10 +02:00
ConnectionString = $"type=charge;server={tester.MerchantCharge.Client.Uri.AbsoluteUri};allowinsecure=true" ,
2018-07-19 07:49:30 +02:00
SkipPortTest = true // We can't test this as the IP can't be resolved by the test host :(
2018-03-20 18:48:11 +01:00
} , "test" , "BTC" ) . GetAwaiter ( ) . GetResult ( ) ;
2019-10-31 04:29:59 +01:00
Assert . False ( storeController . TempData . ContainsKey ( WellKnownTempData . ErrorMessage ) ) ;
storeController . TempData . Clear ( ) ;
2018-02-25 16:48:12 +01:00
Assert . True ( storeController . ModelState . IsValid ) ;
2021-04-19 16:21:50 +02:00
Assert . IsType < RedirectToActionResult > ( storeController . SetupLightningNode ( user . StoreId ,
new LightningNodeViewModel
2020-04-05 13:48:00 +02:00
{
2020-08-16 15:09:10 +02:00
ConnectionString = $"type=charge;server={tester.MerchantCharge.Client.Uri.AbsoluteUri};allowinsecure=true"
2020-04-05 13:48:00 +02:00
} , "save" , "BTC" ) . GetAwaiter ( ) . GetResult ( ) ) ;
2018-07-01 09:10:17 +02:00
// Make sure old connection string format does not work
2021-04-20 12:23:50 +02:00
Assert . IsType < RedirectToActionResult > ( storeController . SetupLightningNode ( user . StoreId ,
2021-04-19 16:21:50 +02:00
new LightningNodeViewModel { ConnectionString = tester . MerchantCharge . Client . Uri . AbsoluteUri } ,
2020-04-05 13:48:00 +02:00
"save" , "BTC" ) . GetAwaiter ( ) . GetResult ( ) ) ;
2018-02-25 16:48:12 +01:00
2021-10-29 08:25:43 +02:00
storeResponse = storeController . PaymentMethods ( ) ;
2020-04-05 13:48:00 +02:00
var storeVm =
2021-10-29 08:25:43 +02:00
Assert . IsType < PaymentMethodsViewModel > ( Assert
2021-06-18 03:25:17 +02:00
. IsType < ViewResult > ( storeResponse ) . Model ) ;
2018-03-20 18:48:11 +01:00
Assert . Single ( storeVm . LightningNodes . Where ( l = > ! string . IsNullOrEmpty ( l . Address ) ) ) ;
2018-02-25 16:48:12 +01:00
}
2017-12-13 07:49:19 +01:00
}
2019-03-27 10:57:51 +01:00
[Fact(Timeout = 60 * 2 * 1000)]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2019-12-24 10:11:21 +01:00
[Trait("Lightning", "Lightning")]
2018-08-30 04:50:39 +02:00
public async Task CanSendLightningPaymentCLightning ( )
2018-03-20 16:31:19 +01:00
{
2018-08-30 04:50:39 +02:00
await ProcessLightningPayment ( LightningConnectionType . CLightning ) ;
2018-03-20 16:31:19 +01:00
}
2019-03-27 10:57:51 +01:00
[Fact(Timeout = 60 * 2 * 1000)]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2019-12-24 10:11:21 +01:00
[Trait("Lightning", "Lightning")]
2018-08-30 04:50:39 +02:00
public async Task CanSendLightningPaymentCharge ( )
2018-02-23 07:21:42 +01:00
{
2018-08-30 04:50:39 +02:00
await ProcessLightningPayment ( LightningConnectionType . Charge ) ;
2018-05-25 19:18:47 +02:00
}
2018-02-23 07:21:42 +01:00
2019-03-27 10:57:51 +01:00
[Fact(Timeout = 60 * 2 * 1000)]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2019-12-24 10:11:21 +01:00
[Trait("Lightning", "Lightning")]
2018-08-30 04:50:39 +02:00
public async Task CanSendLightningPaymentLnd ( )
2018-05-31 23:07:59 +02:00
{
2018-08-30 04:50:39 +02:00
await ProcessLightningPayment ( LightningConnectionType . LndREST ) ;
2018-05-31 23:07:59 +02:00
}
2018-05-25 19:18:47 +02:00
2018-08-30 04:50:39 +02:00
async Task ProcessLightningPayment ( LightningConnectionType type )
2018-05-25 19:18:47 +02:00
{
2018-06-15 20:57:39 +02:00
// For easier debugging and testing
2018-06-16 00:20:56 +02:00
// LightningLikePaymentHandler.LIGHTNING_TIMEOUT = int.MaxValue;
2018-02-23 07:21:42 +01:00
2021-11-22 09:16:08 +01:00
using ( var tester = CreateServerTester ( ) )
2018-02-23 07:21:42 +01:00
{
2019-12-24 10:11:21 +01:00
tester . ActivateLightning ( ) ;
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2018-10-28 14:46:03 +01:00
await tester . EnsureChannelsSetup ( ) ;
2018-02-25 16:48:12 +01:00
var user = tester . NewAccount ( ) ;
2021-03-02 03:11:58 +01:00
user . GrantAccess ( true ) ;
2018-05-25 19:18:47 +02:00
user . RegisterLightningNode ( "BTC" , type ) ;
2018-02-25 16:48:12 +01:00
user . RegisterDerivationScheme ( "BTC" ) ;
2018-07-27 11:04:41 +02:00
2018-08-30 04:50:39 +02:00
await CanSendLightningPaymentCore ( tester , user ) ;
2018-02-26 05:29:23 +01:00
2018-08-30 04:50:39 +02:00
await Task . WhenAll ( Enumerable . Range ( 0 , 5 )
2018-02-26 05:29:23 +01:00
. Select ( _ = > CanSendLightningPaymentCore ( tester , user ) )
. ToArray ( ) ) ;
2018-02-23 07:21:42 +01:00
}
}
2018-02-26 05:29:23 +01:00
async Task CanSendLightningPaymentCore ( ServerTester tester , TestAccount user )
{
var invoice = await user . BitPay . CreateInvoiceAsync ( new Invoice ( )
{
2018-05-11 15:38:31 +02:00
Price = 0.01 m ,
2018-02-26 05:29:23 +01:00
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description"
} ) ;
2018-10-28 16:22:30 +01:00
await Task . Delay ( TimeSpan . FromMilliseconds ( 1000 ) ) ; // Give time to listen the new invoices
2021-11-22 09:16:08 +01:00
TestLogs . LogInformation ( $"Trying to send Lightning payment to {invoice.Id}" ) ;
2018-02-26 05:29:23 +01:00
await tester . SendLightningPaymentAsync ( invoice ) ;
2021-11-22 09:16:08 +01:00
TestLogs . LogInformation ( $"Lightning payment to {invoice.Id} is sent" ) ;
2019-01-05 19:47:39 +01:00
await TestUtils . EventuallyAsync ( async ( ) = >
2018-02-26 05:29:23 +01:00
{
var localInvoice = await user . BitPay . GetInvoiceAsync ( invoice . Id ) ;
Assert . Equal ( "complete" , localInvoice . Status ) ;
2020-01-10 03:13:54 +01:00
// C-Lightning may overpay for privacy
2020-06-24 03:34:09 +02:00
Assert . Contains ( localInvoice . ExceptionStatus . ToString ( ) , new [ ] { "False" , "paidOver" } ) ;
2018-02-26 05:29:23 +01:00
} ) ;
}
2021-04-18 04:26:06 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2019-10-07 09:04:25 +02:00
public async Task CanUseServerInitiatedPairingCode ( )
2017-10-27 10:53:04 +02:00
{
2021-11-22 09:16:08 +01:00
using ( var tester = CreateServerTester ( ) )
2017-10-27 10:53:04 +02:00
{
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2017-10-27 10:53:04 +02:00
var acc = tester . NewAccount ( ) ;
acc . Register ( ) ;
acc . CreateStore ( ) ;
2018-04-29 19:33:42 +02:00
var controller = acc . GetController < StoresController > ( ) ;
2020-04-05 13:48:00 +02:00
var token = ( RedirectToActionResult ) await controller . CreateToken2 (
new Models . StoreViewModels . CreateTokenViewModel ( )
{
2020-06-24 03:34:09 +02:00
Label = "bla" ,
PublicKey = null ,
StoreId = acc . StoreId
2020-04-05 13:48:00 +02:00
} ) ;
2017-10-27 10:53:04 +02:00
var pairingCode = ( string ) token . RouteValues [ "pairingCode" ] ;
acc . BitPay . AuthorizeClient ( new PairingCode ( pairingCode ) ) . GetAwaiter ( ) . GetResult ( ) ;
Assert . True ( acc . BitPay . TestAccess ( Facade . Merchant ) ) ;
}
}
2021-04-18 04:26:06 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
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 ( ) )
{
2021-11-22 09:16:08 +01:00
using ( var tester = CreateServerTester ( ) )
2017-10-27 10:53:04 +02:00
{
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2017-10-27 10:53:04 +02:00
var acc = tester . NewAccount ( ) ;
acc . GrantAccess ( ) ;
2018-02-23 07:21:42 +01:00
acc . RegisterDerivationScheme ( "BTC" ) ;
2021-10-29 08:25:43 +02:00
await acc . ModifyWalletSettings ( p = > p . SpeedPolicy = SpeedPolicy . LowSpeed ) ;
2021-06-18 03:25:17 +02:00
var invoice = acc . BitPay . CreateInvoice ( new Invoice
2017-10-27 10:53:04 +02:00
{
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
} ) ;
2021-11-22 16:49:51 +01:00
#pragma warning disable CS0618
2020-04-05 13:48:00 +02:00
BitcoinUrlBuilder url = new BitcoinUrlBuilder ( invoice . PaymentUrls . BIP21 ,
tester . NetworkProvider . BTC . NBitcoinNetwork ) ;
2019-01-06 07:04:30 +01:00
bool receivedPayment = false ;
bool paid = false ;
bool confirmed = false ;
bool completed = false ;
while ( ! completed | | ! confirmed )
2017-10-27 10:53:04 +02:00
{
2019-01-06 07:04:30 +01:00
var request = await callbackServer . GetNextRequest ( ) ;
if ( request . ContainsKey ( "event" ) )
{
var evtName = request [ "event" ] [ "name" ] . Value < string > ( ) ;
switch ( evtName )
{
2019-01-06 10:12:45 +01:00
case InvoiceEvent . Created :
2019-01-06 07:04:30 +01:00
tester . ExplorerNode . SendToAddress ( url . Address , url . Amount ) ;
break ;
2019-01-06 10:12:45 +01:00
case InvoiceEvent . ReceivedPayment :
2019-01-06 07:04:30 +01:00
receivedPayment = true ;
break ;
2019-01-06 10:12:45 +01:00
case InvoiceEvent . PaidInFull :
2019-01-06 07:04:30 +01:00
Assert . True ( receivedPayment ) ;
tester . ExplorerNode . Generate ( 6 ) ;
paid = true ;
break ;
2019-01-06 10:12:45 +01:00
case InvoiceEvent . Confirmed :
2019-01-06 07:04:30 +01:00
Assert . True ( paid ) ;
confirmed = true ;
break ;
2019-01-06 10:12:45 +01:00
case InvoiceEvent . Completed :
2020-04-05 13:48:00 +02:00
Assert . True (
paid ) ; //TODO: Fix, out of order event mean we can receive invoice_confirmed after invoice_complete
2019-01-06 07:04:30 +01:00
completed = true ;
break ;
default :
Assert . False ( true , $"{evtName} was not expected" ) ;
break ;
}
}
}
2017-10-27 10:53:04 +02:00
var invoice2 = acc . BitPay . GetInvoice ( invoice . Id ) ;
Assert . NotNull ( invoice2 ) ;
}
}
}
2021-04-18 04:26:06 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2019-10-07 09:04:25 +02:00
public async Task CantPairTwiceWithSamePubkey ( )
2017-10-27 10:53:04 +02:00
{
2021-11-22 09:16:08 +01:00
using ( var tester = CreateServerTester ( ) )
2017-10-27 10:53:04 +02:00
{
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2017-10-27 10:53:04 +02:00
var acc = tester . NewAccount ( ) ;
acc . Register ( ) ;
2018-04-30 15:28:00 +02:00
acc . CreateStore ( ) ;
var store = acc . GetController < StoresController > ( ) ;
2017-10-27 10:53:04 +02:00
var pairingCode = acc . BitPay . RequestClientAuthorization ( "test" , Facade . Merchant ) ;
2020-04-05 13:48:00 +02:00
Assert . IsType < RedirectToActionResult > ( store . Pair ( pairingCode . ToString ( ) , acc . StoreId ) . GetAwaiter ( )
. GetResult ( ) ) ;
2017-10-27 10:53:04 +02:00
pairingCode = acc . BitPay . RequestClientAuthorization ( "test1" , Facade . Merchant ) ;
2018-04-30 15:28:00 +02:00
acc . CreateStore ( ) ;
var store2 = acc . GetController < StoresController > ( ) ;
2019-10-12 13:35:30 +02:00
await store2 . Pair ( pairingCode . ToString ( ) , store2 . CurrentStore . Id ) ;
2020-04-05 13:48:00 +02:00
Assert . Contains ( nameof ( PairingResult . ReusedKey ) ,
( string ) store2 . TempData [ WellKnownTempData . ErrorMessage ] , StringComparison . CurrentCultureIgnoreCase ) ;
2017-10-27 10:53:04 +02:00
}
}
2021-04-18 04:26:06 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
2020-04-08 15:40:41 +02:00
[Trait("Integration", "Integration")]
public async Task CanUseTorClient ( )
{
2021-11-22 09:16:08 +01:00
using ( var tester = CreateServerTester ( ) )
2020-04-08 15:40:41 +02:00
{
await tester . StartAsync ( ) ;
2020-04-09 10:38:55 +02:00
var proxy = tester . PayTester . GetService < Socks5HttpProxyServer > ( ) ;
2020-04-09 11:49:30 +02:00
void AssertConnectionDropped ( )
{
TestUtils . Eventually ( ( ) = >
{
Thread . MemoryBarrier ( ) ;
Assert . Equal ( 0 , proxy . ConnectionCount ) ;
} ) ;
}
2020-04-09 10:38:55 +02:00
var httpFactory = tester . PayTester . GetService < IHttpClientFactory > ( ) ;
2021-03-01 14:44:53 +01:00
var client = httpFactory . CreateClient ( PayjoinServerCommunicator . PayjoinOnionNamedClient ) ;
2020-04-08 15:40:41 +02:00
Assert . NotNull ( client ) ;
var response = await client . GetAsync ( "https://check.torproject.org/" ) ;
response . EnsureSuccessStatusCode ( ) ;
var result = await response . Content . ReadAsStringAsync ( ) ;
Assert . DoesNotContain ( "You are not using Tor." , result ) ;
Assert . Contains ( "Congratulations. This browser is configured to use Tor." , result ) ;
2020-04-09 11:49:30 +02:00
AssertConnectionDropped ( ) ;
2020-04-09 10:38:55 +02:00
response = await client . GetAsync ( "http://explorerzydxu5ecjrkwceayqybizmpjjznk5izmitf2modhcusuqlid.onion/" ) ;
response . EnsureSuccessStatusCode ( ) ;
result = await response . Content . ReadAsStringAsync ( ) ;
Assert . Contains ( "Bitcoin" , result ) ;
2020-04-09 11:49:30 +02:00
AssertConnectionDropped ( ) ;
2020-04-09 10:38:55 +02:00
response = await client . GetAsync ( "http://explorerzydxu5ecjrkwceayqybizmpjjznk5izmitf2modhcusuqlid.onion/" ) ;
response . EnsureSuccessStatusCode ( ) ;
2020-04-09 11:49:30 +02:00
AssertConnectionDropped ( ) ;
2020-04-09 10:38:55 +02:00
client . Dispose ( ) ;
2020-04-09 11:49:30 +02:00
AssertConnectionDropped ( ) ;
2021-03-01 14:44:53 +01:00
client = httpFactory . CreateClient ( PayjoinServerCommunicator . PayjoinOnionNamedClient ) ;
2020-04-09 10:38:55 +02:00
response = await client . GetAsync ( "http://explorerzydxu5ecjrkwceayqybizmpjjznk5izmitf2modhcusuqlid.onion/" ) ;
response . EnsureSuccessStatusCode ( ) ;
2020-04-09 11:49:30 +02:00
AssertConnectionDropped ( ) ;
2021-11-22 09:16:08 +01:00
TestLogs . LogInformation ( "Querying an onion address which can't be found should send http 500" ) ;
2020-04-09 11:49:30 +02:00
response = await client . GetAsync ( "http://dwoduwoi.onion/" ) ;
Assert . Equal ( HttpStatusCode . InternalServerError , response . StatusCode ) ;
AssertConnectionDropped ( ) ;
2021-11-22 09:16:08 +01:00
TestLogs . LogInformation ( "Querying valid onion but unreachable should send error 502" ) ;
2021-11-05 04:30:28 +01:00
using ( CancellationTokenSource cts = new CancellationTokenSource ( TimeSpan . FromSeconds ( 20 ) ) )
{
try
{
response = await client . GetAsync ( "http://nzwsosflsoquxirwb2zikz6uxr3u5n5u73l33umtdx4hq5mzm5dycuqd.onion/" , cts . Token ) ;
Assert . Equal ( HttpStatusCode . BadGateway , response . StatusCode ) ;
AssertConnectionDropped ( ) ;
}
catch when ( cts . Token . IsCancellationRequested )
{
2021-11-22 09:16:08 +01:00
TestLogs . LogInformation ( "Skipping this test, it timed out" ) ;
2021-11-05 04:30:28 +01:00
}
}
2020-04-08 15:40:41 +02:00
}
}
2021-04-18 04:26:06 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
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
{
2021-11-22 09:16:08 +01:00
using ( var tester = CreateServerTester ( ) )
2018-10-26 16:07:39 +02:00
{
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2018-10-26 16:07:39 +02:00
var acc = tester . NewAccount ( ) ;
acc . GrantAccess ( ) ;
2020-04-07 12:32:30 +02:00
acc . RegisterDerivationScheme ( "BTC" , ScriptPubKeyType . Segwit ) ;
2018-10-26 16:07:39 +02:00
var btcDerivationScheme = acc . DerivationScheme ;
2018-11-02 06:26:13 +01:00
2019-10-12 13:35:30 +02:00
var walletController = acc . GetController < WalletsController > ( ) ;
2018-10-26 16:07:39 +02:00
2019-12-24 10:11:21 +01:00
var walletId = new WalletId ( acc . StoreId , "BTC" ) ;
2019-10-12 13:35:30 +02:00
acc . IsAdmin = true ;
walletController = acc . GetController < WalletsController > ( ) ;
2020-02-13 06:44:31 +01:00
2020-04-05 13:48:00 +02:00
var rescan =
Assert . IsType < RescanWalletModel > ( Assert
. IsType < ViewResult > ( walletController . WalletRescan ( walletId ) . Result ) . Model ) ;
2018-10-26 16:07:39 +02:00
Assert . True ( rescan . Ok ) ;
Assert . True ( rescan . IsFullySync ) ;
Assert . True ( rescan . IsSupportedByCurrency ) ;
Assert . True ( rescan . IsServerAdmin ) ;
rescan . GapLimit = 100 ;
// Sending a coin
2020-04-05 13:48:00 +02:00
var txId = tester . ExplorerNode . SendToAddress (
btcDerivationScheme . GetDerivation ( new KeyPath ( "0/90" ) ) . ScriptPubKey , Money . Coins ( 1.0 m ) ) ;
2018-10-26 16:07:39 +02:00
tester . ExplorerNode . Generate ( 1 ) ;
2020-04-05 13:48:00 +02:00
var transactions = Assert . IsType < ListTransactionsViewModel > ( Assert
. IsType < ViewResult > ( walletController . WalletTransactions ( walletId ) . Result ) . Model ) ;
2018-10-26 16:07:39 +02:00
Assert . Empty ( transactions . Transactions ) ;
Assert . IsType < RedirectToActionResult > ( walletController . WalletRescan ( walletId , rescan ) . Result ) ;
2018-11-02 06:26:13 +01:00
while ( true )
2018-10-26 16:07:39 +02:00
{
2020-04-05 13:48:00 +02:00
rescan = Assert . IsType < RescanWalletModel > ( Assert
. IsType < ViewResult > ( walletController . WalletRescan ( walletId ) . Result ) . Model ) ;
2018-11-02 06:26:13 +01:00
if ( rescan . Progress = = null & & rescan . LastSuccess ! = null )
2018-10-26 16:07:39 +02:00
{
if ( rescan . LastSuccess . Found = = 0 )
continue ;
// Scan over
break ;
}
else
{
Assert . Null ( rescan . TimeOfScan ) ;
Assert . NotNull ( rescan . RemainingTime ) ;
Assert . NotNull ( rescan . Progress ) ;
Thread . Sleep ( 100 ) ;
}
}
2020-04-05 13:48:00 +02:00
2018-10-26 16:07:39 +02:00
Assert . Null ( rescan . PreviousError ) ;
Assert . NotNull ( rescan . TimeOfScan ) ;
Assert . Equal ( 1 , rescan . LastSuccess . Found ) ;
2020-04-05 13:48:00 +02:00
transactions = Assert . IsType < ListTransactionsViewModel > ( Assert
. IsType < ViewResult > ( walletController . WalletTransactions ( walletId ) . Result ) . Model ) ;
2018-10-26 16:07:39 +02:00
var tx = Assert . Single ( transactions . Transactions ) ;
Assert . Equal ( tx . Id , txId . ToString ( ) ) ;
2019-08-02 17:42:30 +02:00
// Hijack the test to see if we can add label and comments
2020-04-05 13:48:00 +02:00
Assert . IsType < RedirectToActionResult > (
await walletController . ModifyTransaction ( walletId , tx . Id , addcomment : "hello-pouet" ) ) ;
Assert . IsType < RedirectToActionResult > (
await walletController . ModifyTransaction ( walletId , tx . Id , addlabel : "test" ) ) ;
Assert . IsType < RedirectToActionResult > (
await walletController . ModifyTransaction ( walletId , tx . Id , addlabelclick : "test2" ) ) ;
Assert . IsType < RedirectToActionResult > (
await walletController . ModifyTransaction ( walletId , tx . Id , addcomment : "hello" ) ) ;
transactions = Assert . IsType < ListTransactionsViewModel > ( Assert
. IsType < ViewResult > ( walletController . WalletTransactions ( walletId ) . Result ) . Model ) ;
2019-08-02 17:42:30 +02:00
tx = Assert . Single ( transactions . Transactions ) ;
Assert . Equal ( "hello" , tx . Comment ) ;
2020-12-12 06:10:47 +01:00
Assert . Contains ( "test" , tx . Labels . Select ( l = > l . Text ) ) ;
Assert . Contains ( "test2" , tx . Labels . Select ( l = > l . Text ) ) ;
2019-08-02 17:42:30 +02:00
Assert . Equal ( 2 , tx . Labels . GroupBy ( l = > l . Color ) . Count ( ) ) ;
2020-04-05 13:48:00 +02:00
Assert . IsType < RedirectToActionResult > (
await walletController . ModifyTransaction ( walletId , tx . Id , removelabel : "test2" ) ) ;
2019-08-02 17:42:30 +02:00
2020-04-05 13:48:00 +02:00
transactions = Assert . IsType < ListTransactionsViewModel > ( Assert
. IsType < ViewResult > ( walletController . WalletTransactions ( walletId ) . Result ) . Model ) ;
2019-08-02 17:42:30 +02:00
tx = Assert . Single ( transactions . Transactions ) ;
Assert . Equal ( "hello" , tx . Comment ) ;
2020-12-12 06:10:47 +01:00
Assert . Contains ( "test" , tx . Labels . Select ( l = > l . Text ) ) ;
Assert . DoesNotContain ( "test2" , tx . Labels . Select ( l = > l . Text ) ) ;
2019-08-02 17:42:30 +02:00
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
}
}
2021-04-18 04:26:06 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2019-10-07 09:04:25 +02:00
public async Task CanListInvoices ( )
2018-05-06 06:16:39 +02:00
{
2021-11-22 09:16:08 +01:00
using ( var tester = CreateServerTester ( ) )
2018-05-06 06:16:39 +02:00
{
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2018-05-06 06:16:39 +02:00
var acc = tester . NewAccount ( ) ;
acc . GrantAccess ( ) ;
acc . RegisterDerivationScheme ( "BTC" ) ;
// First we try payment with a merchant having only BTC
2020-04-05 13:48:00 +02:00
var invoice = acc . BitPay . CreateInvoice (
new Invoice ( )
{
Price = 500 ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
2018-05-13 08:09:17 +02:00
2018-05-06 06:16:39 +02:00
var cashCow = tester . ExplorerNode ;
var invoiceAddress = BitcoinAddress . Create ( invoice . CryptoInfo [ 0 ] . Address , cashCow . Network ) ;
var firstPayment = invoice . CryptoInfo [ 0 ] . TotalDue - Money . Satoshis ( 10 ) ;
cashCow . SendToAddress ( invoiceAddress , firstPayment ) ;
2019-01-05 19:47:39 +01:00
TestUtils . Eventually ( ( ) = >
2018-05-06 06:16:39 +02:00
{
invoice = acc . BitPay . GetInvoice ( invoice . Id ) ;
Assert . Equal ( firstPayment , invoice . CryptoInfo [ 0 ] . Paid ) ;
} ) ;
AssertSearchInvoice ( acc , true , invoice . Id , $"storeid:{acc.StoreId}" ) ;
AssertSearchInvoice ( acc , false , invoice . Id , $"storeid:blah" ) ;
AssertSearchInvoice ( acc , true , invoice . Id , $"{invoice.Id}" ) ;
AssertSearchInvoice ( acc , true , invoice . Id , $"exceptionstatus:paidPartial" ) ;
AssertSearchInvoice ( acc , false , invoice . Id , $"exceptionstatus:paidOver" ) ;
AssertSearchInvoice ( acc , true , invoice . Id , $"unusual:true" ) ;
AssertSearchInvoice ( acc , false , invoice . Id , $"unusual:false" ) ;
2019-04-26 01:13:17 +02:00
var time = invoice . InvoiceTime ;
AssertSearchInvoice ( acc , true , invoice . Id , $"startdate:{time.ToString(" yyyy - MM - dd HH : mm : ss ")}" ) ;
AssertSearchInvoice ( acc , true , invoice . Id , $"enddate:{time.ToStringLowerInvariant()}" ) ;
2020-04-05 13:48:00 +02:00
AssertSearchInvoice ( acc , false , invoice . Id ,
$"startdate:{time.AddSeconds(1).ToString(" yyyy - MM - dd HH : mm : ss ")}" ) ;
AssertSearchInvoice ( acc , false , invoice . Id ,
$"enddate:{time.AddSeconds(-1).ToString(" yyyy - MM - dd HH : mm : ss ")}" ) ;
2018-05-06 06:16:39 +02:00
}
}
2021-04-18 04:26:06 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
2020-06-15 07:53:12 +02:00
[Trait("Integration", "Integration")]
public async Task CanListNotifications ( )
{
2021-11-22 09:16:08 +01:00
using ( var tester = CreateServerTester ( ) )
2020-06-15 07:53:12 +02:00
{
await tester . StartAsync ( ) ;
var acc = tester . NewAccount ( ) ;
acc . GrantAccess ( true ) ;
acc . RegisterDerivationScheme ( "BTC" ) ;
const string newVersion = "1.0.4.4" ;
var ctrl = acc . GetController < NotificationsController > ( ) ;
var resp = await ctrl . Generate ( newVersion ) ;
var vm = Assert . IsType < Models . NotificationViewModels . IndexViewModel > (
2020-12-11 15:11:08 +01:00
Assert . IsType < ViewResult > ( await ctrl . Index ( ) ) . Model ) ;
2020-06-15 07:53:12 +02:00
Assert . True ( vm . Skip = = 0 ) ;
Assert . True ( vm . Count = = 50 ) ;
Assert . True ( vm . Total = = 1 ) ;
Assert . True ( vm . Items . Count = = 1 ) ;
var fn = vm . Items . First ( ) ;
2020-06-15 07:57:20 +02:00
var now = DateTimeOffset . UtcNow ;
Assert . True ( fn . Created > = now . AddSeconds ( - 3 ) ) ;
Assert . True ( fn . Created < = now ) ;
2020-06-15 07:53:12 +02:00
Assert . Equal ( $"New version {newVersion} released!" , fn . Body ) ;
Assert . Equal ( $"https://github.com/btcpayserver/btcpayserver/releases/tag/v{newVersion}" , fn . ActionLink ) ;
2020-06-15 07:57:20 +02:00
Assert . False ( fn . Seen ) ;
2020-06-15 07:53:12 +02:00
}
}
2018-05-29 17:12:07 +02:00
[Fact]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2019-10-07 09:04:25 +02:00
public async Task CanGetRates ( )
2018-05-29 17:12:07 +02:00
{
2021-11-22 09:16:08 +01:00
using ( var tester = CreateServerTester ( ) )
2018-05-29 17:12:07 +02:00
{
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2018-05-29 17:12:07 +02:00
var acc = tester . NewAccount ( ) ;
acc . GrantAccess ( ) ;
acc . RegisterDerivationScheme ( "BTC" ) ;
var rateController = acc . GetController < RateController > ( ) ;
2020-04-05 13:48:00 +02:00
var GetBaseCurrencyRatesResult = JObject . Parse ( ( ( JsonResult ) rateController
. GetBaseCurrencyRates ( "BTC" , default )
2018-07-30 16:22:26 +02:00
. GetAwaiter ( ) . GetResult ( ) ) . Value . ToJson ( ) ) . ToObject < DataWrapper < Rate [ ] > > ( ) ;
2018-05-29 17:12:07 +02:00
Assert . NotNull ( GetBaseCurrencyRatesResult ) ;
Assert . NotNull ( GetBaseCurrencyRatesResult . Data ) ;
2019-12-24 10:11:21 +01:00
var rate = Assert . Single ( GetBaseCurrencyRatesResult . Data ) ;
Assert . Equal ( "BTC" , rate . Code ) ;
2018-05-29 17:12:07 +02:00
2019-10-12 13:35:30 +02:00
var GetRatesResult = JObject . Parse ( ( ( JsonResult ) rateController . GetRates ( null , default )
2018-07-30 16:22:26 +02:00
. GetAwaiter ( ) . GetResult ( ) ) . Value . ToJson ( ) ) . ToObject < DataWrapper < Rate [ ] > > ( ) ;
2019-03-11 10:39:21 +01:00
// We don't have any default currencies, so this should be failing
Assert . Null ( GetRatesResult ? . Data ) ;
var store = acc . GetController < StoresController > ( ) ;
2020-01-13 14:20:45 +01:00
var ratesVM = ( RatesViewModel ) ( Assert . IsType < ViewResult > ( store . Rates ( ) ) . Model ) ;
2019-03-11 10:39:21 +01:00
ratesVM . DefaultCurrencyPairs = "BTC_USD,LTC_USD" ;
2020-01-13 14:20:45 +01:00
await store . Rates ( ratesVM ) ;
2019-03-11 10:39:21 +01:00
store = acc . GetController < StoresController > ( ) ;
rateController = acc . GetController < RateController > ( ) ;
2019-10-12 13:35:30 +02:00
GetRatesResult = JObject . Parse ( ( ( JsonResult ) rateController . GetRates ( null , default )
2019-03-11 10:39:21 +01:00
. GetAwaiter ( ) . GetResult ( ) ) . Value . ToJson ( ) ) . ToObject < DataWrapper < Rate [ ] > > ( ) ;
// Now we should have a result
2018-05-29 17:12:07 +02:00
Assert . NotNull ( GetRatesResult ) ;
Assert . NotNull ( GetRatesResult . Data ) ;
Assert . Equal ( 2 , GetRatesResult . Data . Length ) ;
2020-04-05 13:48:00 +02:00
var GetCurrencyPairRateResult = JObject . Parse ( ( ( JsonResult ) rateController
. GetCurrencyPairRate ( "BTC" , "LTC" , default )
2018-07-30 16:22:26 +02:00
. GetAwaiter ( ) . GetResult ( ) ) . Value . ToJson ( ) ) . ToObject < DataWrapper < Rate > > ( ) ;
2018-05-29 17:12:07 +02:00
Assert . NotNull ( GetCurrencyPairRateResult ) ;
Assert . NotNull ( GetCurrencyPairRateResult . Data ) ;
Assert . Equal ( "LTC" , GetCurrencyPairRateResult . Data . Code ) ;
2018-10-15 11:11:20 +02:00
// Should be OK because the request is signed, so we can know the store
var rates = acc . BitPay . GetRates ( ) ;
HttpClient client = new HttpClient ( ) ;
// Unauthentified requests should also be ok
2020-04-05 13:48:00 +02:00
var response =
await client . GetAsync ( $"http://127.0.0.1:{tester.PayTester.Port}/api/rates?storeId={acc.StoreId}" ) ;
2019-10-12 13:35:30 +02:00
response . EnsureSuccessStatusCode ( ) ;
2020-04-05 13:48:00 +02:00
response = await client . GetAsync (
$"http://127.0.0.1:{tester.PayTester.Port}/rates?storeId={acc.StoreId}" ) ;
2018-10-15 11:11:20 +02:00
response . EnsureSuccessStatusCode ( ) ;
2018-05-29 17:12:07 +02:00
}
}
2018-05-06 06:16:39 +02:00
private void AssertSearchInvoice ( TestAccount acc , bool expected , string invoiceId , string filter )
{
2020-04-05 13:48:00 +02:00
var result =
2021-12-11 04:32:23 +01:00
( InvoicesModel ) ( ( ViewResult ) acc . GetController < InvoiceController > ( )
2020-07-30 16:10:10 +02:00
. ListInvoices ( new InvoicesModel { SearchTerm = filter } ) . Result ) . Model ;
2018-05-06 06:16:39 +02:00
Assert . Equal ( expected , result . Invoices . Any ( i = > i . InvoiceId = = invoiceId ) ) ;
}
2020-04-05 13:48:00 +02:00
// [Fact(Timeout = TestTimeout)]
[Fact()]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2019-10-07 09:04:25 +02:00
public async Task CanRBFPayment ( )
2017-11-06 09:31:02 +01:00
{
2021-11-22 09:16:08 +01:00
using ( var tester = CreateServerTester ( ) )
2017-11-06 09:31:02 +01:00
{
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2017-11-06 09:31:02 +01:00
var user = tester . NewAccount ( ) ;
2021-10-29 08:25:43 +02:00
await user . GrantAccessAsync ( ) ;
2018-02-23 07:21:42 +01:00
user . RegisterDerivationScheme ( "BTC" ) ;
2021-06-18 03:25:17 +02:00
await user . SetNetworkFeeMode ( NetworkFeeMode . Always ) ;
2020-04-05 13:48:00 +02:00
var invoice =
2020-06-24 03:34:09 +02:00
user . BitPay . CreateInvoice ( new Invoice ( ) { Price = 5000.0 m , Currency = "USD" } , Facade . Merchant ) ;
2018-02-19 07:09:05 +01:00
var payment1 = invoice . BtcDue + Money . Coins ( 0.0001 m ) ;
var payment2 = invoice . BtcDue ;
2018-10-28 15:43:48 +01:00
2017-11-06 09:31:02 +01:00
var tx1 = new uint256 ( tester . ExplorerNode . SendCommand ( "sendtoaddress" , new object [ ]
{
2020-04-05 13:48:00 +02:00
invoice . BitcoinAddress , payment1 . ToString ( ) , null , //comment
2017-11-06 09:31:02 +01:00
null , //comment_to
false , //subtractfeefromamount
true , //replaceable
} ) . ResultString ) ;
2021-11-22 09:16:08 +01:00
TestLogs . LogInformation (
2020-04-05 13:48:00 +02:00
$"Let's send a first payment of {payment1} for the {invoice.BtcDue} invoice ({tx1})" ) ;
var invoiceAddress =
BitcoinAddress . Create ( invoice . BitcoinAddress , user . SupportedNetwork . NBitcoinNetwork ) ;
2017-11-06 09:31:02 +01:00
2021-11-22 09:16:08 +01:00
TestLogs . LogInformation ( $"The invoice should be paidOver" ) ;
2019-01-05 19:47:39 +01:00
TestUtils . Eventually ( ( ) = >
2017-11-06 09:31:02 +01:00
{
invoice = user . BitPay . GetInvoice ( invoice . Id ) ;
Assert . Equal ( payment1 , invoice . BtcPaid ) ;
2018-02-19 07:09:05 +01:00
Assert . Equal ( "paid" , invoice . Status ) ;
Assert . Equal ( "paidOver" , invoice . ExceptionStatus . ToString ( ) ) ;
2020-04-05 13:48:00 +02:00
invoiceAddress =
BitcoinAddress . Create ( invoice . BitcoinAddress , user . SupportedNetwork . NBitcoinNetwork ) ;
2017-11-06 09:31:02 +01:00
} ) ;
var tx = tester . ExplorerNode . GetRawTransaction ( new uint256 ( tx1 ) ) ;
foreach ( var input in tx . Inputs )
{
input . ScriptSig = Script . Empty ; //Strip signatures
}
2020-04-05 13:48:00 +02:00
2017-11-06 09:31:02 +01:00
var output = tx . Outputs . First ( o = > o . Value = = payment1 ) ;
output . Value = payment2 ;
output . ScriptPubKey = invoiceAddress . ScriptPubKey ;
2018-10-28 15:43:48 +01:00
2018-11-02 06:26:13 +01:00
using ( var cts = new CancellationTokenSource ( 10000 ) )
2018-11-21 12:41:51 +01:00
using ( var listener = tester . ExplorerClient . CreateWebsocketNotificationSession ( ) )
2018-10-28 15:43:48 +01:00
{
listener . ListenAllDerivationSchemes ( ) ;
var replaced = tester . ExplorerNode . SignRawTransaction ( tx ) ;
2018-10-31 08:57:31 +01:00
Thread . Sleep ( 1000 ) ; // Make sure the replacement has a different timestamp
2018-10-28 15:43:48 +01:00
var tx2 = tester . ExplorerNode . SendRawTransaction ( replaced ) ;
2021-11-22 09:16:08 +01:00
TestLogs . LogInformation (
2020-04-05 13:48:00 +02:00
$"Let's RBF with a payment of {payment2} ({tx2}), waiting for NBXplorer to pick it up" ) ;
Assert . Equal ( tx2 ,
( ( NewTransactionEvent ) listener . NextEvent ( cts . Token ) ) . TransactionData . TransactionHash ) ;
2018-10-28 15:43:48 +01:00
}
2020-04-05 13:48:00 +02:00
2021-11-22 09:16:08 +01:00
TestLogs . LogInformation ( $"The invoice should now not be paidOver anymore" ) ;
2019-01-05 19:47:39 +01:00
TestUtils . Eventually ( ( ) = >
2017-11-06 09:31:02 +01:00
{
invoice = user . BitPay . GetInvoice ( invoice . Id ) ;
Assert . Equal ( payment2 , invoice . BtcPaid ) ;
2018-02-19 07:09:05 +01:00
Assert . Equal ( "False" , invoice . ExceptionStatus . ToString ( ) ) ;
2017-11-06 09:31:02 +01:00
} ) ;
2020-03-11 12:46:37 +01:00
2021-11-22 09:16:08 +01:00
TestLogs . LogInformation (
2020-04-05 13:48:00 +02:00
$"Let's test out rbf payments where the payment gets sent elsehwere instead" ) ;
var invoice2 =
2020-06-24 03:34:09 +02:00
user . BitPay . CreateInvoice ( new Invoice ( ) { Price = 0.01 m , Currency = "BTC" } , Facade . Merchant ) ;
2020-03-11 12:46:37 +01:00
2020-04-05 13:48:00 +02:00
var invoice2Address =
BitcoinAddress . Create ( invoice2 . BitcoinAddress , user . SupportedNetwork . NBitcoinNetwork ) ;
uint256 invoice2tx1Id =
2021-07-29 13:29:34 +02:00
await tester . ExplorerNode . SendToAddressAsync ( invoice2Address , invoice2 . BtcDue , new NBitcoin . RPC . SendToAddressParameters ( )
{
Replaceable = true
} ) ;
2020-03-11 12:46:37 +01:00
Transaction invoice2Tx1 = null ;
TestUtils . Eventually ( ( ) = >
{
invoice2 = user . BitPay . GetInvoice ( invoice2 . Id ) ;
Assert . Equal ( "paid" , invoice2 . Status ) ;
invoice2Tx1 = tester . ExplorerNode . GetRawTransaction ( new uint256 ( invoice2tx1Id ) ) ;
} ) ;
var invoice2Tx2 = invoice2Tx1 . Clone ( ) ;
foreach ( var input in invoice2Tx2 . Inputs )
{
input . ScriptSig = Script . Empty ; //Strip signatures
input . WitScript = WitScript . Empty ; //Strip signatures
}
output = invoice2Tx2 . Outputs . First ( o = >
o . ScriptPubKey = = invoice2Address . ScriptPubKey ) ;
output . Value - = new Money ( 10_000 , MoneyUnit . Satoshi ) ;
2021-07-29 13:29:34 +02:00
output . ScriptPubKey = new Key ( ) . GetScriptPubKey ( ScriptPubKeyType . Legacy ) ;
2020-03-11 12:46:37 +01:00
invoice2Tx2 = await tester . ExplorerNode . SignRawTransactionAsync ( invoice2Tx2 ) ;
await tester . ExplorerNode . SendRawTransactionAsync ( invoice2Tx2 ) ;
tester . ExplorerNode . Generate ( 1 ) ;
await TestUtils . EventuallyAsync ( async ( ) = >
{
var i = await tester . PayTester . InvoiceRepository . GetInvoice ( invoice2 . Id ) ;
2020-11-23 07:57:05 +01:00
Assert . Equal ( InvoiceStatusLegacy . New , i . Status ) ;
2021-05-14 09:16:19 +02:00
Assert . Single ( i . GetPayments ( false ) ) ;
Assert . False ( i . GetPayments ( false ) . First ( ) . Accounted ) ;
2020-03-11 12:46:37 +01:00
} ) ;
2020-04-05 13:48:00 +02:00
2021-11-22 09:16:08 +01:00
TestLogs . LogInformation ( "Let's test if we can RBF a normal payment without adding fees to the invoice" ) ;
2021-06-18 03:25:17 +02:00
await user . SetNetworkFeeMode ( NetworkFeeMode . MultiplePaymentsOnly ) ;
2021-10-29 08:25:43 +02:00
invoice = user . BitPay . CreateInvoice ( new Invoice { Price = 5000.0 m , Currency = "USD" } , Facade . Merchant ) ;
2020-04-05 13:48:00 +02:00
payment1 = invoice . BtcDue ;
tx1 = new uint256 ( tester . ExplorerNode . SendCommand ( "sendtoaddress" , new object [ ]
{
invoice . BitcoinAddress , payment1 . ToString ( ) , null , //comment
null , //comment_to
false , //subtractfeefromamount
true , //replaceable
} ) . ResultString ) ;
2021-11-22 09:16:08 +01:00
TestLogs . LogInformation ( $"Paid {tx1}" ) ;
2020-04-05 13:48:00 +02:00
TestUtils . Eventually ( ( ) = >
{
invoice = user . BitPay . GetInvoice ( invoice . Id ) ;
Assert . Equal ( payment1 , invoice . BtcPaid ) ;
Assert . Equal ( "paid" , invoice . Status ) ;
Assert . Equal ( "False" , invoice . ExceptionStatus . ToString ( ) ) ;
}
) ;
var tx1Bump = new uint256 ( tester . ExplorerNode . SendCommand ( "bumpfee" , new object [ ]
{
tx1 . ToString ( ) ,
} ) . Result [ "txid" ] . Value < string > ( ) ) ;
2021-11-22 09:16:08 +01:00
TestLogs . LogInformation ( $"Bumped with {tx1Bump}" ) ;
2020-04-05 13:48:00 +02:00
await TestUtils . EventuallyAsync ( async ( ) = >
{
var invoiceEntity = await tester . PayTester . InvoiceRepository . GetInvoice ( invoice . Id ) ;
2021-05-14 09:16:19 +02:00
var btcPayments = invoiceEntity . GetAllBitcoinPaymentData ( false ) . ToArray ( ) ;
var payments = invoiceEntity . GetPayments ( false ) . ToArray ( ) ;
2020-04-05 13:48:00 +02:00
Assert . Equal ( tx1 , btcPayments [ 0 ] . Outpoint . Hash ) ;
Assert . False ( payments [ 0 ] . Accounted ) ;
Assert . Equal ( tx1Bump , payments [ 1 ] . Outpoint . Hash ) ;
Assert . True ( payments [ 1 ] . Accounted ) ;
Assert . Equal ( 0.0 m , payments [ 1 ] . NetworkFee ) ;
invoice = user . BitPay . GetInvoice ( invoice . Id ) ;
Assert . Equal ( payment1 , invoice . BtcPaid ) ;
Assert . Equal ( "paid" , invoice . Status ) ;
Assert . Equal ( "False" , invoice . ExceptionStatus . ToString ( ) ) ;
}
) ;
2017-11-06 09:31:02 +01:00
}
}
2020-11-09 08:48:45 +01:00
2020-09-16 11:49:47 +02:00
// [Fact(Timeout = TestTimeout)]
[Fact()]
[Trait("Integration", "Integration")]
public async Task CanSaveKeyPathForOnChainPayments ( )
{
2021-11-22 09:16:08 +01:00
using var tester = CreateServerTester ( ) ;
2020-09-16 11:49:47 +02:00
await tester . StartAsync ( ) ;
var user = tester . NewAccount ( ) ;
await user . GrantAccessAsync ( ) ;
await user . RegisterDerivationSchemeAsync ( "BTC" ) ;
var invoice = await user . BitPay . CreateInvoiceAsync ( new Invoice ( 0.01 m , "BTC" ) ) ;
await tester . WaitForEvent < InvoiceEvent > ( async ( ) = >
{
var tx = await tester . ExplorerNode . SendToAddressAsync (
BitcoinAddress . Create ( invoice . BitcoinAddress , Network . RegTest ) ,
Money . Coins ( 0.01 m ) ) ;
} ) ;
2020-11-09 08:48:45 +01:00
2020-09-16 11:49:47 +02:00
var payments = Assert . IsType < InvoiceDetailsModel > (
Assert . IsType < ViewResult > ( await user . GetController < InvoiceController > ( ) . Invoice ( invoice . Id ) ) . Model )
. Payments ;
Assert . Single ( payments ) ;
var paymentData = payments . First ( ) . GetCryptoPaymentData ( ) as BitcoinLikePaymentData ;
Assert . NotNull ( paymentData . KeyPath ) ;
}
2017-11-06 09:31:02 +01:00
2021-04-18 04:26:06 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
2019-02-02 08:12:51 +01:00
[Trait("Integration", "Integration")]
public async void CheckCORSSetOnBitpayAPI ( )
{
2021-11-22 09:16:08 +01:00
using ( var tester = CreateServerTester ( ) )
2019-02-02 08:12:51 +01:00
{
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2020-06-24 03:34:09 +02:00
foreach ( var req in new [ ] { "invoices/" , "invoices" , "rates" , "tokens" } . Select ( async path = >
{
using ( HttpClient client = new HttpClient ( ) )
{
HttpRequestMessage message = new HttpRequestMessage ( HttpMethod . Options ,
tester . PayTester . ServerUri . AbsoluteUri + path ) ;
message . Headers . Add ( "Access-Control-Request-Headers" , "test" ) ;
var response = await client . SendAsync ( message ) ;
response . EnsureSuccessStatusCode ( ) ;
Assert . True ( response . Headers . TryGetValues ( "Access-Control-Allow-Origin" , out var val ) ) ;
Assert . Equal ( "*" , val . FirstOrDefault ( ) ) ;
Assert . True ( response . Headers . TryGetValues ( "Access-Control-Allow-Headers" , out val ) ) ;
Assert . Equal ( "test" , val . FirstOrDefault ( ) ) ;
}
} ) . ToList ( ) )
2019-02-02 08:12:51 +01:00
{
await req ;
}
2020-04-05 13:48:00 +02:00
2019-02-02 08:12:51 +01:00
HttpClient client2 = new HttpClient ( ) ;
2020-04-05 13:48:00 +02:00
HttpRequestMessage message2 = new HttpRequestMessage ( HttpMethod . Options ,
tester . PayTester . ServerUri . AbsoluteUri + "rates" ) ;
2019-02-02 08:12:51 +01:00
var response2 = await client2 . SendAsync ( message2 ) ;
Assert . True ( response2 . Headers . TryGetValues ( "Access-Control-Allow-Origin" , out var val2 ) ) ;
Assert . Equal ( "*" , val2 . FirstOrDefault ( ) ) ;
}
}
2021-04-18 04:26:06 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2019-10-07 09:04:25 +02:00
public async Task TestAccessBitpayAPI ( )
2017-10-27 10:53:04 +02:00
{
2021-11-22 09:16:08 +01:00
using ( var tester = CreateServerTester ( ) )
2017-10-27 10:53:04 +02:00
{
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2017-10-27 10:53:04 +02:00
var user = tester . NewAccount ( ) ;
Assert . False ( user . BitPay . TestAccess ( Facade . Merchant ) ) ;
user . GrantAccess ( ) ;
2018-02-23 07:21:42 +01:00
user . RegisterDerivationScheme ( "BTC" ) ;
2018-06-06 09:02:37 +02:00
2017-10-27 10:53:04 +02:00
Assert . True ( user . BitPay . TestAccess ( Facade . Merchant ) ) ;
2018-04-29 11:28:04 +02:00
2018-06-06 07:46:41 +02:00
// Test request pairing code client side
var storeController = user . GetController < StoresController > ( ) ;
2020-04-05 13:48:00 +02:00
storeController
2020-06-24 03:34:09 +02:00
. CreateToken ( user . StoreId , new CreateTokenViewModel ( ) { Label = "test2" , StoreId = user . StoreId } )
2020-04-05 13:48:00 +02:00
. GetAwaiter ( ) . GetResult ( ) ;
2018-06-06 07:46:41 +02:00
Assert . NotNull ( storeController . GeneratedPairingCode ) ;
2018-06-06 09:02:37 +02:00
var k = new Key ( ) ;
var bitpay = new Bitpay ( k , tester . PayTester . ServerUri ) ;
2018-06-06 07:46:41 +02:00
bitpay . AuthorizeClient ( new PairingCode ( storeController . GeneratedPairingCode ) ) . Wait ( ) ;
Assert . True ( bitpay . TestAccess ( Facade . Merchant ) ) ;
Assert . True ( bitpay . TestAccess ( Facade . PointOfSale ) ) ;
2018-06-06 09:02:37 +02:00
// Same with new instance
bitpay = new Bitpay ( k , tester . PayTester . ServerUri ) ;
Assert . True ( bitpay . TestAccess ( Facade . Merchant ) ) ;
Assert . True ( bitpay . TestAccess ( Facade . PointOfSale ) ) ;
2018-06-06 07:46:41 +02:00
2018-04-29 11:28:04 +02:00
// Can generate API Key
var repo = tester . PayTester . GetService < TokenRepository > ( ) ;
Assert . Empty ( repo . GetLegacyAPIKeys ( user . StoreId ) . GetAwaiter ( ) . GetResult ( ) ) ;
2020-04-05 13:48:00 +02:00
Assert . IsType < RedirectToActionResult > ( user . GetController < StoresController > ( )
. GenerateAPIKey ( user . StoreId ) . GetAwaiter ( ) . GetResult ( ) ) ;
2018-04-29 11:28:04 +02:00
var apiKey = Assert . Single ( repo . GetLegacyAPIKeys ( user . StoreId ) . GetAwaiter ( ) . GetResult ( ) ) ;
///////
// Generating a new one remove the previous
2020-04-05 13:48:00 +02:00
Assert . IsType < RedirectToActionResult > ( user . GetController < StoresController > ( )
. GenerateAPIKey ( user . StoreId ) . GetAwaiter ( ) . GetResult ( ) ) ;
2018-04-29 11:28:04 +02:00
var apiKey2 = Assert . Single ( repo . GetLegacyAPIKeys ( user . StoreId ) . GetAwaiter ( ) . GetResult ( ) ) ;
Assert . NotEqual ( apiKey , apiKey2 ) ;
////////
apiKey = apiKey2 ;
// Can create an invoice with this new API Key
HttpClient client = new HttpClient ( ) ;
2020-04-05 13:48:00 +02:00
HttpRequestMessage message = new HttpRequestMessage ( HttpMethod . Post ,
tester . PayTester . ServerUri . AbsoluteUri + "invoices" ) ;
message . Headers . Authorization = new System . Net . Http . Headers . AuthenticationHeaderValue ( "Basic" ,
Encoders . Base64 . EncodeData ( Encoders . ASCII . DecodeData ( apiKey ) ) ) ;
2020-06-24 03:34:09 +02:00
var invoice = new Invoice ( ) { Price = 5000.0 m , Currency = "USD" } ;
2020-04-05 13:48:00 +02:00
message . Content = new StringContent ( JsonConvert . SerializeObject ( invoice ) , Encoding . UTF8 ,
"application/json" ) ;
2018-04-29 11:28:04 +02:00
var result = client . SendAsync ( message ) . GetAwaiter ( ) . GetResult ( ) ;
result . EnsureSuccessStatusCode ( ) ;
/////////////////////
2018-12-18 16:28:06 +01:00
2018-12-07 06:34:07 +01:00
// Have error 403 with bad signature
client = new HttpClient ( ) ;
2020-04-05 13:48:00 +02:00
HttpRequestMessage mess =
new HttpRequestMessage ( HttpMethod . Get , tester . PayTester . ServerUri . AbsoluteUri + "tokens" ) ;
2018-12-07 06:34:07 +01:00
mess . Content = new StringContent ( string . Empty , Encoding . UTF8 , "application/json" ) ;
2020-04-05 13:48:00 +02:00
mess . Headers . Add ( "x-signature" ,
"3045022100caa123193afc22ef93d9c6b358debce6897c09dd9869fe6fe029c9cb43623fac022000b90c65c50ba8bbbc6ebee8878abe5659e17b9f2e1b27d95eda4423da5608fe" ) ;
mess . Headers . Add ( "x-identity" ,
"04b4d82095947262dd70f94c0a0e005ec3916e3f5f2181c176b8b22a52db22a8c436c4703f43a9e8884104854a11e1eb30df8fdf116e283807a1f1b8fe4c182b99" ) ;
2018-12-07 06:34:07 +01:00
mess . Method = HttpMethod . Get ;
result = client . SendAsync ( mess ) . GetAwaiter ( ) . GetResult ( ) ;
Assert . Equal ( System . Net . HttpStatusCode . Unauthorized , result . StatusCode ) ;
//
2018-01-07 18:36:41 +01:00
}
}
2018-01-13 14:01:09 +01:00
2021-04-18 04:26:06 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
2019-03-25 04:59:42 +01:00
[Trait("Integration", "Integration")]
public async Task CanUseAnyoneCanCreateInvoice ( )
{
2021-11-22 09:16:08 +01:00
using ( var tester = CreateServerTester ( ) )
2019-03-25 04:59:42 +01:00
{
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2019-03-25 04:59:42 +01:00
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
2021-11-22 09:16:08 +01:00
TestLogs . LogInformation ( "StoreId without anyone can create invoice = 403" ) ;
2020-04-05 13:48:00 +02:00
var response = await tester . PayTester . HttpClient . SendAsync (
new HttpRequestMessage ( HttpMethod . Post , $"invoices?storeId={user.StoreId}" )
{
Content = new StringContent ( "{\"Price\": 5000, \"currency\": \"USD\"}" , Encoding . UTF8 ,
"application/json" ) ,
} ) ;
2019-10-12 13:35:30 +02:00
Assert . Equal ( 403 , ( int ) response . StatusCode ) ;
2019-03-25 04:59:42 +01:00
2021-11-22 09:16:08 +01:00
TestLogs . LogInformation (
2020-04-05 13:48:00 +02:00
"No store without anyone can create invoice = 404 because the bitpay API can't know the storeid" ) ;
response = await tester . PayTester . HttpClient . SendAsync (
new HttpRequestMessage ( HttpMethod . Post , $"invoices" )
{
Content = new StringContent ( "{\"Price\": 5000, \"currency\": \"USD\"}" , Encoding . UTF8 ,
"application/json" ) ,
} ) ;
2019-03-25 04:59:42 +01:00
Assert . Equal ( 404 , ( int ) response . StatusCode ) ;
2021-10-25 08:18:02 +02:00
await user . ModifyPayment ( p = > p . AnyoneCanCreateInvoice = true ) ;
2019-03-25 04:59:42 +01:00
2021-11-22 09:16:08 +01:00
TestLogs . LogInformation ( "Bad store with anyone can create invoice = 403" ) ;
2020-04-05 13:48:00 +02:00
response = await tester . PayTester . HttpClient . SendAsync (
new HttpRequestMessage ( HttpMethod . Post , $"invoices?storeId=badid" )
{
Content = new StringContent ( "{\"Price\": 5000, \"currency\": \"USD\"}" , Encoding . UTF8 ,
"application/json" ) ,
} ) ;
2019-10-12 13:35:30 +02:00
Assert . Equal ( 403 , ( int ) response . StatusCode ) ;
2019-03-25 04:59:42 +01:00
2021-11-22 09:16:08 +01:00
TestLogs . LogInformation ( "Good store with anyone can create invoice = 200" ) ;
2020-04-05 13:48:00 +02:00
response = await tester . PayTester . HttpClient . SendAsync (
new HttpRequestMessage ( HttpMethod . Post , $"invoices?storeId={user.StoreId}" )
{
Content = new StringContent ( "{\"Price\": 5000, \"currency\": \"USD\"}" , Encoding . UTF8 ,
"application/json" ) ,
} ) ;
2019-03-25 04:59:42 +01:00
Assert . Equal ( 200 , ( int ) response . StatusCode ) ;
}
}
2021-04-18 04:26:06 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2019-10-07 09:04:25 +02:00
public async Task CanTweakRate ( )
2018-01-17 07:59:31 +01:00
{
2021-11-22 09:16:08 +01:00
using ( var tester = CreateServerTester ( ) )
2018-01-17 07:59:31 +01:00
{
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2018-01-17 07:59:31 +01:00
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
2018-02-23 07:21:42 +01:00
user . RegisterDerivationScheme ( "BTC" ) ;
2018-01-17 07:59:31 +01:00
// First we try payment with a merchant having only BTC
2020-04-05 13:48:00 +02:00
var invoice1 = user . BitPay . CreateInvoice (
new Invoice ( )
{
Price = 5000.0 m ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
2018-08-22 17:24:33 +02:00
Assert . Equal ( Money . Coins ( 1.0 m ) , invoice1 . BtcPrice ) ;
2018-01-17 07:59:31 +01:00
2018-04-29 19:33:42 +02:00
var storeController = user . GetController < StoresController > ( ) ;
2020-01-13 14:20:45 +01:00
var vm = ( RatesViewModel ) ( ( ViewResult ) storeController . Rates ( ) ) . Model ;
2018-08-01 11:38:46 +02:00
Assert . Equal ( 0.0 , vm . Spread ) ;
vm . Spread = 40 ;
2020-01-13 14:20:45 +01:00
await storeController . Rates ( vm ) ;
2018-01-17 07:59:31 +01:00
2020-04-05 13:48:00 +02:00
var invoice2 = user . BitPay . CreateInvoice (
new Invoice ( )
{
Price = 5000.0 m ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
2018-01-17 07:59:31 +01:00
2018-08-22 17:24:33 +02:00
var expectedRate = 5000.0 m * 0.6 m ;
var expectedCoins = invoice2 . Price / expectedRate ;
Assert . True ( invoice2 . BtcPrice . Almost ( Money . Coins ( expectedCoins ) , 0.00001 m ) ) ;
2018-01-17 07:59:31 +01:00
}
}
2021-08-03 10:03:00 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
[Trait("Integration", "Integration")]
public async Task CanCreateTopupInvoices ( )
{
2021-11-22 09:16:08 +01:00
using ( var tester = CreateServerTester ( ) )
2021-08-03 10:03:00 +02:00
{
await tester . StartAsync ( ) ;
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
var rng = new Random ( ) ;
var seed = rng . Next ( ) ;
rng = new Random ( seed ) ;
2021-11-22 09:16:08 +01:00
TestLogs . LogInformation ( "Seed: " + seed ) ;
2021-08-03 10:03:00 +02:00
foreach ( var networkFeeMode in Enum . GetValues ( typeof ( NetworkFeeMode ) ) . Cast < NetworkFeeMode > ( ) )
2021-10-29 08:25:43 +02:00
{
2021-08-03 10:03:00 +02:00
await user . SetNetworkFeeMode ( networkFeeMode ) ;
await AssertTopUpBtcPrice ( tester , user , Money . Coins ( 1.0 m ) , 5000.0 m , networkFeeMode ) ;
await AssertTopUpBtcPrice ( tester , user , Money . Coins ( 1.23456789 m ) , 5000.0 m * 1.23456789 m , networkFeeMode ) ;
// Check if there is no strange roundup issues
var v = ( decimal ) ( rng . NextDouble ( ) + 1.0 ) ;
v = Money . Coins ( v ) . ToDecimal ( MoneyUnit . BTC ) ;
await AssertTopUpBtcPrice ( tester , user , Money . Coins ( v ) , 5000.0 m * v , networkFeeMode ) ;
}
}
}
private static async Task AssertTopUpBtcPrice ( ServerTester tester , TestAccount user , Money btcSent , decimal expectedPriceWithoutNetworkFee , NetworkFeeMode networkFeeMode )
{
var cashCow = tester . ExplorerNode ;
// First we try payment with a merchant having only BTC
var client = await user . CreateClient ( ) ;
var invoice = await client . CreateInvoice ( user . StoreId , new CreateInvoiceRequest ( )
{
Amount = null ,
Currency = "USD"
} ) ;
Assert . Equal ( 0 m , invoice . Amount ) ;
Assert . Equal ( InvoiceType . TopUp , invoice . Type ) ;
var btcmethod = ( await client . GetInvoicePaymentMethods ( user . StoreId , invoice . Id ) ) [ 0 ] ;
var paid = btcSent ;
var invoiceAddress = BitcoinAddress . Create ( btcmethod . Destination , cashCow . Network ) ;
var btc = new PaymentMethodId ( "BTC" , PaymentTypes . BTCLike ) ;
var networkFee = ( await tester . PayTester . InvoiceRepository . GetInvoice ( invoice . Id ) )
. GetPaymentMethods ( ) [ btc ]
. GetPaymentMethodDetails ( )
. AssertType < BitcoinLikeOnChainPaymentMethod > ( )
. GetNextNetworkFee ( ) ;
if ( networkFeeMode ! = NetworkFeeMode . Always )
{
networkFee = 0.0 m ;
}
cashCow . SendToAddress ( invoiceAddress , paid ) ;
await TestUtils . EventuallyAsync ( async ( ) = >
{
try
{
var bitpayinvoice = await user . BitPay . GetInvoiceAsync ( invoice . Id ) ;
Assert . NotEqual ( 0.0 m , bitpayinvoice . Price ) ;
var due = Money . Parse ( bitpayinvoice . CryptoInfo [ 0 ] . CryptoPaid ) ;
Assert . Equal ( paid , due ) ;
Assert . Equal ( expectedPriceWithoutNetworkFee - networkFee * bitpayinvoice . Rate , bitpayinvoice . Price ) ;
Assert . Equal ( Money . Zero , bitpayinvoice . BtcDue ) ;
Assert . Equal ( "paid" , bitpayinvoice . Status ) ;
Assert . Equal ( "False" , bitpayinvoice . ExceptionStatus . ToString ( ) ) ;
// Check if we index by price correctly once we know it
var invoices = await client . GetInvoices ( user . StoreId , textSearch : $"{bitpayinvoice.Price.ToString(CultureInfo.InvariantCulture)}" ) ;
Assert . Contains ( invoices , inv = > inv . Id = = bitpayinvoice . Id ) ;
}
catch ( JsonSerializationException )
{
Assert . False ( true , "The bitpay's amount is not set" ) ;
}
} ) ;
}
2021-04-18 04:26:06 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2019-10-07 09:04:25 +02:00
public async Task CanModifyRates ( )
2018-05-03 18:46:52 +02:00
{
2021-11-22 09:16:08 +01:00
using ( var tester = CreateServerTester ( ) )
2018-05-03 18:46:52 +02:00
{
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2018-05-03 18:46:52 +02:00
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
var store = user . GetController < StoresController > ( ) ;
2020-01-13 14:20:45 +01:00
var rateVm = Assert . IsType < RatesViewModel > ( Assert . IsType < ViewResult > ( store . Rates ( ) ) . Model ) ;
2018-05-03 18:46:52 +02:00
Assert . False ( rateVm . ShowScripting ) ;
2020-01-10 14:50:39 +01:00
Assert . Equal ( CoinGeckoRateProvider . CoinGeckoName , rateVm . PreferredExchange ) ;
2018-08-01 11:38:46 +02:00
Assert . Equal ( 0.0 , rateVm . Spread ) ;
2018-05-03 18:46:52 +02:00
Assert . Null ( rateVm . TestRateRules ) ;
rateVm . PreferredExchange = "bitflyer" ;
2020-01-13 14:20:45 +01:00
Assert . IsType < RedirectToActionResult > ( await store . Rates ( rateVm , "Save" ) ) ;
rateVm = Assert . IsType < RatesViewModel > ( Assert . IsType < ViewResult > ( store . Rates ( ) ) . Model ) ;
2018-05-03 18:46:52 +02:00
Assert . Equal ( "bitflyer" , rateVm . PreferredExchange ) ;
rateVm . ScriptTest = "BTC_JPY,BTC_CAD" ;
2018-08-01 11:38:46 +02:00
rateVm . Spread = 10 ;
2018-05-03 18:46:52 +02:00
store = user . GetController < StoresController > ( ) ;
2020-04-05 13:48:00 +02:00
rateVm = Assert . IsType < RatesViewModel > ( Assert . IsType < ViewResult > ( await store . Rates ( rateVm , "Test" ) )
. Model ) ;
2018-05-03 18:46:52 +02:00
Assert . NotNull ( rateVm . TestRateRules ) ;
Assert . Equal ( 2 , rateVm . TestRateRules . Count ) ;
Assert . False ( rateVm . TestRateRules [ 0 ] . Error ) ;
2020-04-05 13:48:00 +02:00
Assert . StartsWith ( "(bitflyer(BTC_JPY)) * (0.9, 1.1) =" , rateVm . TestRateRules [ 0 ] . Rule ,
StringComparison . OrdinalIgnoreCase ) ;
2018-05-03 18:46:52 +02:00
Assert . True ( rateVm . TestRateRules [ 1 ] . Error ) ;
2020-01-13 14:20:45 +01:00
Assert . IsType < RedirectToActionResult > ( await store . Rates ( rateVm , "Save" ) ) ;
2018-05-03 18:46:52 +02:00
Assert . IsType < RedirectToActionResult > ( store . ShowRateRulesPost ( true ) . Result ) ;
2020-01-13 14:20:45 +01:00
Assert . IsType < RedirectToActionResult > ( await store . Rates ( rateVm , "Save" ) ) ;
2018-05-03 18:46:52 +02:00
store = user . GetController < StoresController > ( ) ;
2020-01-13 14:20:45 +01:00
rateVm = Assert . IsType < RatesViewModel > ( Assert . IsType < ViewResult > ( store . Rates ( ) ) . Model ) ;
2019-03-11 10:39:21 +01:00
Assert . Equal ( rateVm . StoreId , user . StoreId ) ;
2018-05-03 18:46:52 +02:00
Assert . Equal ( rateVm . DefaultScript , rateVm . Script ) ;
Assert . True ( rateVm . ShowScripting ) ;
rateVm . ScriptTest = "BTC_JPY" ;
2020-04-05 13:48:00 +02:00
rateVm = Assert . IsType < RatesViewModel > ( Assert . IsType < ViewResult > ( await store . Rates ( rateVm , "Test" ) )
. Model ) ;
2018-05-03 18:46:52 +02:00
Assert . True ( rateVm . ShowScripting ) ;
2020-04-05 13:48:00 +02:00
Assert . Contains ( "(bitflyer(BTC_JPY)) * (0.9, 1.1) = " , rateVm . TestRateRules [ 0 ] . Rule ,
StringComparison . OrdinalIgnoreCase ) ;
2018-05-03 18:46:52 +02:00
rateVm . ScriptTest = "BTC_USD,BTC_CAD,DOGE_USD,DOGE_CAD" ;
rateVm . Script = "DOGE_X = bittrex(DOGE_BTC) * BTC_X;\n" +
2020-11-02 13:10:55 +01:00
"X_CAD = ndax(X_CAD);\n" +
2020-04-05 13:48:00 +02:00
"X_X = coingecko(X_X);" ;
2018-08-01 11:38:46 +02:00
rateVm . Spread = 50 ;
2020-04-05 13:48:00 +02:00
rateVm = Assert . IsType < RatesViewModel > ( Assert . IsType < ViewResult > ( await store . Rates ( rateVm , "Test" ) )
. Model ) ;
2018-05-03 18:46:52 +02:00
Assert . True ( rateVm . TestRateRules . All ( t = > ! t . Error ) ) ;
2020-01-13 14:20:45 +01:00
Assert . IsType < RedirectToActionResult > ( await store . Rates ( rateVm , "Save" ) ) ;
2018-05-03 18:46:52 +02:00
store = user . GetController < StoresController > ( ) ;
2020-01-13 14:20:45 +01:00
rateVm = Assert . IsType < RatesViewModel > ( Assert . IsType < ViewResult > ( store . Rates ( ) ) . Model ) ;
2018-08-01 11:38:46 +02:00
Assert . Equal ( 50 , rateVm . Spread ) ;
2018-05-03 18:46:52 +02:00
Assert . True ( rateVm . ShowScripting ) ;
Assert . Contains ( "DOGE_X" , rateVm . Script , StringComparison . OrdinalIgnoreCase ) ;
}
}
2021-10-20 16:17:40 +02:00
[Fact]
[Trait("Integration", "Integration")]
public async Task CanUseDefaultCurrency ( )
{
2021-11-22 09:16:08 +01:00
using ( var tester = CreateServerTester ( ) )
2021-10-20 16:17:40 +02:00
{
await tester . StartAsync ( ) ;
var user = tester . NewAccount ( ) ;
2021-10-29 08:25:43 +02:00
await user . GrantAccessAsync ( true ) ;
2021-10-20 16:17:40 +02:00
user . RegisterDerivationScheme ( "BTC" ) ;
2021-10-25 08:18:02 +02:00
await user . ModifyPayment ( s = >
2021-10-20 16:17:40 +02:00
{
Assert . Equal ( "USD" , s . DefaultCurrency ) ;
s . DefaultCurrency = "EUR" ;
} ) ;
var client = await user . CreateClient ( ) ;
// with greenfield
var invoice = await client . CreateInvoice ( user . StoreId , new CreateInvoiceRequest ( ) ) ;
Assert . Equal ( "EUR" , invoice . Currency ) ;
Assert . Equal ( InvoiceType . TopUp , invoice . Type ) ;
// with bitpay api
var invoice2 = await user . BitPay . CreateInvoiceAsync ( new Invoice ( ) ) ;
Assert . Equal ( "EUR" , invoice2 . Currency ) ;
// via UI
var controller = user . GetController < InvoiceController > ( ) ;
var model = await controller . CreateInvoice ( ) ;
( await controller . CreateInvoice ( new CreateInvoiceModel ( ) , default ) ) . AssertType < RedirectToActionResult > ( ) ;
invoice = await client . GetInvoice ( user . StoreId , controller . CreatedInvoiceId ) ;
Assert . Equal ( "EUR" , invoice . Currency ) ;
Assert . Equal ( InvoiceType . TopUp , invoice . Type ) ;
// Check that the SendWallet use the default currency
var walletController = user . GetController < WalletsController > ( ) ;
var walletSend = await walletController . WalletSend ( new WalletId ( user . StoreId , "BTC" ) ) . AssertViewModelAsync < WalletSendModel > ( ) ;
Assert . Equal ( "EUR" , walletSend . Fiat ) ;
}
}
2019-12-26 06:22:36 +01:00
[Fact]
2021-10-18 09:56:47 +02:00
[Trait("Lightning", "Lightning")]
2018-10-28 14:46:03 +01:00
public async Task CanSetPaymentMethodLimits ( )
2018-04-03 10:39:28 +02:00
{
2021-11-22 09:16:08 +01:00
using ( var tester = CreateServerTester ( ) )
2018-04-03 10:39:28 +02:00
{
2021-10-18 09:56:47 +02:00
tester . ActivateLightning ( ) ;
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2018-04-03 10:39:28 +02:00
var user = tester . NewAccount ( ) ;
2021-10-18 09:56:47 +02:00
user . GrantAccess ( true ) ;
2018-04-03 10:39:28 +02:00
user . RegisterDerivationScheme ( "BTC" ) ;
2021-10-18 09:56:47 +02:00
await user . RegisterLightningNodeAsync ( "BTC" ) ;
var lnMethod = new PaymentMethodId ( "BTC" , PaymentTypes . LightningLike ) . ToString ( ) ;
var btcMethod = new PaymentMethodId ( "BTC" , PaymentTypes . BTCLike ) . ToString ( ) ;
// We allow BTC and LN, but not BTC under 5 USD, so only LN should be in the invoice
2021-10-29 08:25:43 +02:00
var vm = Assert . IsType < CheckoutAppearanceViewModel > ( Assert
. IsType < ViewResult > ( user . GetController < StoresController > ( ) . CheckoutAppearance ( ) ) . Model ) ;
Assert . Equal ( 2 , vm . PaymentMethodCriteria . Count ) ;
2021-10-18 09:56:47 +02:00
var criteria = Assert . Single ( vm . PaymentMethodCriteria . Where ( m = > m . PaymentMethod = = btcMethod . ToString ( ) ) ) ;
2020-09-15 11:09:09 +02:00
Assert . Equal ( new PaymentMethodId ( "BTC" , BitcoinPaymentType . Instance ) . ToString ( ) , criteria . PaymentMethod ) ;
criteria . Value = "5 USD" ;
criteria . Type = PaymentMethodCriteriaViewModel . CriteriaType . GreaterThan ;
2021-10-29 08:25:43 +02:00
Assert . IsType < RedirectToActionResult > ( user . GetController < StoresController > ( ) . CheckoutAppearance ( vm )
2020-04-05 13:48:00 +02:00
. Result ) ;
2018-04-03 10:39:28 +02:00
2020-04-05 13:48:00 +02:00
var invoice = user . BitPay . CreateInvoice (
2021-10-29 08:25:43 +02:00
new Invoice
2020-04-05 13:48:00 +02:00
{
2021-10-18 09:56:47 +02:00
Price = 4.5 m ,
2020-04-05 13:48:00 +02:00
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
2018-04-03 10:39:28 +02:00
Assert . Single ( invoice . CryptoInfo ) ;
2021-10-18 09:56:47 +02:00
Assert . Equal ( PaymentTypes . LightningLike . ToString ( ) , invoice . CryptoInfo [ 0 ] . PaymentType ) ;
// Let's replicate https://github.com/btcpayserver/btcpayserver/issues/2963
// We allow BTC for more than 5 USD, and LN for less than 150. The default is LN, so the default
// payment method should be LN.
2021-10-29 08:25:43 +02:00
vm = Assert . IsType < CheckoutAppearanceViewModel > ( Assert
. IsType < ViewResult > ( user . GetController < StoresController > ( ) . CheckoutAppearance ( ) ) . Model ) ;
2021-10-18 09:56:47 +02:00
vm . DefaultPaymentMethod = lnMethod ;
criteria = vm . PaymentMethodCriteria . First ( ) ;
criteria . Value = "150 USD" ;
criteria . Type = PaymentMethodCriteriaViewModel . CriteriaType . LessThan ;
criteria = vm . PaymentMethodCriteria . Skip ( 1 ) . First ( ) ;
criteria . Value = "5 USD" ;
criteria . Type = PaymentMethodCriteriaViewModel . CriteriaType . GreaterThan ;
2021-10-29 08:25:43 +02:00
Assert . IsType < RedirectToActionResult > ( user . GetController < StoresController > ( ) . CheckoutAppearance ( vm )
2021-10-18 09:56:47 +02:00
. Result ) ;
invoice = user . BitPay . CreateInvoice (
new Invoice ( )
{
Price = 50 m ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
var checkout = ( await user . GetController < InvoiceController > ( ) . Checkout ( invoice . Id ) ) . AssertViewModel < PaymentModel > ( ) ;
Assert . Equal ( lnMethod , checkout . PaymentMethodId ) ;
// If we change store's default, it should change the checkout's default
vm . DefaultPaymentMethod = btcMethod ;
2021-10-29 08:25:43 +02:00
Assert . IsType < RedirectToActionResult > ( user . GetController < StoresController > ( ) . CheckoutAppearance ( vm )
2021-10-18 09:56:47 +02:00
. Result ) ;
checkout = ( await user . GetController < InvoiceController > ( ) . Checkout ( invoice . Id ) ) . AssertViewModel < PaymentModel > ( ) ;
Assert . Equal ( btcMethod , checkout . PaymentMethodId ) ;
2019-12-24 10:11:21 +01:00
}
}
2018-04-03 10:39:28 +02:00
2020-11-10 06:41:29 +01:00
[Fact]
[Trait("Integration", "Integration")]
public async Task CanSetUnifiedQrCode ( )
{
2021-11-22 09:16:08 +01:00
using ( var tester = CreateServerTester ( ) )
2020-11-10 06:41:29 +01:00
{
tester . ActivateLightning ( ) ;
await tester . StartAsync ( ) ;
await tester . EnsureChannelsSetup ( ) ;
var user = tester . NewAccount ( ) ;
2021-10-29 08:25:43 +02:00
var cryptoCode = "BTC" ;
await user . GrantAccessAsync ( true ) ;
user . RegisterDerivationScheme ( cryptoCode , ScriptPubKeyType . Segwit ) ;
user . RegisterLightningNode ( cryptoCode , LightningConnectionType . CLightning ) ;
2020-11-10 06:41:29 +01:00
var invoice = user . BitPay . CreateInvoice (
2021-10-29 08:25:43 +02:00
new Invoice
2020-11-10 06:41:29 +01:00
{
Price = 5.5 m ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
// validate that invoice data model doesn't have lightning string initially
var res = await user . GetController < InvoiceController > ( ) . Checkout ( invoice . Id ) ;
var paymentMethodFirst = Assert . IsType < PaymentModel > (
Assert . IsType < ViewResult > ( res ) . Model
) ;
Assert . DoesNotContain ( "&lightning=" , paymentMethodFirst . InvoiceBitcoinUrlQR ) ;
// enable unified QR code in settings
2021-10-29 08:25:43 +02:00
var vm = Assert . IsType < LightningSettingsViewModel > ( Assert
. IsType < ViewResult > ( await user . GetController < StoresController > ( ) . LightningSettings ( user . StoreId , cryptoCode ) ) . Model
2020-11-10 06:41:29 +01:00
) ;
vm . OnChainWithLnInvoiceFallback = true ;
Assert . IsType < RedirectToActionResult > (
2021-10-29 08:25:43 +02:00
user . GetController < StoresController > ( ) . LightningSettings ( vm ) . Result
2020-11-10 06:41:29 +01:00
) ;
// validate that QR code now has both onchain and offchain payment urls
res = await user . GetController < InvoiceController > ( ) . Checkout ( invoice . Id ) ;
var paymentMethodSecond = Assert . IsType < PaymentModel > (
Assert . IsType < ViewResult > ( res ) . Model
) ;
Assert . Contains ( "&lightning=" , paymentMethodSecond . InvoiceBitcoinUrlQR ) ;
2020-12-04 06:39:18 +01:00
Assert . StartsWith ( "bitcoin:" , paymentMethodSecond . InvoiceBitcoinUrlQR ) ;
2020-11-10 06:57:48 +01:00
var split = paymentMethodSecond . InvoiceBitcoinUrlQR . Split ( '?' ) [ 0 ] ;
2020-12-04 00:30:14 +01:00
2021-03-06 05:52:25 +01:00
// Standard for all uppercase characters in QR codes is still not implemented in all wallets
// But we're proceeding with BECH32 being uppercase
Assert . True ( $"bitcoin:{paymentMethodSecond.BtcAddress.ToUpperInvariant()}" = = split ) ;
2021-06-08 05:24:17 +02:00
// Fallback lightning invoice should be uppercase inside the QR code.
2021-10-25 08:18:02 +02:00
var lightningFallback = paymentMethodSecond . InvoiceBitcoinUrlQR . Split ( new [ ] { "&lightning=" } , StringSplitOptions . None ) [ 1 ] ;
2021-06-08 05:24:17 +02:00
Assert . True ( lightningFallback . ToUpperInvariant ( ) = = lightningFallback ) ;
2019-12-24 10:11:21 +01:00
}
}
2018-04-03 10:39:28 +02:00
2019-12-24 10:11:21 +01:00
[Fact(Timeout = 60 * 2 * 1000)]
[Trait("Integration", "Integration")]
[Trait("Lightning", "Lightning")]
public async Task CanSetPaymentMethodLimitsLightning ( )
{
2021-11-22 09:16:08 +01:00
using ( var tester = CreateServerTester ( ) )
2019-12-24 10:11:21 +01:00
{
tester . ActivateLightning ( ) ;
await tester . StartAsync ( ) ;
await tester . EnsureChannelsSetup ( ) ;
var user = tester . NewAccount ( ) ;
2021-10-29 08:25:43 +02:00
var cryptoCode = "BTC" ;
2021-03-02 03:11:58 +01:00
user . GrantAccess ( true ) ;
2021-10-29 08:25:43 +02:00
user . RegisterLightningNode ( cryptoCode , LightningConnectionType . Charge ) ;
var vm = user . GetController < StoresController > ( ) . CheckoutAppearance ( ) . AssertViewModel < CheckoutAppearanceViewModel > ( ) ;
2021-10-25 08:18:02 +02:00
var criteria = Assert . Single ( vm . PaymentMethodCriteria ) ;
2021-10-29 08:25:43 +02:00
Assert . Equal ( new PaymentMethodId ( cryptoCode , LightningPaymentType . Instance ) . ToString ( ) , criteria . PaymentMethod ) ;
2020-09-15 11:09:09 +02:00
criteria . Value = "2 USD" ;
criteria . Type = PaymentMethodCriteriaViewModel . CriteriaType . LessThan ;
2021-10-29 08:25:43 +02:00
Assert . IsType < RedirectToActionResult > ( user . GetController < StoresController > ( ) . CheckoutAppearance ( vm )
2020-04-05 13:48:00 +02:00
. Result ) ;
2019-12-24 10:11:21 +01:00
2020-04-05 13:48:00 +02:00
var invoice = user . BitPay . CreateInvoice (
2021-10-25 08:18:02 +02:00
new Invoice
2020-04-05 13:48:00 +02:00
{
Price = 1.5 m ,
2021-10-25 08:18:02 +02:00
Currency = "USD"
2020-04-05 13:48:00 +02:00
} , Facade . Merchant ) ;
2018-04-03 10:39:28 +02:00
Assert . Single ( invoice . CryptoInfo ) ;
2019-12-24 10:11:21 +01:00
Assert . Equal ( PaymentTypes . LightningLike . ToString ( ) , invoice . CryptoInfo [ 0 ] . PaymentType ) ;
2021-10-25 08:18:02 +02:00
// Activating LNUrl, we should still have only 1 payment criteria that can be set.
2021-10-29 08:25:43 +02:00
user . RegisterLightningNode ( cryptoCode , LightningConnectionType . Charge ) ;
var lnSettingsVm = await user . GetController < StoresController > ( ) . LightningSettings ( user . StoreId , cryptoCode ) . AssertViewModelAsync < LightningSettingsViewModel > ( ) ;
lnSettingsVm . LNURLEnabled = true ;
lnSettingsVm . LNURLStandardInvoiceEnabled = true ;
Assert . IsType < RedirectToActionResult > ( user . GetController < StoresController > ( ) . LightningSettings ( lnSettingsVm ) . Result ) ;
vm = user . GetController < StoresController > ( ) . CheckoutAppearance ( ) . AssertViewModel < CheckoutAppearanceViewModel > ( ) ;
2021-10-25 08:18:02 +02:00
criteria = Assert . Single ( vm . PaymentMethodCriteria ) ;
2021-10-29 08:25:43 +02:00
Assert . Equal ( new PaymentMethodId ( cryptoCode , LightningPaymentType . Instance ) . ToString ( ) , criteria . PaymentMethod ) ;
Assert . IsType < RedirectToActionResult > ( user . GetController < StoresController > ( ) . CheckoutAppearance ( vm ) . Result ) ;
2021-10-25 08:18:02 +02:00
// However, creating an invoice should show LNURL
invoice = user . BitPay . CreateInvoice (
new Invoice
{
Price = 1.5 m ,
Currency = "USD"
} , Facade . Merchant ) ;
Assert . Equal ( 2 , invoice . CryptoInfo . Length ) ;
// Make sure this throw: Since BOLT11 and LN Url share the same criteria, there should be no payment method available
Assert . Throws < BitPayException > ( ( ) = > user . BitPay . CreateInvoice (
new Invoice
{
Price = 2.5 m ,
Currency = "USD"
} , Facade . Merchant ) ) ;
2018-04-03 10:39:28 +02:00
}
}
2021-04-18 04:26:06 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
2018-11-27 07:13:09 +01:00
[Trait("Integration", "Integration")]
public async Task PosDataParser_ParsesCorrectly_Slower ( )
{
2021-11-22 09:16:08 +01:00
using ( var tester = CreateServerTester ( ) )
2018-11-27 07:13:09 +01:00
{
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2018-11-27 07:13:09 +01:00
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
2018-11-30 09:34:43 +01:00
2018-11-27 07:13:09 +01:00
var controller = tester . PayTester . GetController < InvoiceController > ( null ) ;
var testCases =
2019-01-26 05:26:49 +01:00
new List < ( string input , Dictionary < string , object > expectedOutput ) > ( )
2018-11-27 07:13:09 +01:00
{
2020-04-05 13:48:00 +02:00
{ ( null , new Dictionary < string , object > ( ) ) } ,
2019-01-26 05:26:49 +01:00
{ ( "" , new Dictionary < string , object > ( ) ) } ,
{ ( "{}" , new Dictionary < string , object > ( ) ) } ,
2020-04-05 13:48:00 +02:00
{
( "non-json-content" ,
new Dictionary < string , object > ( ) { { string . Empty , "non-json-content" } } )
} ,
{ ( "[1,2,3]" , new Dictionary < string , object > ( ) { { string . Empty , "[1,2,3]" } } ) } ,
{ ( "{ \"key\": \"value\"}" , new Dictionary < string , object > ( ) { { "key" , "value" } } ) } ,
{ ( "{ \"key\": true}" , new Dictionary < string , object > ( ) { { "key" , "True" } } ) } ,
{
( "{ invalidjson file here}" ,
new Dictionary < string , object > ( ) { { String . Empty , "{ invalidjson file here}" } } )
}
2018-11-27 07:13:09 +01:00
} ;
var tasks = new List < Task > ( ) ;
foreach ( var valueTuple in testCases )
{
2020-06-24 03:34:09 +02:00
tasks . Add ( user . BitPay . CreateInvoiceAsync ( new Invoice ( 1 , "BTC" ) { PosData = valueTuple . input } )
2020-04-05 13:48:00 +02:00
. ContinueWith ( async task = >
{
var result = await controller . Invoice ( task . Result . Id ) ;
var viewModel =
Assert . IsType < InvoiceDetailsModel > (
Assert . IsType < ViewResult > ( result ) . Model ) ;
Assert . Equal ( valueTuple . expectedOutput , viewModel . PosData ) ;
} ) ) ;
2018-11-27 07:13:09 +01:00
}
await Task . WhenAll ( tasks ) ;
}
}
2021-04-18 04:26:06 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
2018-11-30 09:34:43 +01:00
[Trait("Integration", "Integration")]
2019-10-07 09:04:25 +02:00
public async Task CanExportInvoicesJson ( )
2018-11-30 09:34:43 +01:00
{
2019-01-04 16:37:09 +01:00
decimal GetFieldValue ( string input , string fieldName )
{
var match = Regex . Match ( input , $"\" { fieldName } \ ":([^,]*)" ) ;
Assert . True ( match . Success ) ;
return decimal . Parse ( match . Groups [ 1 ] . Value . Trim ( ) , CultureInfo . InvariantCulture ) ;
}
2020-04-05 13:48:00 +02:00
2021-11-22 09:16:08 +01:00
using ( var tester = CreateServerTester ( ) )
2018-11-30 09:34:43 +01:00
{
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2018-11-30 09:34:43 +01:00
var user = tester . NewAccount ( ) ;
2021-10-29 08:25:43 +02:00
await user . GrantAccessAsync ( ) ;
2018-11-30 09:34:43 +01:00
user . RegisterDerivationScheme ( "BTC" ) ;
2021-06-18 03:25:17 +02:00
await user . SetNetworkFeeMode ( NetworkFeeMode . Always ) ;
2020-04-05 13:48:00 +02:00
var invoice = user . BitPay . CreateInvoice (
2021-10-29 08:25:43 +02:00
new Invoice
2020-04-05 13:48:00 +02:00
{
Price = 10 ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some \", description" ,
FullNotifications = true
} , Facade . Merchant ) ;
2018-11-30 09:34:43 +01:00
2019-01-04 16:37:09 +01:00
var networkFee = new FeeRate ( invoice . MinerFees [ "BTC" ] . SatoshiPerBytes ) . GetFee ( 100 ) ;
2018-11-30 09:34:43 +01:00
// ensure 0 invoices exported because there are no payments yet
var jsonResult = user . GetController < InvoiceController > ( ) . Export ( "json" ) . GetAwaiter ( ) . GetResult ( ) ;
var result = Assert . IsType < ContentResult > ( jsonResult ) ;
Assert . Equal ( "application/json" , result . ContentType ) ;
Assert . Equal ( "[]" , result . Content ) ;
var cashCow = tester . ExplorerNode ;
var invoiceAddress = BitcoinAddress . Create ( invoice . CryptoInfo [ 0 ] . Address , cashCow . Network ) ;
2020-02-13 06:44:31 +01:00
//
2019-01-04 16:37:09 +01:00
var firstPayment = invoice . CryptoInfo [ 0 ] . TotalDue - 3 * networkFee ;
2018-11-30 09:34:43 +01:00
cashCow . SendToAddress ( invoiceAddress , firstPayment ) ;
2018-12-18 18:56:12 +01:00
Thread . Sleep ( 1000 ) ; // prevent race conditions, ordering payments
// look if you can reduce thread sleep, this was min value for me
// should reduce invoice due by 0 USD because payment = network fee
cashCow . SendToAddress ( invoiceAddress , networkFee ) ;
Thread . Sleep ( 1000 ) ;
// pay remaining amount
2019-01-04 16:37:09 +01:00
cashCow . SendToAddress ( invoiceAddress , 4 * networkFee ) ;
2018-12-18 18:56:12 +01:00
Thread . Sleep ( 1000 ) ;
2018-11-30 09:34:43 +01:00
2019-01-05 19:47:39 +01:00
TestUtils . Eventually ( ( ) = >
2018-11-30 09:34:43 +01:00
{
2020-04-05 13:48:00 +02:00
var jsonResultPaid =
user . GetController < InvoiceController > ( ) . Export ( "json" ) . GetAwaiter ( ) . GetResult ( ) ;
2018-11-30 09:34:43 +01:00
var paidresult = Assert . IsType < ContentResult > ( jsonResultPaid ) ;
Assert . Equal ( "application/json" , paidresult . ContentType ) ;
2018-12-18 18:56:12 +01:00
var parsedJson = JsonConvert . DeserializeObject < object [ ] > ( paidresult . Content ) ;
Assert . Equal ( 3 , parsedJson . Length ) ;
2019-01-04 16:37:09 +01:00
var invoiceDueAfterFirstPayment = ( 3 * networkFee ) . ToDecimal ( MoneyUnit . BTC ) * invoice . Rate ;
2018-12-18 18:56:12 +01:00
var pay1str = parsedJson [ 0 ] . ToString ( ) ;
Assert . Contains ( "\"InvoiceItemDesc\": \"Some \\\", description\"" , pay1str ) ;
2019-01-04 16:37:09 +01:00
Assert . Equal ( invoiceDueAfterFirstPayment , GetFieldValue ( pay1str , "InvoiceDue" ) ) ;
2018-12-18 18:56:12 +01:00
Assert . Contains ( "\"InvoicePrice\": 10.0" , pay1str ) ;
Assert . Contains ( "\"ConversionRate\": 5000.0" , pay1str ) ;
Assert . Contains ( $"\" InvoiceId \ ": \"{invoice.Id}\"," , pay1str ) ;
var pay2str = parsedJson [ 1 ] . ToString ( ) ;
2019-01-04 16:37:09 +01:00
Assert . Equal ( invoiceDueAfterFirstPayment , GetFieldValue ( pay2str , "InvoiceDue" ) ) ;
2018-12-18 18:56:12 +01:00
var pay3str = parsedJson [ 2 ] . ToString ( ) ;
Assert . Contains ( "\"InvoiceDue\": 0" , pay3str ) ;
2018-11-30 09:34:43 +01:00
} ) ;
}
}
2020-04-05 13:48:00 +02:00
2021-04-18 04:26:06 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
2019-01-04 16:37:09 +01:00
[Trait("Integration", "Integration")]
2019-10-07 09:04:25 +02:00
public async Task CanChangeNetworkFeeMode ( )
2019-01-04 16:37:09 +01:00
{
2021-11-22 09:16:08 +01:00
using ( var tester = CreateServerTester ( ) )
2019-01-04 16:37:09 +01:00
{
2020-04-05 13:00:28 +02:00
var btc = new PaymentMethodId ( "BTC" , PaymentTypes . BTCLike ) ;
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2019-01-04 16:37:09 +01:00
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
foreach ( var networkFeeMode in Enum . GetValues ( typeof ( NetworkFeeMode ) ) . Cast < NetworkFeeMode > ( ) )
{
2021-11-22 09:16:08 +01:00
TestLogs . LogInformation ( $"Trying with {nameof(networkFeeMode)}={networkFeeMode}" ) ;
2021-06-18 03:25:17 +02:00
await user . SetNetworkFeeMode ( networkFeeMode ) ;
2020-04-05 13:48:00 +02:00
var invoice = user . BitPay . CreateInvoice (
2021-10-29 08:25:43 +02:00
new Invoice
2020-04-05 13:48:00 +02:00
{
Price = 10 ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some \", description" ,
FullNotifications = true
} , Facade . Merchant ) ;
2020-04-05 13:00:28 +02:00
var nextNetworkFee = ( await tester . PayTester . InvoiceRepository . GetInvoice ( invoice . Id ) )
. GetPaymentMethods ( ) [ btc ]
. GetPaymentMethodDetails ( )
. AssertType < BitcoinLikeOnChainPaymentMethod > ( )
. GetNextNetworkFee ( ) ;
var firstPaymentFee = nextNetworkFee ;
2019-01-04 16:37:09 +01:00
switch ( networkFeeMode )
{
2020-04-05 13:48:00 +02:00
case NetworkFeeMode . Never :
case NetworkFeeMode . MultiplePaymentsOnly :
2020-04-05 13:00:28 +02:00
Assert . Equal ( 0.0 m , nextNetworkFee ) ;
2019-01-04 16:37:09 +01:00
break ;
case NetworkFeeMode . Always :
2020-04-05 13:00:28 +02:00
Assert . NotEqual ( 0.0 m , nextNetworkFee ) ;
2019-01-04 16:37:09 +01:00
break ;
}
2020-04-05 13:48:00 +02:00
2020-04-05 13:00:28 +02:00
var missingMoney = Money . Satoshis ( 5000 ) . ToDecimal ( MoneyUnit . BTC ) ;
var cashCow = tester . ExplorerNode ;
var invoiceAddress = BitcoinAddress . Create ( invoice . CryptoInfo [ 0 ] . Address , cashCow . Network ) ;
var due = Money . Parse ( invoice . CryptoInfo [ 0 ] . Due ) ;
var productPartDue = ( invoice . Price / invoice . Rate ) ;
2021-11-22 09:16:08 +01:00
TestLogs . LogInformation (
2020-04-05 13:48:00 +02:00
$"Product part due is {productPartDue} and due {due} with network fee {nextNetworkFee}" ) ;
2020-04-05 13:00:28 +02:00
Assert . Equal ( productPartDue + nextNetworkFee , due . ToDecimal ( MoneyUnit . BTC ) ) ;
2019-01-04 16:37:09 +01:00
var firstPayment = productPartDue - missingMoney ;
cashCow . SendToAddress ( invoiceAddress , Money . Coins ( firstPayment ) ) ;
2020-04-05 13:00:28 +02:00
await TestUtils . EventuallyAsync ( async ( ) = >
{
2019-01-04 16:37:09 +01:00
invoice = user . BitPay . GetInvoice ( invoice . Id ) ;
due = Money . Parse ( invoice . CryptoInfo [ 0 ] . Due ) ;
2021-11-22 09:16:08 +01:00
TestLogs . LogInformation ( $"Remaining due after first payment: {due}" ) ;
2019-01-04 16:37:09 +01:00
Assert . Equal ( Money . Coins ( firstPayment ) , Money . Parse ( invoice . CryptoInfo [ 0 ] . Paid ) ) ;
2020-04-05 13:00:28 +02:00
nextNetworkFee = ( await tester . PayTester . InvoiceRepository . GetInvoice ( invoice . Id ) )
. GetPaymentMethods ( ) [ btc ]
. GetPaymentMethodDetails ( )
. AssertType < BitcoinLikeOnChainPaymentMethod > ( )
. GetNextNetworkFee ( ) ;
2019-01-04 16:37:09 +01:00
switch ( networkFeeMode )
{
2020-04-05 13:48:00 +02:00
case NetworkFeeMode . Never :
2020-04-05 13:00:28 +02:00
Assert . Equal ( 0.0 m , nextNetworkFee ) ;
2019-01-04 16:37:09 +01:00
break ;
2020-04-05 13:00:28 +02:00
case NetworkFeeMode . MultiplePaymentsOnly :
2019-01-04 16:37:09 +01:00
case NetworkFeeMode . Always :
2020-04-05 13:00:28 +02:00
Assert . NotEqual ( 0.0 m , nextNetworkFee ) ;
2019-01-04 16:37:09 +01:00
break ;
}
2020-04-05 13:48:00 +02:00
2020-04-05 13:00:28 +02:00
Assert . Equal ( missingMoney + firstPaymentFee + nextNetworkFee , due . ToDecimal ( MoneyUnit . BTC ) ) ;
2020-04-05 13:48:00 +02:00
Assert . Equal ( firstPayment + missingMoney + firstPaymentFee + nextNetworkFee ,
Money . Parse ( invoice . CryptoInfo [ 0 ] . TotalDue ) . ToDecimal ( MoneyUnit . BTC ) ) ;
2019-01-04 16:37:09 +01:00
} ) ;
cashCow . SendToAddress ( invoiceAddress , due ) ;
2021-11-22 09:16:08 +01:00
TestLogs . LogInformation ( $"After payment of {due}, the invoice should be paid" ) ;
2019-01-06 09:29:21 +01:00
TestUtils . Eventually ( ( ) = >
2019-01-04 16:37:09 +01:00
{
invoice = user . BitPay . GetInvoice ( invoice . Id ) ;
Assert . Equal ( "paid" , invoice . Status ) ;
} ) ;
}
}
}
2018-11-30 09:34:43 +01:00
2021-04-18 04:26:06 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
2018-11-30 09:34:43 +01:00
[Trait("Integration", "Integration")]
2019-10-07 09:04:25 +02:00
public async Task CanExportInvoicesCsv ( )
2018-11-30 09:34:43 +01:00
{
2021-11-22 09:16:08 +01:00
using ( var tester = CreateServerTester ( ) )
2018-11-30 09:34:43 +01:00
{
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2018-11-30 09:34:43 +01:00
var user = tester . NewAccount ( ) ;
2021-10-29 08:25:43 +02:00
await user . GrantAccessAsync ( ) ;
2018-11-30 09:34:43 +01:00
user . RegisterDerivationScheme ( "BTC" ) ;
2021-06-18 03:25:17 +02:00
await user . SetNetworkFeeMode ( NetworkFeeMode . Always ) ;
2020-04-05 13:48:00 +02:00
var invoice = user . BitPay . CreateInvoice (
2021-10-29 08:25:43 +02:00
new Invoice
2020-04-05 13:48:00 +02:00
{
Price = 500 ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some \", description" ,
FullNotifications = true
} , Facade . Merchant ) ;
2018-11-30 09:34:43 +01:00
var cashCow = tester . ExplorerNode ;
var invoiceAddress = BitcoinAddress . Create ( invoice . CryptoInfo [ 0 ] . Address , cashCow . Network ) ;
2019-01-10 05:47:21 +01:00
var firstPayment = invoice . CryptoInfo [ 0 ] . TotalDue - Money . Coins ( 0.001 m ) ;
2018-11-30 09:34:43 +01:00
cashCow . SendToAddress ( invoiceAddress , firstPayment ) ;
2019-01-05 19:47:39 +01:00
TestUtils . Eventually ( ( ) = >
2018-11-30 09:34:43 +01:00
{
2020-04-05 13:48:00 +02:00
var exportResultPaid =
user . GetController < InvoiceController > ( ) . Export ( "csv" ) . GetAwaiter ( ) . GetResult ( ) ;
2018-11-30 09:34:43 +01:00
var paidresult = Assert . IsType < ContentResult > ( exportResultPaid ) ;
Assert . Equal ( "application/csv" , paidresult . ContentType ) ;
2020-07-30 03:01:56 +02:00
Assert . Contains ( $",orderId,{invoice.Id}," , paidresult . Content ) ;
Assert . Contains ( $",On-Chain,BTC,0.0991,0.0001,5000.0" , paidresult . Content ) ;
Assert . Contains ( $",USD,5.00" , paidresult . Content ) ; // Seems hacky but some plateform does not render this decimal the same
Assert . Contains ( "0,,\"Some \"\", description\",new (paidPartial),new,paidPartial" ,
2020-04-05 13:48:00 +02:00
paidresult . Content ) ;
2018-11-30 09:34:43 +01:00
} ) ;
}
}
2021-12-11 04:32:23 +01:00
2021-04-18 04:26:06 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2019-10-07 09:04:25 +02:00
public async Task CanCreateAndDeleteApps ( )
2018-04-03 09:53:55 +02:00
{
2021-11-22 09:16:08 +01:00
using ( var tester = CreateServerTester ( ) )
2018-04-03 09:53:55 +02:00
{
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2018-04-03 09:53:55 +02:00
var user = tester . NewAccount ( ) ;
2021-12-11 04:32:23 +01:00
await user . GrantAccessAsync ( ) ;
2018-04-03 09:53:55 +02:00
var user2 = tester . NewAccount ( ) ;
2021-12-11 04:32:23 +01:00
await user2 . GrantAccessAsync ( ) ;
2018-04-03 09:53:55 +02:00
var apps = user . GetController < AppsController > ( ) ;
var apps2 = user2 . GetController < AppsController > ( ) ;
2021-12-11 04:32:23 +01:00
var vm = Assert . IsType < CreateAppViewModel > ( Assert . IsType < ViewResult > ( apps . CreateApp ( user . StoreId ) ) . Model ) ;
2018-04-03 09:53:55 +02:00
Assert . NotNull ( vm . SelectedAppType ) ;
2021-10-29 12:29:02 +02:00
Assert . Null ( vm . AppName ) ;
vm . AppName = "test" ;
2018-07-08 08:33:42 +02:00
vm . SelectedAppType = AppType . PointOfSale . ToString ( ) ;
2021-12-11 04:32:23 +01:00
var redirectToAction = Assert . IsType < RedirectToActionResult > ( apps . CreateApp ( user . StoreId , vm ) . Result ) ;
2018-07-08 08:33:42 +02:00
Assert . Equal ( nameof ( apps . UpdatePointOfSale ) , redirectToAction . ActionName ) ;
2021-12-11 04:32:23 +01:00
var appList = Assert . IsType < ListAppsViewModel > ( Assert . IsType < ViewResult > ( apps . ListApps ( user . StoreId ) . Result ) . Model ) ;
2020-04-05 13:48:00 +02:00
var appList2 =
2021-12-11 04:32:23 +01:00
Assert . IsType < ListAppsViewModel > ( Assert . IsType < ViewResult > ( apps2 . ListApps ( user2 . StoreId ) . Result ) . Model ) ;
2021-12-16 17:37:19 +01:00
var app = appList . Apps [ 0 ] ;
apps . HttpContext . SetAppData ( new AppData { Id = app . Id , StoreDataId = app . StoreId , Name = app . AppName } ) ;
2018-04-03 09:53:55 +02:00
Assert . Single ( appList . Apps ) ;
Assert . Empty ( appList2 . Apps ) ;
Assert . Equal ( "test" , appList . Apps [ 0 ] . AppName ) ;
2018-10-09 16:38:56 +02:00
Assert . Equal ( apps . CreatedAppId , appList . Apps [ 0 ] . Id ) ;
2021-12-16 17:37:19 +01:00
Assert . True ( app . IsOwner ) ;
2018-04-03 09:53:55 +02:00
Assert . Equal ( user . StoreId , appList . Apps [ 0 ] . StoreId ) ;
2021-12-16 17:37:19 +01:00
Assert . IsType < NotFoundResult > ( apps2 . DeleteApp ( appList . Apps [ 0 ] . Id ) ) ;
Assert . IsType < ViewResult > ( apps . DeleteApp ( appList . Apps [ 0 ] . Id ) ) ;
2018-04-03 09:53:55 +02:00
redirectToAction = Assert . IsType < RedirectToActionResult > ( apps . DeleteAppPost ( appList . Apps [ 0 ] . Id ) . Result ) ;
Assert . Equal ( nameof ( apps . ListApps ) , redirectToAction . ActionName ) ;
2021-12-11 04:32:23 +01:00
appList = Assert . IsType < ListAppsViewModel > ( Assert . IsType < ViewResult > ( apps . ListApps ( user . StoreId ) . Result ) . Model ) ;
2018-04-03 09:53:55 +02:00
Assert . Empty ( appList . Apps ) ;
}
}
2021-04-18 04:26:06 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
2019-01-15 14:12:29 +01:00
[Trait("Integration", "Integration")]
2021-11-15 05:48:07 +01:00
[Trait("Lightning", "Lightning")]
2019-10-07 09:04:25 +02:00
public async Task CanCreateStrangeInvoice ( )
2019-01-15 14:12:29 +01:00
{
2021-11-22 09:16:08 +01:00
using ( var tester = CreateServerTester ( ) )
2019-01-15 14:12:29 +01:00
{
2021-11-15 05:48:07 +01:00
tester . ActivateLightning ( ) ;
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2019-01-15 14:12:29 +01:00
var user = tester . NewAccount ( ) ;
2021-11-15 05:48:07 +01:00
user . GrantAccess ( true ) ;
2019-01-15 14:12:29 +01:00
user . RegisterDerivationScheme ( "BTC" ) ;
2021-11-15 05:48:07 +01:00
2020-03-10 09:11:15 +01:00
DateTimeOffset expiration = DateTimeOffset . UtcNow + TimeSpan . FromMinutes ( 21 ) ;
2021-11-15 05:48:07 +01:00
// This should fail, the amount is too low to be above the dust limit of bitcoin
var ex = Assert . Throws < BitPayException > ( ( ) = > user . BitPay . CreateInvoice (
new Invoice ( )
{
Price = 0.000000012 m ,
Currency = "USD" ,
FullNotifications = true ,
ExpirationTime = expiration
} , Facade . Merchant ) ) ;
Assert . Contains ( "dust threshold" , ex . Message ) ;
await user . RegisterLightningNodeAsync ( "BTC" ) ;
2020-04-05 13:48:00 +02:00
var invoice1 = user . BitPay . CreateInvoice (
new Invoice ( )
{
Price = 0.000000012 m ,
Currency = "USD" ,
FullNotifications = true ,
ExpirationTime = expiration
} , Facade . Merchant ) ;
2021-11-15 05:48:07 +01:00
2020-03-10 09:11:15 +01:00
Assert . Equal ( expiration . ToUnixTimeSeconds ( ) , invoice1 . ExpirationTime . ToUnixTimeSeconds ( ) ) ;
2020-06-24 03:34:09 +02:00
var invoice2 = user . BitPay . CreateInvoice ( new Invoice ( ) { Price = 0.000000019 m , Currency = "USD" } ,
2020-04-05 13:48:00 +02:00
Facade . Merchant ) ;
2019-02-22 14:48:39 +01:00
Assert . Equal ( 0.000000012 m , invoice1 . Price ) ;
Assert . Equal ( 0.000000019 m , invoice2 . Price ) ;
// Should round up to 1 because 0.000000019 is unsignificant
2020-04-05 13:48:00 +02:00
var invoice3 = user . BitPay . CreateInvoice (
2020-06-24 03:34:09 +02:00
new Invoice ( ) { Price = 1.000000019 m , Currency = "USD" , FullNotifications = true } , Facade . Merchant ) ;
2019-02-22 14:48:39 +01:00
Assert . Equal ( 1 m , invoice3 . Price ) ;
2019-02-22 14:52:43 +01:00
// Should not round up at 8 digit because the 9th is insignificant
2020-04-05 13:48:00 +02:00
var invoice4 = user . BitPay . CreateInvoice (
2020-06-24 03:34:09 +02:00
new Invoice ( ) { Price = 1.000000019 m , Currency = "BTC" , FullNotifications = true } , Facade . Merchant ) ;
2019-02-22 14:52:43 +01:00
Assert . Equal ( 1.00000002 m , invoice4 . Price ) ;
// But not if the 9th is insignificant
2020-04-05 13:48:00 +02:00
invoice4 = user . BitPay . CreateInvoice (
2020-06-24 03:34:09 +02:00
new Invoice ( ) { Price = 0.000000019 m , Currency = "BTC" , FullNotifications = true } , Facade . Merchant ) ;
2019-02-22 14:52:43 +01:00
Assert . Equal ( 0.000000019 m , invoice4 . Price ) ;
2019-01-15 14:12:29 +01:00
2020-04-05 13:48:00 +02:00
var invoice = user . BitPay . CreateInvoice (
2020-06-24 03:34:09 +02:00
new Invoice ( ) { Price = - 0.1 m , Currency = "BTC" , FullNotifications = true } , Facade . Merchant ) ;
2019-01-15 14:12:29 +01:00
Assert . Equal ( 0.0 m , invoice . Price ) ;
2021-07-30 11:46:49 +02:00
// Should round down to 50.51, taxIncluded should be also clipped to this value because taxIncluded can't be higher than the price.
var invoice5 = user . BitPay . CreateInvoice (
new Invoice ( ) { Price = 50.513 m , Currency = "USD" , FullNotifications = true , TaxIncluded = 50.516 m } , Facade . Merchant ) ;
Assert . Equal ( 50.51 m , invoice5 . Price ) ;
Assert . Equal ( 50.51 m , invoice5 . TaxIncluded ) ;
var greenfield = await user . CreateClient ( ) ;
var invoice5g = await greenfield . CreateInvoice ( user . StoreId , new CreateInvoiceRequest ( )
{
Amount = 50.513 m ,
Currency = "USD" ,
2021-09-27 04:44:55 +02:00
Metadata = new JObject ( ) { new JProperty ( "taxIncluded" , 50.516 m ) , new JProperty ( "orderId" , "000000161" ) }
2021-07-30 11:46:49 +02:00
} ) ;
Assert . Equal ( 50.51 m , invoice5g . Amount ) ;
Assert . Equal ( 50.51 m , ( decimal ) invoice5g . Metadata [ "taxIncluded" ] ) ;
2021-09-27 04:44:55 +02:00
Assert . Equal ( "000000161" , ( string ) invoice5g . Metadata [ "orderId" ] ) ;
2021-09-06 17:23:41 +02:00
var zeroInvoice = await greenfield . CreateInvoice ( user . StoreId , new CreateInvoiceRequest ( )
{
Amount = 0 m ,
Currency = "USD"
} ) ;
Assert . Equal ( InvoiceStatus . New , zeroInvoice . Status ) ;
await TestUtils . EventuallyAsync ( async ( ) = >
{
zeroInvoice = await greenfield . GetInvoice ( user . StoreId , zeroInvoice . Id ) ;
Assert . Equal ( InvoiceStatus . Settled , zeroInvoice . Status ) ;
} ) ;
var zeroInvoicePM = await greenfield . GetInvoicePaymentMethods ( user . StoreId , zeroInvoice . Id ) ;
Assert . Empty ( zeroInvoicePM ) ;
2019-01-15 14:12:29 +01:00
}
}
2021-04-18 04:26:06 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
2018-10-28 14:51:02 +01:00
[Trait("Integration", "Integration")]
2019-10-07 09:04:25 +02:00
public async Task InvoiceFlowThroughDifferentStatesCorrectly ( )
2018-01-07 18:36:41 +01:00
{
2021-11-22 09:16:08 +01:00
using ( var tester = CreateServerTester ( ) )
2018-01-07 18:36:41 +01:00
{
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2018-01-07 18:36:41 +01:00
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
2018-02-23 07:21:42 +01:00
user . RegisterDerivationScheme ( "BTC" ) ;
2020-11-13 08:28:15 +01:00
await user . SetupWebhook ( ) ;
2020-04-05 13:48:00 +02:00
var invoice = user . BitPay . CreateInvoice (
new Invoice ( )
{
Price = 5000.0 m ,
TaxIncluded = 1000.0 m ,
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
2017-10-27 10:53:04 +02:00
var repo = tester . PayTester . GetService < InvoiceRepository > ( ) ;
var ctx = tester . PayTester . GetService < ApplicationDbContextFactory > ( ) . CreateContext ( ) ;
2018-02-25 16:48:12 +01:00
Assert . Equal ( 0 , invoice . CryptoInfo [ 0 ] . TxCount ) ;
2018-05-25 15:49:49 +02:00
Assert . True ( invoice . MinerFees . ContainsKey ( "BTC" ) ) ;
2020-06-24 03:34:09 +02:00
Assert . Contains ( invoice . MinerFees [ "BTC" ] . SatoshiPerBytes , new [ ] { 100.0 m , 20.0 m } ) ;
2019-01-05 19:47:39 +01:00
TestUtils . Eventually ( ( ) = >
2017-10-27 10:53:04 +02:00
{
2017-12-16 17:04:20 +01:00
var textSearchResult = tester . PayTester . InvoiceRepository . GetInvoices ( new InvoiceQuery ( )
2017-11-12 15:03:33 +01:00
{
2020-06-24 03:34:09 +02:00
StoreId = new [ ] { user . StoreId } ,
TextSearch = invoice . OrderId
2017-11-12 15:03:33 +01:00
} ) . GetAwaiter ( ) . GetResult ( ) ;
2017-12-21 07:52:04 +01:00
Assert . Single ( textSearchResult ) ;
2017-12-16 17:04:20 +01:00
textSearchResult = tester . PayTester . InvoiceRepository . GetInvoices ( new InvoiceQuery ( )
2017-11-12 15:03:33 +01:00
{
2020-06-24 03:34:09 +02:00
StoreId = new [ ] { user . StoreId } ,
TextSearch = invoice . Id
2017-11-12 15:03:33 +01:00
} ) . GetAwaiter ( ) . GetResult ( ) ;
2017-10-27 10:53:04 +02:00
2017-12-21 07:52:04 +01:00
Assert . Single ( textSearchResult ) ;
2017-11-12 15:03:33 +01:00
} ) ;
2017-10-27 10:53:04 +02:00
invoice = user . BitPay . GetInvoice ( invoice . Id , Facade . Merchant ) ;
2019-01-24 12:53:29 +01:00
Assert . Equal ( 1000.0 m , invoice . TaxIncluded ) ;
Assert . Equal ( 5000.0 m , invoice . Price ) ;
2017-10-27 10:53:04 +02:00
Assert . Equal ( Money . Coins ( 0 ) , invoice . BtcPaid ) ;
Assert . Equal ( "new" , invoice . Status ) ;
2017-12-21 07:52:04 +01:00
Assert . False ( ( bool ) ( ( JValue ) invoice . ExceptionStatus ) . Value ) ;
2017-10-27 10:53:04 +02:00
2018-02-16 17:34:40 +01:00
Assert . Single ( user . BitPay . GetInvoices ( invoice . InvoiceTime . UtcDateTime ) ) ;
Assert . Empty ( user . BitPay . GetInvoices ( invoice . InvoiceTime . UtcDateTime + TimeSpan . FromDays ( 2 ) ) ) ;
Assert . Single ( user . BitPay . GetInvoices ( invoice . InvoiceTime . UtcDateTime - TimeSpan . FromDays ( 5 ) ) ) ;
2020-04-05 13:48:00 +02:00
Assert . Single ( user . BitPay . GetInvoices ( invoice . InvoiceTime . UtcDateTime - TimeSpan . FromDays ( 5 ) ,
invoice . InvoiceTime . DateTime + TimeSpan . FromDays ( 1.0 ) ) ) ;
Assert . Empty ( user . BitPay . GetInvoices ( invoice . InvoiceTime . UtcDateTime - TimeSpan . FromDays ( 5 ) ,
invoice . InvoiceTime . DateTime - TimeSpan . FromDays ( 1 ) ) ) ;
2017-10-27 10:53:04 +02:00
var firstPayment = Money . Coins ( 0.04 m ) ;
var txFee = Money . Zero ;
var cashCow = tester . ExplorerNode ;
var invoiceAddress = BitcoinAddress . Create ( invoice . BitcoinAddress , cashCow . Network ) ;
Assert . True ( IsMapped ( invoice , ctx ) ) ;
cashCow . SendToAddress ( invoiceAddress , firstPayment ) ;
2018-12-06 09:05:27 +01:00
var invoiceEntity = repo . GetInvoice ( invoice . Id , true ) . GetAwaiter ( ) . GetResult ( ) ;
2017-12-21 07:52:04 +01:00
Assert . Single ( invoiceEntity . HistoricalAddresses ) ;
2017-10-27 10:53:04 +02:00
Assert . Null ( invoiceEntity . HistoricalAddresses [ 0 ] . UnAssigned ) ;
Money secondPayment = Money . Zero ;
2019-01-05 19:47:39 +01:00
TestUtils . Eventually ( ( ) = >
2017-10-27 10:53:04 +02:00
{
var localInvoice = user . BitPay . GetInvoice ( invoice . Id , Facade . Merchant ) ;
Assert . Equal ( "new" , localInvoice . Status ) ;
Assert . Equal ( firstPayment , localInvoice . BtcPaid ) ;
txFee = localInvoice . BtcDue - invoice . BtcDue ;
2017-12-21 07:52:04 +01:00
Assert . Equal ( "paidPartial" , localInvoice . ExceptionStatus . ToString ( ) ) ;
2018-02-25 16:48:12 +01:00
Assert . Equal ( 1 , localInvoice . CryptoInfo [ 0 ] . TxCount ) ;
2017-10-27 10:53:04 +02:00
Assert . NotEqual ( localInvoice . BitcoinAddress , invoice . BitcoinAddress ) ; //New address
Assert . True ( IsMapped ( invoice , ctx ) ) ;
Assert . True ( IsMapped ( localInvoice , ctx ) ) ;
2018-12-06 09:05:27 +01:00
invoiceEntity = repo . GetInvoice ( invoice . Id , true ) . GetAwaiter ( ) . GetResult ( ) ;
2020-04-05 13:48:00 +02:00
var historical1 =
invoiceEntity . HistoricalAddresses . FirstOrDefault ( h = > h . GetAddress ( ) = = invoice . BitcoinAddress ) ;
2017-10-27 10:53:04 +02:00
Assert . NotNull ( historical1 . UnAssigned ) ;
2020-04-05 13:48:00 +02:00
var historical2 =
invoiceEntity . HistoricalAddresses . FirstOrDefault ( h = >
h . GetAddress ( ) = = localInvoice . BitcoinAddress ) ;
2017-10-27 10:53:04 +02:00
Assert . Null ( historical2 . UnAssigned ) ;
invoiceAddress = BitcoinAddress . Create ( localInvoice . BitcoinAddress , cashCow . Network ) ;
secondPayment = localInvoice . BtcDue ;
} ) ;
cashCow . SendToAddress ( invoiceAddress , secondPayment ) ;
2019-01-05 19:47:39 +01:00
TestUtils . Eventually ( ( ) = >
2017-10-27 10:53:04 +02:00
{
var localInvoice = user . BitPay . GetInvoice ( invoice . Id , Facade . Merchant ) ;
Assert . Equal ( "paid" , localInvoice . Status ) ;
2018-02-25 16:48:12 +01:00
Assert . Equal ( 2 , localInvoice . CryptoInfo [ 0 ] . TxCount ) ;
2017-10-27 10:53:04 +02:00
Assert . Equal ( firstPayment + secondPayment , localInvoice . BtcPaid ) ;
Assert . Equal ( Money . Zero , localInvoice . BtcDue ) ;
Assert . Equal ( localInvoice . BitcoinAddress , invoiceAddress . ToString ( ) ) ; //no new address generated
Assert . True ( IsMapped ( localInvoice , ctx ) ) ;
2017-12-18 00:56:27 +01:00
Assert . False ( ( bool ) ( ( JValue ) localInvoice . ExceptionStatus ) . Value ) ;
2017-10-27 10:53:04 +02:00
} ) ;
cashCow . Generate ( 1 ) ; //The user has medium speed settings, so 1 conf is enough to be confirmed
2019-01-05 19:47:39 +01:00
TestUtils . Eventually ( ( ) = >
2017-10-27 10:53:04 +02:00
{
var localInvoice = user . BitPay . GetInvoice ( invoice . Id , Facade . Merchant ) ;
Assert . Equal ( "confirmed" , localInvoice . Status ) ;
} ) ;
cashCow . Generate ( 5 ) ; //Now should be complete
2019-01-05 19:47:39 +01:00
TestUtils . Eventually ( ( ) = >
2017-10-27 10:53:04 +02:00
{
var localInvoice = user . BitPay . GetInvoice ( invoice . Id , Facade . Merchant ) ;
Assert . Equal ( "complete" , localInvoice . Status ) ;
2018-05-11 15:38:31 +02:00
Assert . NotEqual ( 0.0 m , localInvoice . Rate ) ;
2017-10-27 10:53:04 +02:00
} ) ;
invoice = user . BitPay . CreateInvoice ( new Invoice ( )
{
2018-05-11 15:38:31 +02:00
Price = 5000.0 m ,
2017-10-27 10:53:04 +02:00
Currency = "USD" ,
PosData = "posData" ,
OrderId = "orderId" ,
//RedirectURL = redirect + "redirect",
//NotificationURL = CallbackUri + "/notification",
ItemDesc = "Some description" ,
FullNotifications = true
} , Facade . Merchant ) ;
invoiceAddress = BitcoinAddress . Create ( invoice . BitcoinAddress , cashCow . Network ) ;
2018-11-02 06:26:13 +01:00
var txId = cashCow . SendToAddress ( invoiceAddress , invoice . BtcDue + Money . Coins ( 1 ) ) ;
2017-10-27 10:53:04 +02:00
2019-01-05 19:47:39 +01:00
TestUtils . Eventually ( ( ) = >
2017-10-27 10:53:04 +02:00
{
var localInvoice = user . BitPay . GetInvoice ( invoice . Id , Facade . Merchant ) ;
Assert . Equal ( "paid" , localInvoice . Status ) ;
Assert . Equal ( Money . Zero , localInvoice . BtcDue ) ;
Assert . Equal ( "paidOver" , ( string ) ( ( JValue ) localInvoice . ExceptionStatus ) . Value ) ;
2018-11-02 06:26:13 +01:00
var textSearchResult = tester . PayTester . InvoiceRepository . GetInvoices ( new InvoiceQuery ( )
{
2020-06-24 03:34:09 +02:00
StoreId = new [ ] { user . StoreId } ,
TextSearch = txId . ToString ( )
2018-11-02 06:26:13 +01:00
} ) . GetAwaiter ( ) . GetResult ( ) ;
Assert . Single ( textSearchResult ) ;
2017-10-27 10:53:04 +02:00
} ) ;
cashCow . Generate ( 1 ) ;
2019-01-05 19:47:39 +01:00
TestUtils . Eventually ( ( ) = >
2017-10-27 10:53:04 +02:00
{
var localInvoice = user . BitPay . GetInvoice ( invoice . Id , Facade . Merchant ) ;
Assert . Equal ( "confirmed" , localInvoice . Status ) ;
Assert . Equal ( Money . Zero , localInvoice . BtcDue ) ;
Assert . Equal ( "paidOver" , ( string ) ( ( JValue ) localInvoice . ExceptionStatus ) . Value ) ;
} ) ;
2020-11-13 08:28:15 +01:00
// Test on the webhooks
2020-11-23 07:57:05 +01:00
user . AssertHasWebhookEvent < WebhookInvoiceSettledEvent > ( WebhookEventType . InvoiceSettled ,
2020-11-13 08:28:15 +01:00
c = >
{
Assert . False ( c . ManuallyMarked ) ;
} ) ;
2020-11-23 07:57:05 +01:00
user . AssertHasWebhookEvent < WebhookInvoiceProcessingEvent > ( WebhookEventType . InvoiceProcessing ,
2020-11-13 08:28:15 +01:00
c = >
{
Assert . True ( c . OverPaid ) ;
} ) ;
user . AssertHasWebhookEvent < WebhookInvoiceReceivedPaymentEvent > ( WebhookEventType . InvoiceReceivedPayment ,
c = >
{
Assert . False ( c . AfterExpiration ) ;
2021-10-05 11:10:41 +02:00
Assert . Equal ( new PaymentMethodId ( "BTC" , PaymentTypes . BTCLike ) . ToStringNormalized ( ) , c . PaymentMethod ) ;
Assert . NotNull ( c . Payment ) ;
Assert . Equal ( invoice . BitcoinAddress , c . Payment . Destination ) ;
Assert . StartsWith ( txId . ToString ( ) , c . Payment . Id ) ;
} ) ;
user . AssertHasWebhookEvent < WebhookInvoicePaymentSettledEvent > ( WebhookEventType . InvoicePaymentSettled ,
c = >
{
Assert . False ( c . AfterExpiration ) ;
Assert . Equal ( new PaymentMethodId ( "BTC" , PaymentTypes . BTCLike ) . ToStringNormalized ( ) , c . PaymentMethod ) ;
Assert . NotNull ( c . Payment ) ;
Assert . Equal ( invoice . BitcoinAddress , c . Payment . Destination ) ;
Assert . StartsWith ( txId . ToString ( ) , c . Payment . Id ) ;
2020-11-13 08:28:15 +01:00
} ) ;
2017-10-27 10:53:04 +02:00
}
}
2021-04-18 04:26:06 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
2018-11-07 14:29:35 +01:00
[Trait("Integration", "Integration")]
public async Task CheckLogsRoute ( )
{
2021-11-22 09:16:08 +01:00
using ( var tester = CreateServerTester ( ) )
2018-11-07 14:29:35 +01:00
{
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2018-11-07 14:29:35 +01:00
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
user . RegisterDerivationScheme ( "BTC" ) ;
2018-11-30 09:34:43 +01:00
2018-11-07 14:29:35 +01:00
var serverController = user . GetController < ServerController > ( ) ;
2020-04-05 13:48:00 +02:00
var vm = Assert . IsType < LogsViewModel > (
Assert . IsType < ViewResult > ( await serverController . LogsView ( ) ) . Model ) ;
2018-11-07 14:29:35 +01:00
}
2018-11-30 09:34:43 +01:00
}
2021-04-18 04:26:06 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
2019-05-02 14:01:08 +02:00
[Trait("Integration", "Integration")]
public async Task CanLoginWithNoSecondaryAuthSystemsOrRequestItWhenAdded ( )
{
2021-11-22 09:16:08 +01:00
using ( var tester = CreateServerTester ( ) )
2019-05-02 14:01:08 +02:00
{
2019-10-07 09:04:25 +02:00
await tester . StartAsync ( ) ;
2019-05-02 14:01:08 +02:00
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
2020-04-05 13:48:00 +02:00
var accountController = tester . PayTester . GetController < AccountController > ( ) ;
2019-05-02 14:01:08 +02:00
2021-04-28 06:14:15 +02:00
//no 2fa or fido2 enabled, login should work
2020-04-05 13:48:00 +02:00
Assert . Equal ( nameof ( HomeController . Index ) ,
Assert . IsType < RedirectToActionResult > ( await accountController . Login ( new LoginViewModel ( )
{
2020-06-24 03:34:09 +02:00
Email = user . RegisterDetails . Email ,
Password = user . RegisterDetails . Password
2020-04-05 13:48:00 +02:00
} ) ) . ActionName ) ;
2019-05-02 14:01:08 +02:00
2021-09-13 03:16:52 +02:00
var listController = user . GetController < ManageController > ( ) ;
2021-04-28 06:14:15 +02:00
var manageController = user . GetController < Fido2Controller > ( ) ;
2020-02-13 06:44:31 +01:00
2021-04-28 06:14:15 +02:00
//by default no fido2 devices available
2020-04-05 13:48:00 +02:00
Assert . Empty ( Assert
2021-09-13 03:16:52 +02:00
. IsType < TwoFactorAuthenticationViewModel > ( Assert
. IsType < ViewResult > ( await listController . TwoFactorAuthentication ( ) ) . Model ) . Credentials ) ;
2021-04-28 06:14:15 +02:00
Assert . IsType < CredentialCreateOptions > ( Assert
2021-09-13 03:16:52 +02:00
. IsType < ViewResult > ( await manageController . Create ( new AddFido2CredentialViewModel
2021-04-28 06:14:15 +02:00
{
Name = "label"
} ) ) . Model ) ;
2019-11-07 10:35:47 +01:00
//sending an invalid response model back to server, should error out
2021-04-28 06:14:15 +02:00
Assert . IsType < RedirectToActionResult > ( await manageController . CreateResponse ( "sdsdsa" , "sds" ) ) ;
2019-11-07 10:35:47 +01:00
var statusModel = manageController . TempData . GetStatusMessageModel ( ) ;
2020-04-05 13:48:00 +02:00
Assert . Equal ( StatusMessageModel . StatusSeverity . Error , statusModel . Severity ) ;
var contextFactory = tester . PayTester . GetService < ApplicationDbContextFactory > ( ) ;
2021-04-28 06:14:15 +02:00
//add a fake fido2 device in db directly since emulating a fido2 device is hard and annoying
2020-04-05 13:48:00 +02:00
using ( var context = contextFactory . CreateContext ( ) )
{
2021-04-28 06:14:15 +02:00
var newDevice = new Fido2Credential ( )
2020-04-05 13:48:00 +02:00
{
Id = Guid . NewGuid ( ) . ToString ( ) ,
Name = "fake" ,
2021-04-28 06:14:15 +02:00
Type = Fido2Credential . CredentialType . FIDO2 ,
2020-04-05 13:48:00 +02:00
ApplicationUserId = user . UserId
} ;
2021-04-28 06:14:15 +02:00
newDevice . SetBlob ( new Fido2CredentialBlob ( ) { } ) ;
await context . Fido2Credentials . AddAsync ( newDevice ) ;
2020-04-05 13:48:00 +02:00
await context . SaveChangesAsync ( ) ;
Assert . NotNull ( newDevice . Id ) ;
Assert . NotEmpty ( Assert
2021-09-13 03:16:52 +02:00
. IsType < TwoFactorAuthenticationViewModel > ( Assert
. IsType < ViewResult > ( await listController . TwoFactorAuthentication ( ) ) . Model ) . Credentials ) ;
2020-04-05 13:48:00 +02:00
}
2021-04-28 06:14:15 +02:00
//check if we are showing the fido2 login screen now
2020-04-05 13:48:00 +02:00
var secondLoginResult = Assert . IsType < ViewResult > ( await accountController . Login ( new LoginViewModel ( )
{
2020-06-24 03:34:09 +02:00
Email = user . RegisterDetails . Email ,
Password = user . RegisterDetails . Password
2020-04-05 13:48:00 +02:00
} ) ) ;
Assert . Equal ( "SecondaryLogin" , secondLoginResult . ViewName ) ;
var vm = Assert . IsType < SecondaryLoginViewModel > ( secondLoginResult . Model ) ;
//2fa was never enabled for user so this should be empty
Assert . Null ( vm . LoginWith2FaViewModel ) ;
2021-04-28 06:14:15 +02:00
Assert . NotNull ( vm . LoginWithFido2ViewModel ) ;
2019-05-02 14:01:08 +02:00
}
}
2020-02-13 06:44:31 +01:00
2021-04-18 04:26:06 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
2020-06-04 08:53:55 +02:00
[Trait("Integration", "Integration")]
public async void CheckOnionlocationForNonOnionHtmlRequests ( )
{
2021-11-22 09:16:08 +01:00
using ( var tester = CreateServerTester ( ) )
2020-06-04 08:53:55 +02:00
{
await tester . StartAsync ( ) ;
var url = tester . PayTester . ServerUri . AbsoluteUri ;
// check onion location is present for HTML page request
using var htmlRequest = new HttpRequestMessage ( HttpMethod . Get , new Uri ( url ) ) ;
htmlRequest . Headers . TryAddWithoutValidation ( "Accept" , "text/html,*/*" ) ;
2020-06-05 11:38:55 +02:00
var htmlResponse = await tester . PayTester . HttpClient . SendAsync ( htmlRequest ) ;
2020-06-04 08:53:55 +02:00
htmlResponse . EnsureSuccessStatusCode ( ) ;
Assert . True ( htmlResponse . Headers . TryGetValues ( "Onion-Location" , out var onionLocation ) ) ;
Assert . StartsWith ( "http://wsaxew3qa5ljfuenfebmaf3m5ykgatct3p6zjrqwoouj3foererde3id.onion" , onionLocation . FirstOrDefault ( ) ? ? "no-onion-location-header" ) ;
// no onion location for other mime types
using var otherRequest = new HttpRequestMessage ( HttpMethod . Get , new Uri ( url ) ) ;
otherRequest . Headers . TryAddWithoutValidation ( "Accept" , "*/*" ) ;
2020-06-05 11:38:55 +02:00
var otherResponse = await tester . PayTester . HttpClient . SendAsync ( otherRequest ) ;
2020-06-04 08:53:55 +02:00
otherResponse . EnsureSuccessStatusCode ( ) ;
Assert . False ( otherResponse . Headers . Contains ( "Onion-Location" ) ) ;
}
}
2017-10-27 10:53:04 +02:00
private static bool IsMapped ( Invoice invoice , ApplicationDbContext ctx )
{
2018-04-10 12:07:57 +02:00
var h = BitcoinAddress . Create ( invoice . BitcoinAddress , Network . RegTest ) . ScriptPubKey . Hash . ToString ( ) ;
2020-04-05 13:48:00 +02:00
return ( ctx . AddressInvoices . Where ( i = > i . InvoiceDataId = = invoice . Id ) . ToArrayAsync ( ) . GetAwaiter ( )
. GetResult ( ) )
2019-10-06 08:47:46 +02:00
. Where ( i = > i . GetAddress ( ) = = h ) . Any ( ) ;
2017-10-27 10:53:04 +02:00
}
2020-07-31 03:52:33 +02:00
class MockVersionFetcher : IVersionFetcher
{
public const string MOCK_NEW_VERSION = "9.9.9.9" ;
public Task < string > Fetch ( CancellationToken cancellation )
{
return Task . FromResult ( MOCK_NEW_VERSION ) ;
}
}
2021-04-18 04:26:06 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
2020-07-31 03:52:33 +02:00
[Trait("Integration", "Integration")]
public async Task CanCheckForNewVersion ( )
{
2021-11-22 09:16:08 +01:00
using ( var tester = CreateServerTester ( newDb : true ) )
2020-07-31 03:52:33 +02:00
{
await tester . StartAsync ( ) ;
var acc = tester . NewAccount ( ) ;
acc . GrantAccess ( true ) ;
var settings = tester . PayTester . GetService < SettingsRepository > ( ) ;
await settings . UpdateSetting < PoliciesSettings > ( new PoliciesSettings ( ) { CheckForNewVersions = true } ) ;
2020-07-31 07:20:17 +02:00
var mockEnv = tester . PayTester . GetService < BTCPayServerEnvironment > ( ) ;
var mockSender = tester . PayTester . GetService < Services . Notifications . NotificationSender > ( ) ;
2020-07-31 03:52:33 +02:00
2021-11-22 09:16:08 +01:00
var svc = new NewVersionCheckerHostedService ( settings , mockEnv , mockSender , new MockVersionFetcher ( ) , BTCPayLogs ) ;
2020-07-31 03:52:33 +02:00
await svc . ProcessVersionCheck ( ) ;
// since last version present in database was null, it should've been updated with version mock returned
var lastVersion = await settings . GetSettingAsync < NewVersionCheckerDataHolder > ( ) ;
Assert . Equal ( MockVersionFetcher . MOCK_NEW_VERSION , lastVersion . LastVersion ) ;
// we should also have notification in UI
var ctrl = acc . GetController < NotificationsController > ( ) ;
var newVersion = MockVersionFetcher . MOCK_NEW_VERSION ;
var vm = Assert . IsType < Models . NotificationViewModels . IndexViewModel > (
2020-12-11 15:11:08 +01:00
Assert . IsType < ViewResult > ( await ctrl . Index ( ) ) . Model ) ;
2020-07-31 03:52:33 +02:00
Assert . True ( vm . Skip = = 0 ) ;
Assert . True ( vm . Count = = 50 ) ;
Assert . True ( vm . Total = = 1 ) ;
Assert . True ( vm . Items . Count = = 1 ) ;
var fn = vm . Items . First ( ) ;
var now = DateTimeOffset . UtcNow ;
Assert . True ( fn . Created > = now . AddSeconds ( - 3 ) ) ;
Assert . True ( fn . Created < = now ) ;
Assert . Equal ( $"New version {newVersion} released!" , fn . Body ) ;
Assert . Equal ( $"https://github.com/btcpayserver/btcpayserver/releases/tag/v{newVersion}" , fn . ActionLink ) ;
Assert . False ( fn . Seen ) ;
}
}
2021-03-02 03:11:58 +01:00
2021-04-18 04:26:06 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
2021-03-02 03:11:58 +01:00
[Trait("Integration", "Integration")]
public async Task CanDoLightningInternalNodeMigration ( )
{
2021-11-22 09:16:08 +01:00
using ( var tester = CreateServerTester ( newDb : true ) )
2021-03-02 03:11:58 +01:00
{
tester . ActivateLightning ( LightningConnectionType . CLightning ) ;
await tester . StartAsync ( ) ;
var acc = tester . NewAccount ( ) ;
await acc . GrantAccessAsync ( true ) ;
await acc . CreateStoreAsync ( ) ;
// Test if legacy DerivationStrategy column is converted to DerivationStrategies
var store = await tester . PayTester . StoreRepository . FindStore ( acc . StoreId ) ;
var xpub = "tpubDDmH1briYfZcTDMEc7uMEA5hinzjUTzR9yMC1drxTMeiWyw1VyCqTuzBke6df2sqbfw9QG6wbgTLF5yLjcXsZNaXvJMZLwNEwyvmiFWcLav" ;
var derivation = $"{xpub}-[legacy]" ;
store . DerivationStrategy = derivation ;
await tester . PayTester . StoreRepository . UpdateStore ( store ) ;
await RestartMigration ( tester ) ;
store = await tester . PayTester . StoreRepository . FindStore ( acc . StoreId ) ;
Assert . True ( string . IsNullOrEmpty ( store . DerivationStrategy ) ) ;
var v = ( DerivationSchemeSettings ) store . GetSupportedPaymentMethods ( tester . NetworkProvider ) . First ( ) ;
Assert . Equal ( derivation , v . AccountDerivation . ToString ( ) ) ;
Assert . Equal ( derivation , v . AccountOriginal . ToString ( ) ) ;
Assert . Equal ( xpub , v . SigningKey . ToString ( ) ) ;
Assert . Equal ( xpub , v . GetSigningAccountKeySettings ( ) . AccountKey . ToString ( ) ) ;
await acc . RegisterLightningNodeAsync ( "BTC" , LightningConnectionType . CLightning , true ) ;
store = await tester . PayTester . StoreRepository . FindStore ( acc . StoreId ) ;
var lnMethod = store . GetSupportedPaymentMethods ( tester . NetworkProvider ) . OfType < LightningSupportedPaymentMethod > ( ) . First ( ) ;
Assert . NotNull ( lnMethod . GetExternalLightningUrl ( ) ) ;
await RestartMigration ( tester ) ;
store = await tester . PayTester . StoreRepository . FindStore ( acc . StoreId ) ;
lnMethod = store . GetSupportedPaymentMethods ( tester . NetworkProvider ) . OfType < LightningSupportedPaymentMethod > ( ) . First ( ) ;
Assert . Null ( lnMethod . GetExternalLightningUrl ( ) ) ;
// Test if legacy lightning charge settings are converted to LightningConnectionString
store . DerivationStrategies = new JObject ( )
{
new JProperty ( "BTC_LightningLike" , new JObject ( )
{
new JProperty ( "LightningChargeUrl" , "http://mycharge.com/" ) ,
new JProperty ( "Username" , "usr" ) ,
new JProperty ( "Password" , "pass" ) ,
new JProperty ( "CryptoCode" , "BTC" ) ,
new JProperty ( "PaymentId" , "someshit" ) ,
} )
} . ToString ( ) ;
await tester . PayTester . StoreRepository . UpdateStore ( store ) ;
await RestartMigration ( tester ) ;
store = await tester . PayTester . StoreRepository . FindStore ( acc . StoreId ) ;
lnMethod = store . GetSupportedPaymentMethods ( tester . NetworkProvider ) . OfType < LightningSupportedPaymentMethod > ( ) . First ( ) ;
Assert . NotNull ( lnMethod . GetExternalLightningUrl ( ) ) ;
var url = lnMethod . GetExternalLightningUrl ( ) ;
Assert . Equal ( LightningConnectionType . Charge , url . ConnectionType ) ;
Assert . Equal ( "pass" , url . Password ) ;
Assert . Equal ( "usr" , url . Username ) ;
// Test if lightning connection strings get migrated to internal
store . DerivationStrategies = new JObject ( )
{
new JProperty ( "BTC_LightningLike" , new JObject ( )
{
new JProperty ( "CryptoCode" , "BTC" ) ,
new JProperty ( "LightningConnectionString" , tester . PayTester . IntegratedLightning ) ,
} )
} . ToString ( ) ;
await tester . PayTester . StoreRepository . UpdateStore ( store ) ;
await RestartMigration ( tester ) ;
store = await tester . PayTester . StoreRepository . FindStore ( acc . StoreId ) ;
lnMethod = store . GetSupportedPaymentMethods ( tester . NetworkProvider ) . OfType < LightningSupportedPaymentMethod > ( ) . First ( ) ;
Assert . True ( lnMethod . IsInternalNode ) ;
}
}
2021-04-18 04:26:06 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
2020-12-29 09:58:35 +01:00
[Trait("Integration", "Integration")]
public async Task CanDoInvoiceMigrations ( )
{
2021-11-22 09:16:08 +01:00
using ( var tester = CreateServerTester ( newDb : true ) )
2020-12-29 09:58:35 +01:00
{
await tester . StartAsync ( ) ;
var acc = tester . NewAccount ( ) ;
await acc . GrantAccessAsync ( true ) ;
await acc . CreateStoreAsync ( ) ;
await acc . RegisterDerivationSchemeAsync ( "BTC" ) ;
var store = await tester . PayTester . StoreRepository . FindStore ( acc . StoreId ) ;
2021-03-02 03:11:58 +01:00
2020-12-29 09:58:35 +01:00
var blob = store . GetStoreBlob ( ) ;
var serializer = new Serializer ( null ) ;
blob . AdditionalData = new Dictionary < string , JToken > ( ) ;
blob . AdditionalData . Add ( "rateRules" , JToken . Parse (
serializer . ToString ( new List < MigrationStartupTask . RateRule_Obsolete > ( )
{
new MigrationStartupTask . RateRule_Obsolete ( )
{
Multiplier = 2
}
} ) ) ) ;
blob . AdditionalData . Add ( "walletKeyPathRoots" , JToken . Parse (
serializer . ToString ( new Dictionary < string , string > ( )
{
{
new PaymentMethodId ( "BTC" , BitcoinPaymentType . Instance ) . ToString ( ) ,
new KeyPath ( "44'/0'/0'" ) . ToString ( )
}
} ) ) ) ;
2021-03-02 03:11:58 +01:00
2020-12-29 09:58:35 +01:00
blob . AdditionalData . Add ( "networkFeeDisabled" , JToken . Parse (
serializer . ToString ( ( bool? ) true ) ) ) ;
2021-03-02 03:11:58 +01:00
2020-12-29 09:58:35 +01:00
blob . AdditionalData . Add ( "onChainMinValue" , JToken . Parse (
serializer . ToString ( new CurrencyValue ( )
{
Currency = "USD" ,
Value = 5 m
} . ToString ( ) ) ) ) ;
blob . AdditionalData . Add ( "lightningMaxValue" , JToken . Parse (
serializer . ToString ( new CurrencyValue ( )
{
Currency = "USD" ,
Value = 5 m
} . ToString ( ) ) ) ) ;
2021-03-02 03:11:58 +01:00
2020-12-29 09:58:35 +01:00
store . SetStoreBlob ( blob ) ;
await tester . PayTester . StoreRepository . UpdateStore ( store ) ;
2021-03-02 03:11:58 +01:00
await RestartMigration ( tester ) ;
2020-12-29 09:58:35 +01:00
store = await tester . PayTester . StoreRepository . FindStore ( acc . StoreId ) ;
2021-03-02 03:11:58 +01:00
2020-12-29 09:58:35 +01:00
blob = store . GetStoreBlob ( ) ;
Assert . Empty ( blob . AdditionalData ) ;
Assert . Single ( blob . PaymentMethodCriteria ) ;
Assert . Contains ( blob . PaymentMethodCriteria ,
criteria = > criteria . PaymentMethod = = new PaymentMethodId ( "BTC" , BitcoinPaymentType . Instance ) & &
criteria . Above & & criteria . Value . Value = = 5 m & & criteria . Value . Currency = = "USD" ) ;
Assert . Equal ( NetworkFeeMode . Never , blob . NetworkFeeMode ) ;
Assert . Contains ( store . GetSupportedPaymentMethods ( tester . NetworkProvider ) , method = >
method is DerivationSchemeSettings dss & &
method . PaymentId = = new PaymentMethodId ( "BTC" , BitcoinPaymentType . Instance ) & &
dss . AccountKeyPath = = new KeyPath ( "44'/0'/0'" ) ) ;
}
}
2021-03-02 03:11:58 +01:00
private static async Task RestartMigration ( ServerTester tester )
{
var settings = tester . PayTester . GetService < SettingsRepository > ( ) ;
await settings . UpdateSetting < MigrationSettings > ( new MigrationSettings ( ) ) ;
var migrationStartupTask = tester . PayTester . GetService < IServiceProvider > ( ) . GetServices < IStartupTask > ( )
. Single ( task = > task is MigrationStartupTask ) ;
await migrationStartupTask . ExecuteAsync ( ) ;
}
2021-04-18 04:26:06 +02:00
[Fact(Timeout = LongRunningTestTimeout)]
2020-12-08 08:12:29 +01:00
[Trait("Integration", "Integration")]
public async Task EmailSenderTests ( )
{
2021-11-22 09:16:08 +01:00
using ( var tester = CreateServerTester ( newDb : true ) )
2020-12-08 08:12:29 +01:00
{
await tester . StartAsync ( ) ;
var acc = tester . NewAccount ( ) ;
acc . GrantAccess ( true ) ;
var settings = tester . PayTester . GetService < SettingsRepository > ( ) ;
var emailSenderFactory = tester . PayTester . GetService < EmailSenderFactory > ( ) ;
2021-07-27 14:08:54 +02:00
Assert . Null ( await Assert . IsType < ServerEmailSender > ( await emailSenderFactory . GetEmailSender ( ) ) . GetEmailSettings ( ) ) ;
Assert . Null ( await Assert . IsType < StoreEmailSender > ( await emailSenderFactory . GetEmailSender ( acc . StoreId ) ) . GetEmailSettings ( ) ) ;
2020-12-08 08:12:29 +01:00
await settings . UpdateSetting ( new PoliciesSettings ( ) { DisableStoresToUseServerEmailSettings = false } ) ;
await settings . UpdateSetting ( new EmailSettings ( )
{
From = "admin@admin.com" ,
Login = "admin@admin.com" ,
Password = "admin@admin.com" ,
Port = 1234 ,
Server = "admin.com" ,
} ) ;
2021-07-27 14:08:54 +02:00
Assert . Equal ( "admin@admin.com" , ( await Assert . IsType < ServerEmailSender > ( await emailSenderFactory . GetEmailSender ( ) ) . GetEmailSettings ( ) ) . Login ) ;
Assert . Equal ( "admin@admin.com" , ( await Assert . IsType < StoreEmailSender > ( await emailSenderFactory . GetEmailSender ( acc . StoreId ) ) . GetEmailSettings ( ) ) . Login ) ;
2020-12-08 08:12:29 +01:00
await settings . UpdateSetting ( new PoliciesSettings ( ) { DisableStoresToUseServerEmailSettings = true } ) ;
2021-07-27 14:08:54 +02:00
Assert . Equal ( "admin@admin.com" , ( await Assert . IsType < ServerEmailSender > ( await emailSenderFactory . GetEmailSender ( ) ) . GetEmailSettings ( ) ) . Login ) ;
Assert . Null ( await Assert . IsType < StoreEmailSender > ( await emailSenderFactory . GetEmailSender ( acc . StoreId ) ) . GetEmailSettings ( ) ) ;
2020-12-08 08:12:29 +01:00
Assert . IsType < RedirectToActionResult > ( await acc . GetController < StoresController > ( ) . Emails ( acc . StoreId , new EmailsViewModel ( new EmailSettings ( )
{
From = "store@store.com" ,
Login = "store@store.com" ,
Password = "store@store.com" ,
Port = 1234 ,
2021-12-15 13:30:46 +01:00
Server = "store.com"
2020-12-08 08:12:29 +01:00
} ) , "" ) ) ;
2021-07-27 14:08:54 +02:00
Assert . Equal ( "store@store.com" , ( await Assert . IsType < StoreEmailSender > ( await emailSenderFactory . GetEmailSender ( acc . StoreId ) ) . GetEmailSettings ( ) ) . Login ) ;
2020-12-08 08:12:29 +01:00
}
}
2021-11-23 04:53:05 +01:00
[Fact(Timeout = TestUtils.TestTimeout)]
[Trait("Integration", "Integration")]
public async Task CanConfigureStorage ( )
{
2021-11-22 09:16:08 +01:00
using ( var tester = CreateServerTester ( ) )
2021-11-23 04:53:05 +01:00
{
await tester . StartAsync ( ) ;
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
var controller = tester . PayTester . GetController < ServerController > ( user . UserId , user . StoreId ) ;
//Once we select a provider, redirect to its view
var localResult = Assert
. IsType < RedirectToActionResult > ( controller . Storage ( new StorageSettings ( )
{
Provider = StorageProvider . FileSystem
} ) ) ;
Assert . Equal ( nameof ( ServerController . StorageProvider ) , localResult . ActionName ) ;
Assert . Equal ( StorageProvider . FileSystem . ToString ( ) , localResult . RouteValues [ "provider" ] ) ;
var AmazonS3result = Assert
. IsType < RedirectToActionResult > ( controller . Storage ( new StorageSettings ( )
{
Provider = StorageProvider . AmazonS3
} ) ) ;
Assert . Equal ( nameof ( ServerController . StorageProvider ) , AmazonS3result . ActionName ) ;
Assert . Equal ( StorageProvider . AmazonS3 . ToString ( ) , AmazonS3result . RouteValues [ "provider" ] ) ;
var GoogleResult = Assert
. IsType < RedirectToActionResult > ( controller . Storage ( new StorageSettings ( )
{
Provider = StorageProvider . GoogleCloudStorage
} ) ) ;
Assert . Equal ( nameof ( ServerController . StorageProvider ) , GoogleResult . ActionName ) ;
Assert . Equal ( StorageProvider . GoogleCloudStorage . ToString ( ) , GoogleResult . RouteValues [ "provider" ] ) ;
var AzureResult = Assert
. IsType < RedirectToActionResult > ( controller . Storage ( new StorageSettings ( )
{
Provider = StorageProvider . AzureBlobStorage
} ) ) ;
Assert . Equal ( nameof ( ServerController . StorageProvider ) , AzureResult . ActionName ) ;
Assert . Equal ( StorageProvider . AzureBlobStorage . ToString ( ) , AzureResult . RouteValues [ "provider" ] ) ;
//Cool, we get redirected to the config pages
//Let's configure this stuff
//Let's try and cheat and go to an invalid storage provider config
Assert . Equal ( nameof ( Storage ) , ( Assert
. IsType < RedirectToActionResult > ( await controller . StorageProvider ( "I am not a real provider" ) )
. ActionName ) ) ;
//ok no more messing around, let's configure this shit.
var fileSystemStorageConfiguration = Assert . IsType < FileSystemStorageConfiguration > ( Assert
. IsType < ViewResult > ( await controller . StorageProvider ( StorageProvider . FileSystem . ToString ( ) ) )
. Model ) ;
//local file system does not need config, easy days!
Assert . IsType < ViewResult > (
await controller . EditFileSystemStorageProvider ( fileSystemStorageConfiguration ) ) ;
//ok cool, let's see if this got set right
var shouldBeRedirectingToLocalStorageConfigPage =
Assert . IsType < RedirectToActionResult > ( await controller . Storage ( ) ) ;
Assert . Equal ( nameof ( StorageProvider ) , shouldBeRedirectingToLocalStorageConfigPage . ActionName ) ;
Assert . Equal ( StorageProvider . FileSystem ,
shouldBeRedirectingToLocalStorageConfigPage . RouteValues [ "provider" ] ) ;
//if we tell the settings page to force, it should allow us to select a new provider
Assert . IsType < ChooseStorageViewModel > ( Assert . IsType < ViewResult > ( await controller . Storage ( true ) ) . Model ) ;
//awesome, now let's see if the files result says we're all set up
var viewFilesViewModel =
Assert . IsType < ViewFilesViewModel > ( Assert . IsType < ViewResult > ( await controller . Files ( ) ) . Model ) ;
Assert . True ( viewFilesViewModel . StorageConfigured ) ;
Assert . Empty ( viewFilesViewModel . Files ) ;
}
}
[Fact]
[Trait("Integration", "Integration")]
public async void CanUseLocalProviderFiles ( )
{
2021-11-22 09:16:08 +01:00
using ( var tester = CreateServerTester ( ) )
2021-11-23 04:53:05 +01:00
{
await tester . StartAsync ( ) ;
var user = tester . NewAccount ( ) ;
user . GrantAccess ( ) ;
var controller = tester . PayTester . GetController < ServerController > ( user . UserId , user . StoreId ) ;
var fileSystemStorageConfiguration = Assert . IsType < FileSystemStorageConfiguration > ( Assert
. IsType < ViewResult > ( await controller . StorageProvider ( StorageProvider . FileSystem . ToString ( ) ) )
. Model ) ;
Assert . IsType < ViewResult > (
await controller . EditFileSystemStorageProvider ( fileSystemStorageConfiguration ) ) ;
var shouldBeRedirectingToLocalStorageConfigPage =
Assert . IsType < RedirectToActionResult > ( await controller . Storage ( ) ) ;
Assert . Equal ( nameof ( StorageProvider ) , shouldBeRedirectingToLocalStorageConfigPage . ActionName ) ;
Assert . Equal ( StorageProvider . FileSystem ,
shouldBeRedirectingToLocalStorageConfigPage . RouteValues [ "provider" ] ) ;
await CanUploadRemoveFiles ( controller ) ;
}
}
internal static async Task CanUploadRemoveFiles ( ServerController controller )
{
var fileContent = "content" ;
List < IFormFile > fileList = new List < IFormFile > ( ) ;
fileList . Add ( TestUtils . GetFormFile ( "uploadtestfile1.txt" , fileContent ) ) ;
var uploadFormFileResult = Assert . IsType < RedirectToActionResult > ( await controller . CreateFiles ( fileList ) ) ;
Assert . True ( uploadFormFileResult . RouteValues . ContainsKey ( "fileIds" ) ) ;
string [ ] uploadFileList = ( string [ ] ) uploadFormFileResult . RouteValues [ "fileIds" ] ;
var fileId = uploadFileList [ 0 ] ;
Assert . Equal ( "Files" , uploadFormFileResult . ActionName ) ;
//check if file was uploaded and saved in db
var viewFilesViewModel =
Assert . IsType < ViewFilesViewModel > ( Assert . IsType < ViewResult > ( await controller . Files ( new string [ ] { fileId } ) ) . Model ) ;
Assert . NotEmpty ( viewFilesViewModel . Files ) ;
Assert . True ( viewFilesViewModel . DirectUrlByFiles . ContainsKey ( fileId ) ) ;
Assert . NotEmpty ( viewFilesViewModel . DirectUrlByFiles [ fileId ] ) ;
//verify file is available and the same
var net = new System . Net . WebClient ( ) ;
var data = await net . DownloadStringTaskAsync ( new Uri ( viewFilesViewModel . DirectUrlByFiles [ fileId ] ) ) ;
Assert . Equal ( fileContent , data ) ;
//create a temporary link to file
var tmpLinkGenerate = Assert . IsType < RedirectToActionResult > ( await controller . CreateTemporaryFileUrl ( fileId ,
new ServerController . CreateTemporaryFileUrlViewModel ( )
{
IsDownload = true ,
TimeAmount = 1 ,
TimeType = ServerController . CreateTemporaryFileUrlViewModel . TmpFileTimeType . Minutes
} ) ) ;
var statusMessageModel = controller . TempData . GetStatusMessageModel ( ) ;
Assert . NotNull ( statusMessageModel ) ;
Assert . Equal ( StatusMessageModel . StatusSeverity . Success , statusMessageModel . Severity ) ;
var index = statusMessageModel . Html . IndexOf ( "target='_blank'>" ) ;
var url = statusMessageModel . Html . Substring ( index )
. Replace ( "</a>" , string . Empty )
. Replace ( "target='_blank'>" , string . Empty ) ;
//verify tmpfile is available and the same
data = await net . DownloadStringTaskAsync ( new Uri ( url ) ) ;
Assert . Equal ( fileContent , data ) ;
//delete file
Assert . IsType < RedirectToActionResult > ( await controller . DeleteFile ( fileId ) ) ;
statusMessageModel = controller . TempData . GetStatusMessageModel ( ) ;
Assert . NotNull ( statusMessageModel ) ;
Assert . Equal ( StatusMessageModel . StatusSeverity . Success , statusMessageModel . Severity ) ;
//attempt to fetch deleted file
viewFilesViewModel =
Assert . IsType < ViewFilesViewModel > ( Assert . IsType < ViewResult > ( await controller . Files ( new string [ ] { fileId } ) ) . Model ) ;
Assert . Null ( viewFilesViewModel . DirectUrlByFiles ) ;
}
2017-10-27 10:53:04 +02:00
}
2017-09-13 08:47:34 +02:00
}