Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
gruve-p committed Feb 27, 2024
2 parents f61fb60 + d72b0e4 commit ff84ed5
Show file tree
Hide file tree
Showing 88 changed files with 2,747 additions and 1,329 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -300,3 +300,4 @@ Plugins/packed
BTCPayServer/wwwroot/swagger/v1/openapi.json
BTCPayServer/appsettings.dev.json
BTCPayServer.Tests/monero_wallet
/BTCPayServer.Tests/NewBlocks.bat
2 changes: 1 addition & 1 deletion BTCPayServer.Abstractions/BTCPayServer.Abstractions.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
<None Include="icon.png" Pack="true" PackagePath="\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="HtmlSanitizer" Version="8.0.723" />
<PackageReference Include="HtmlSanitizer" Version="8.0.838" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.1" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.0" />
Expand Down
8 changes: 8 additions & 0 deletions BTCPayServer.Abstractions/Extensions/ViewsRazor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,14 @@ public static string IsActivePage(this ViewDataDictionary viewData, string page,
return categoryAndPageMatch && idMatch ? ActivePageClass : null;
}

public static HtmlString ToBrowserDate(this DateTimeOffset date, string netFormat, string jsDateFormat = "short", string jsTimeFormat = "short")
{
var dateTime = date.ToString("o", CultureInfo.InvariantCulture);
var displayDate = date.ToString(netFormat, CultureInfo.InvariantCulture);
var tooltip = dateTime.Replace("T", " ");
return new HtmlString($"<time datetime=\"{dateTime}\" data-date-style=\"{jsDateFormat}\" data-time-style=\"{jsTimeFormat}\" data-initial=\"localized\" data-bs-toggle=\"tooltip\" data-bs-title=\"{tooltip}\">{displayDate}</time>");
}

public static HtmlString ToBrowserDate(this DateTimeOffset date, DateDisplayFormat format = DateDisplayFormat.Localized)
{
var relative = date.ToTimeAgo();
Expand Down
16 changes: 8 additions & 8 deletions BTCPayServer.Client/Models/WebhookInvoiceEvent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ namespace BTCPayServer.Client.Models
{
public class WebhookPayoutEvent : StoreWebhookEvent
{
public WebhookPayoutEvent(string evtType, string storeId)
public WebhookPayoutEvent(string type, string storeId)
{
if (!evtType.StartsWith("payout", StringComparison.InvariantCultureIgnoreCase))
throw new ArgumentException("Invalid event type", nameof(evtType));
Type = evtType;
if (!type.StartsWith("payout", StringComparison.InvariantCultureIgnoreCase))
throw new ArgumentException("Invalid event type", nameof(type));
Type = type;
StoreId = storeId;
}

Expand All @@ -21,11 +21,11 @@ public WebhookPayoutEvent(string evtType, string storeId)
}
public class WebhookPaymentRequestEvent : StoreWebhookEvent
{
public WebhookPaymentRequestEvent(string evtType, string storeId)
public WebhookPaymentRequestEvent(string type, string storeId)
{
if (!evtType.StartsWith("paymentrequest", StringComparison.InvariantCultureIgnoreCase))
throw new ArgumentException("Invalid event type", nameof(evtType));
Type = evtType;
if (!type.StartsWith("paymentrequest", StringComparison.InvariantCultureIgnoreCase))
throw new ArgumentException("Invalid event type", nameof(type));
Type = type;
StoreId = storeId;
}

Expand Down
1 change: 0 additions & 1 deletion BTCPayServer.Data/Data/InvoiceEventData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ public string GetCssClass()
{
return Severity switch
{
EventSeverity.Info => "info",
EventSeverity.Error => "danger",
EventSeverity.Success => "success",
EventSeverity.Warning => "warning",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using BTCPayServer.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;

#nullable disable

namespace BTCPayServer.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20240220000000_FixWalletObjectsWithEmptyWalletId")]
public partial class FixWalletObjectsWithEmptyWalletId : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
if (migrationBuilder.IsNpgsql())
{
migrationBuilder.Sql("DELETE FROM \"WalletObjects\" WHERE \"WalletId\"='';");
}
}

protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}
56 changes: 32 additions & 24 deletions BTCPayServer.Tests/Checkoutv2Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,14 @@ public async Task CanConfigureCheckout()
var supportUrl = "https://support.satoshisteaks.com/{InvoiceId}/";
s.GoToStore();
s.Driver.FindElement(By.Id("StoreWebsite")).SendKeys(storeUrl);
s.Driver.FindElement(By.Id("StoreSupportUrl")).SendKeys(supportUrl);
s.Driver.FindElement(By.Id("Save")).Click();
Assert.Contains("Store successfully updated", s.FindAlertMessage().Text);

s.GoToStore(StoreNavPages.CheckoutAppearance);
s.Driver.WaitForAndClick(By.Id("Presets"));
s.Driver.WaitForAndClick(By.Id("Presets_InStore"));
Assert.True(s.Driver.SetCheckbox(By.Id("ShowPayInWalletButton"), true));
s.Driver.FindElement(By.Id("SupportUrl")).SendKeys(supportUrl);
s.Driver.FindElement(By.Id("Save")).SendKeys(Keys.Enter);
Assert.Contains("Store successfully updated", s.FindAlertMessage().Text);

Expand Down Expand Up @@ -130,17 +130,19 @@ public async Task CanConfigureCheckout()
expirySeconds.SendKeys("3");
s.Driver.FindElement(By.Id("Expire")).Click();

var paymentInfo = s.Driver.WaitForElement(By.Id("PaymentInfo"));
Assert.Contains("This invoice will expire in", paymentInfo.Text);
Assert.DoesNotContain("Please send", paymentInfo.Text);
TestUtils.Eventually(() =>
{
var paymentInfo = s.Driver.WaitForElement(By.Id("PaymentInfo"));
Assert.Contains("This invoice will expire in", paymentInfo.Text);
Assert.DoesNotContain("Please send", paymentInfo.Text);
});
TestUtils.Eventually(() =>
{
var expiredSection = s.Driver.FindElement(By.Id("unpaid"));
Assert.True(expiredSection.Displayed);
Assert.Contains("Invoice Expired", expiredSection.Text);
Assert.Contains("resubmit a payment", expiredSection.Text);
Assert.DoesNotContain("This invoice expired with partial payment", expiredSection.Text);

});
Assert.True(s.Driver.ElementDoesNotExist(By.Id("ContactLink")));
Assert.True(s.Driver.ElementDoesNotExist(By.Id("ReceiptLink")));
Expand All @@ -164,9 +166,12 @@ await s.Server.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create(address, Ne
expirySeconds.SendKeys("3");
s.Driver.FindElement(By.Id("Expire")).Click();

paymentInfo = s.Driver.WaitForElement(By.Id("PaymentInfo"));
Assert.Contains("The invoice hasn't been paid in full.", paymentInfo.Text);
Assert.Contains("Please send", paymentInfo.Text);
TestUtils.Eventually(() =>
{
var paymentInfo = s.Driver.WaitForElement(By.Id("PaymentInfo"));
Assert.Contains("The invoice hasn't been paid in full.", paymentInfo.Text);
Assert.Contains("Please send", paymentInfo.Text);
});
TestUtils.Eventually(() =>
{
var expiredSection = s.Driver.FindElement(By.Id("unpaid"));
Expand Down Expand Up @@ -210,7 +215,7 @@ await s.Server.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create(address, Ne
{
s.Driver.FindElement(By.Id("FakePayment")).Click();
s.Driver.FindElement(By.Id("mine-block")).Click();
paymentInfo = s.Driver.WaitForElement(By.Id("PaymentInfo"));
var paymentInfo = s.Driver.WaitForElement(By.Id("PaymentInfo"));
Assert.Contains("The invoice hasn't been paid in full", paymentInfo.Text);
Assert.Contains("Please send", paymentInfo.Text);
});
Expand Down Expand Up @@ -359,11 +364,13 @@ await s.Server.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create(address, Ne
expirySeconds.Clear();
expirySeconds.SendKeys("5");
s.Driver.FindElement(By.Id("Expire")).Click();

paymentInfo = s.Driver.WaitForElement(By.Id("PaymentInfo"));
Assert.Contains("This invoice will expire in", paymentInfo.Text);
Assert.Contains("00:0", paymentInfo.Text);
Assert.DoesNotContain("Please send", paymentInfo.Text);
TestUtils.Eventually(() =>
{
var paymentInfo = s.Driver.WaitForElement(By.Id("PaymentInfo"));
Assert.Contains("This invoice will expire in", paymentInfo.Text);
Assert.Contains("00:0", paymentInfo.Text);
Assert.DoesNotContain("Please send", paymentInfo.Text);
});

// Configure countdown timer
s.GoToHome();
Expand All @@ -379,19 +386,21 @@ await s.Server.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create(address, Ne

s.GoToInvoiceCheckout(invoiceId);
s.Driver.WaitUntilAvailable(By.Id("Checkout-v2"));
paymentInfo = s.Driver.FindElement(By.Id("PaymentInfo"));
var paymentInfo = s.Driver.FindElement(By.Id("PaymentInfo"));
Assert.False(paymentInfo.Displayed);
Assert.DoesNotContain("This invoice will expire in", paymentInfo.Text);

expirySeconds = s.Driver.FindElement(By.Id("ExpirySeconds"));
expirySeconds.Clear();
expirySeconds.SendKeys("599");
s.Driver.FindElement(By.Id("Expire")).Click();

paymentInfo = s.Driver.WaitForElement(By.Id("PaymentInfo"));
Assert.True(paymentInfo.Displayed);
Assert.Contains("This invoice will expire in", paymentInfo.Text);
Assert.Contains("09:5", paymentInfo.Text);
TestUtils.Eventually(() =>
{
paymentInfo = s.Driver.WaitForElement(By.Id("PaymentInfo"));
Assert.True(paymentInfo.Displayed);
Assert.Contains("This invoice will expire in", paymentInfo.Text);
Assert.Contains("09:5", paymentInfo.Text);
});

// Disable LNURL again
s.GoToHome();
Expand Down Expand Up @@ -457,13 +466,12 @@ await s.Server.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create(invoice
.GetPaymentMethodDetails().GetPaymentDestination(), Network.RegTest),
new Money(0.001m, MoneyUnit.BTC));

IWebElement closebutton = null;
TestUtils.Eventually(() =>
{
closebutton = iframe.FindElement(By.Id("close"));
Assert.True(closebutton.Displayed);
var closeButton = iframe.FindElement(By.Id("close"));
Assert.True(closeButton.Displayed);
closeButton.Click();
});
closebutton.Click();
s.Driver.AssertElementNotFound(By.Name("btcpay"));
Assert.Equal(s.Driver.Url,
new Uri(s.ServerUri, $"tests/index.html?invoice={invoiceId}").ToString());
Expand Down
113 changes: 113 additions & 0 deletions BTCPayServer.Tests/CrowdfundTests.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
using System;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Form;
using BTCPayServer.Client;
using BTCPayServer.Client.Models;
using BTCPayServer.Controllers;
using BTCPayServer.Data;
using BTCPayServer.Forms;
using BTCPayServer.Forms.Models;
using BTCPayServer.Models.AppViewModels;
using BTCPayServer.Plugins.Crowdfund;
using BTCPayServer.Plugins.Crowdfund.Controllers;
using BTCPayServer.Plugins.Crowdfund.Models;
using BTCPayServer.Services.Apps;
using BTCPayServer.Services.Invoices;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
using NBitcoin;
using NBitpayClient;
Expand Down Expand Up @@ -303,5 +307,114 @@ public async Task CanComputeCrowdfundModel()
Assert.Equal(0.7m, model.Info.CurrentPendingAmount);
});
}

[Fact(Timeout = LongRunningTestTimeout)]
[Trait("Integration", "Integration")]
public async Task CrowdfundWithFormNoPerk()
{
using var tester = CreateServerTester();
await tester.StartAsync();
var user = tester.NewAccount();
await user.GrantAccessAsync();
user.RegisterDerivationScheme("BTC");
await user.SetNetworkFeeMode(NetworkFeeMode.Never);

var frmService = tester.PayTester.GetService<FormDataService>();
var appService = tester.PayTester.GetService<AppService>();
var crowdfund = user.GetController<UICrowdfundController>();
var apps = user.GetController<UIAppsController>();
var appData = new AppData { StoreDataId = user.StoreId, Name = "test", AppType = CrowdfundAppType.AppType };
await appService.UpdateOrCreateApp(appData);
var appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId).Result).Model);
var app = appList.Apps[0];
apps.HttpContext.SetAppData(appData);
crowdfund.HttpContext.SetAppData(appData);

var form = new Form
{
Fields =
[
Field.Create("Enter your email", "item1", "test@toto.com", true, null, "email"),
Field.Create("Name", "item2", 2.ToString(), true, null),
Field.Create("Item3", "invoice_item3", 3.ToString(), true, null)
]
};
var frmData = new FormData
{
StoreId = user.StoreId,
Name = "frmTest",
Config = form.ToString()
};
await frmService.AddOrUpdateForm(frmData);

var lstForms = await frmService.GetForms(user.StoreId);
Assert.NotEmpty(lstForms);

var crowdfundViewModel = await crowdfund.UpdateCrowdfund(app.Id).AssertViewModelAsync<UpdateCrowdfundViewModel>();
crowdfundViewModel.FormId = lstForms[0].Id;
crowdfundViewModel.TargetCurrency = "BTC";
crowdfundViewModel.Enabled = true;
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);

var vm2 = await crowdfund.CrowdfundForm(app.Id, (decimal?)0.01).AssertViewModelAsync<FormViewModel>();
var res = await crowdfund.CrowdfundFormSubmit(app.Id, (decimal)0.01, "", vm2);
Assert.IsNotType<NotFoundObjectResult>(res);
Assert.IsNotType<BadRequest>(res);
}

[Fact(Timeout = LongRunningTestTimeout)]
[Trait("Integration", "Integration")]
public async Task CrowdfundWithFormAndPerk()
{
using var tester = CreateServerTester();
await tester.StartAsync();
var user = tester.NewAccount();
await user.GrantAccessAsync();
user.RegisterDerivationScheme("BTC");
await user.SetNetworkFeeMode(NetworkFeeMode.Never);

var frmService = tester.PayTester.GetService<FormDataService>();
var appService = tester.PayTester.GetService<AppService>();
var crowdfund = user.GetController<UICrowdfundController>();
var apps = user.GetController<UIAppsController>();
var appData = new AppData { StoreDataId = user.StoreId, Name = "test", AppType = CrowdfundAppType.AppType };
await appService.UpdateOrCreateApp(appData);
var appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId).Result).Model);
var app = appList.Apps[0];
apps.HttpContext.SetAppData(appData);
crowdfund.HttpContext.SetAppData(appData);

var form = new Form
{
Fields =
[
Field.Create("Enter your email", "item1", "test@toto.com", true, null, "email"),
Field.Create("Name", "item2", 2.ToString(), true, null),
Field.Create("Item3", "invoice_item3", 3.ToString(), true, null)
]
};
var frmData = new FormData
{
StoreId = user.StoreId,
Name = "frmTest",
Config = form.ToString()
};
await frmService.AddOrUpdateForm(frmData);

var lstForms = await frmService.GetForms(user.StoreId);
Assert.NotEmpty(lstForms);

var crowdfundViewModel = await crowdfund.UpdateCrowdfund(app.Id).AssertViewModelAsync<UpdateCrowdfundViewModel>();
crowdfundViewModel.FormId = lstForms[0].Id;
crowdfundViewModel.TargetCurrency = "BTC";
crowdfundViewModel.Enabled = true;
crowdfundViewModel.PerksTemplate = "[{\"id\": \"xxx\",\"title\": \"Perk 1\",\"priceType\": \"Fixed\",\"price\": \"0.001\",\"image\": \"\",\"description\": \"\",\"categories\": [],\"disabled\": false}]";
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);

var vm2 = await crowdfund.CrowdfundForm(app.Id, (decimal?)0.01, "xxx").AssertViewModelAsync<FormViewModel>();
var res = await crowdfund.CrowdfundFormSubmit(app.Id, (decimal)0.01, "xxx", vm2);
Assert.IsNotType<NotFoundObjectResult>(res);
Assert.IsNotType<BadRequest>(res);
}
}
}
11 changes: 7 additions & 4 deletions BTCPayServer.Tests/GreenfieldAPITests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2331,7 +2331,7 @@ await AssertHttpError(403, async () =>
if (marked == InvoiceStatus.Settled)
{
Assert.Equal(InvoiceStatus.Settled, result.Status);
user.AssertHasWebhookEvent<WebhookInvoiceSettledEvent>(WebhookEventType.InvoiceSettled,
await user.AssertHasWebhookEvent<WebhookInvoiceSettledEvent>(WebhookEventType.InvoiceSettled,
o =>
{
Assert.Equal(inv.Id, o.InvoiceId);
Expand All @@ -2341,7 +2341,7 @@ await AssertHttpError(403, async () =>
if (marked == InvoiceStatus.Invalid)
{
Assert.Equal(InvoiceStatus.Invalid, result.Status);
var evt = user.AssertHasWebhookEvent<WebhookInvoiceInvalidEvent>(WebhookEventType.InvoiceInvalid,
var evt = await user.AssertHasWebhookEvent<WebhookInvoiceInvalidEvent>(WebhookEventType.InvoiceInvalid,
o =>
{
Assert.Equal(inv.Id, o.InvoiceId);
Expand Down Expand Up @@ -3876,8 +3876,9 @@ await tester.WaitForEvent<NewOnChainTransactionEvent>(async () =>
Assert.True( settings.ProcessNewPayoutsInstantly);

var pluginHookService = tester.PayTester.GetService<IPluginHookService>();
var beforeHookTcs = new TaskCompletionSource();
var afterHookTcs = new TaskCompletionSource();
var beforeHookTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var afterHookTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
TestLogs.LogInformation("Adding hook...");
pluginHookService.ActionInvoked += (sender, tuple) =>
{
switch (tuple.hook)
Expand Down Expand Up @@ -3908,7 +3909,9 @@ await tester.WaitForEvent<NewOnChainTransactionEvent>(async () =>
PaymentMethod = "BTC",
Destination = (await adminClient.GetOnChainWalletReceiveAddress(admin.StoreId, "BTC", true)).Address,
});
TestLogs.LogInformation("Waiting before hook...");
await beforeHookTcs.Task.WaitAsync(TimeSpan.FromSeconds(5));
TestLogs.LogInformation("Waiting before after...");
await afterHookTcs.Task.WaitAsync(TimeSpan.FromSeconds(5));
payouts = await adminClient.GetStorePayouts(admin.StoreId);
try
Expand Down
Loading

0 comments on commit ff84ed5

Please sign in to comment.