What changed, and why it matters
This commit is a routine test and code-quality improvement. It tightens a test helper that was incorrectly always setting IsAdmin to true, modernizes some C# syntax, removes unused imports, and adds test-id attributes to a web page so automated tests can locate elements more reliably. There is no security-relevant change to production behavior.
No security action required. Treat as normal maintenance/test improvement.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The diff contains four categories of changes: (1) Playwright test assertion cleanups and added wait selectors; (2) a bug fix in TestAccount.SetServerRole where IsAdmin was hard-coded to true instead of using the isAdmin parameter; (3) C# primary-constructor refactoring in BoltInvoiceClaimDestination plus a null-check guard in LightningLikePayoutHandler; and (4) addition of data-testid attributes in ViewPullPayment.cshtml. None of these alter access-control enforcement, cryptographic handling, or user input processing in a security-relevant way. The null check is defensive but does not correspond to a disclosed vulnerability.
Changed components
BTCPayServer.Tests/PlaywrightTests.csBTCPayServer.Tests/TestAccount.csBTCPayServer/Data/Payouts/LightningLike/BoltInvoiceClaimDestination.csBTCPayServer/Data/Payouts/LightningLike/LightningLikePayoutHandler.csBTCPayServer/Hosting/BTCPayServerServices.csBTCPayServer/Views/UIPullPayment/ViewPullPayment.cshtmlInspect captured patch +23 / −39
diff --git a/BTCPayServer.Tests/PlaywrightTests.cs b/BTCPayServer.Tests/PlaywrightTests.cs
index fb9239b..e7fed90 100644
--- a/BTCPayServer.Tests/PlaywrightTests.cs
+++ b/BTCPayServer.Tests/PlaywrightTests.cs
@@ -1244,13 +1244,9 @@ namespace BTCPayServer.Tests
await s.LogIn(admin.RegisterDetails.Email, admin.RegisterDetails.Password);
await s.GoToHome();
- await TestUtils.EventuallyAsync(async () =>
- {
- Assert.Equal("1", await s.Page.Locator("#NotificationsBadge").TextContentAsync());
- });
-
+ await Expect(s.Page.Locator("#NotificationsBadge")).ToContainTextAsync("1");
await s.Page.ClickAsync("#NotificationsHandle");
- await s.Page.Locator($"#NotificationsList .notification:has-text('New user {unapproved.RegisterDetails.Email} requires approval')").WaitForAsync();
+ await Expect(s.Page.Locator("#NotificationsList .notification")).ToContainTextAsync($"New user {unapproved.RegisterDetails.Email} requires approval");
await s.Page.ClickAsync("#NotificationsMarkAllAsSeen");
await s.GoToServer(ServerNavPages.Policies);
@@ -1756,8 +1752,8 @@ namespace BTCPayServer.Tests
opening = s.Page.Context.WaitForPageAsync();
await s.Page.ClickAsync("text=View");
newPage = await opening;
- await Expect(newPage.Locator("body")).ToContainTextAsync("Description Edit");
- await Expect(newPage.Locator("body")).ToContainTextAsync("PP1 Edited");
+ await Expect(newPage.GetByTestId("description")).ToContainTextAsync("Description Edit");
+ await Expect(newPage.GetByTestId("title")).ToContainTextAsync("PP1 Edited");
}
[Fact]
@@ -2290,22 +2286,23 @@ namespace BTCPayServer.Tests
await s.FindAlertMessage();
Assert.DoesNotContain("Unarchive", await s.Page.Locator("#btn-archive-toggle").InnerTextAsync());
await s.GoToInvoices(storeId);
+ await s.Page.WaitForSelectorAsync($"tr[id=invoice_{invoiceId}]");
Assert.Contains(invoiceId, await s.Page.ContentAsync());
// archive via list
- await s.Page.Locator($".mass-action-select[value=\"{invoiceId}\"]").ClickAsync();
- await s.Page.Locator("#ArchiveSelected").ClickAsync();
- Assert.Contains("1 invoice archived", await (await s.FindAlertMessage()).InnerTextAsync());
+ await s.Page.ClickAsync($".mass-action-select[value=\"{invoiceId}\"]");
+ await s.Page.ClickAsync("#ArchiveSelected");
+ await s.FindAlertMessage(partialText: "1 invoice archived");
Assert.DoesNotContain(invoiceId, await s.Page.ContentAsync());
// unarchive via list
await s.Page.Locator("#StatusOptionsToggle").ClickAsync();
await s.Page.Locator("#StatusOptionsIncludeArchived").ClickAsync();
Assert.Contains(invoiceId, await s.Page.ContentAsync());
- await s.Page.Locator($".mass-action-select[value=\"{invoiceId}\"]").ClickAsync();
- await s.Page.Locator("#UnarchiveSelected").ClickAsync();
- Assert.Contains("1 invoice unarchived", await (await s.FindAlertMessage()).InnerTextAsync());
- Assert.Contains(invoiceId, await s.Page.ContentAsync());
+ await s.Page.ClickAsync($".mass-action-select[value=\"{invoiceId}\"]");
+ await s.Page.ClickAsync("#UnarchiveSelected");
+ await s.FindAlertMessage(partialText: "1 invoice unarchived");
+ await s.Page.WaitForSelectorAsync($"tr[id=invoice_{invoiceId}]");
// When logout out we should not be able to access store and invoice details
await s.GoToUrl("/account");
diff --git a/BTCPayServer.Tests/TestAccount.cs b/BTCPayServer.Tests/TestAccount.cs
index e1bbe5d..249f713 100644
--- a/BTCPayServer.Tests/TestAccount.cs
+++ b/BTCPayServer.Tests/TestAccount.cs
@@ -66,7 +66,7 @@ namespace BTCPayServer.Tests
await userManager.AddToRoleAsync(u, Roles.ServerAdmin);
else
await userManager.RemoveFromRoleAsync(u, Roles.ServerAdmin);
- IsAdmin = true;
+ IsAdmin = isAdmin;
}
public Task<BTCPayServerClient> CreateClient()
diff --git a/BTCPayServer/Data/Payouts/LightningLike/BoltInvoiceClaimDestination.cs b/BTCPayServer/Data/Payouts/LightningLike/BoltInvoiceClaimDestination.cs
index 5b7ef6a..f54e6c9 100644
--- a/BTCPayServer/Data/Payouts/LightningLike/BoltInvoiceClaimDestination.cs
+++ b/BTCPayServer/Data/Payouts/LightningLike/BoltInvoiceClaimDestination.cs
@@ -1,27 +1,20 @@
+#nullable enable
using System;
using BTCPayServer.Lightning;
using NBitcoin;
namespace BTCPayServer.Data.Payouts.LightningLike
{
- public class BoltInvoiceClaimDestination : ILightningLikeLikeClaimDestination
+ public class BoltInvoiceClaimDestination(string bolt11, BOLT11PaymentRequest paymentRequest) : ILightningLikeLikeClaimDestination
{
- public BoltInvoiceClaimDestination(string bolt11, BOLT11PaymentRequest paymentRequest)
- {
- Bolt11 = bolt11 ?? throw new ArgumentNullException(nameof(bolt11));
- PaymentRequest = paymentRequest;
- PaymentHash = paymentRequest.Hash;
- Amount = paymentRequest.MinimumAmount.MilliSatoshi == LightMoney.Zero ? null: paymentRequest.MinimumAmount.ToDecimal(LightMoneyUnit.BTC);
- }
-
public override string ToString()
{
return Bolt11;
}
- public string Bolt11 { get; }
- public BOLT11PaymentRequest PaymentRequest { get; }
- public uint256 PaymentHash { get; }
+ public string Bolt11 { get; } = bolt11 ?? throw new ArgumentNullException(nameof(bolt11));
+ public BOLT11PaymentRequest PaymentRequest { get; } = paymentRequest;
+ public uint256 PaymentHash { get; } = paymentRequest.Hash;
public string Id => PaymentHash.ToString();
- public decimal? Amount { get; }
+ public decimal? Amount { get; } = paymentRequest.MinimumAmount.MilliSatoshi == LightMoney.Zero ? null: paymentRequest.MinimumAmount.ToDecimal(LightMoneyUnit.BTC);
};
}
diff --git a/BTCPayServer/Data/Payouts/LightningLike/LightningLikePayoutHandler.cs b/BTCPayServer/Data/Payouts/LightningLike/LightningLikePayoutHandler.cs
index 26402a7..fbfb12f 100644
--- a/BTCPayServer/Data/Payouts/LightningLike/LightningLikePayoutHandler.cs
+++ b/BTCPayServer/Data/Payouts/LightningLike/LightningLikePayoutHandler.cs
@@ -1,7 +1,5 @@
using System;
-using System.Collections.Concurrent;
using System.Collections.Generic;
-using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
@@ -14,13 +12,10 @@ using BTCPayServer.Payments;
using BTCPayServer.Payments.Bitcoin;
using BTCPayServer.Payments.Lightning;
using BTCPayServer.Payouts;
-using BTCPayServer.Services;
using BTCPayServer.Services.Invoices;
using LNURL;
-using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
-using MimeKit;
using NBitcoin;
namespace BTCPayServer.Data.Payouts.LightningLike
@@ -32,7 +27,7 @@ namespace BTCPayServer.Data.Payouts.LightningLike
public PaymentMethodId PaymentMethodId { get; }
private readonly IOptions<LightningNetworkOptions> _options;
- private PaymentMethodHandlerDictionary _paymentHandlers;
+ private readonly PaymentMethodHandlerDictionary _paymentHandlers;
public BTCPayNetwork Network { get; }
public string[] DefaultRateRules => Network.DefaultRateRules;
@@ -108,7 +103,7 @@ namespace BTCPayServer.Data.Payouts.LightningLike
}
var result =
- BOLT11PaymentRequest.TryParse(destination, out var invoice, Network.NBitcoinNetwork)
+ BOLT11PaymentRequest.TryParse(destination, out var invoice, Network.NBitcoinNetwork) && invoice is not null
? new BoltInvoiceClaimDestination(destination, invoice)
: null;
diff --git a/BTCPayServer/Hosting/BTCPayServerServices.cs b/BTCPayServer/Hosting/BTCPayServerServices.cs
index 5592c3b..7abe2ed 100644
--- a/BTCPayServer/Hosting/BTCPayServerServices.cs
+++ b/BTCPayServer/Hosting/BTCPayServerServices.cs
@@ -73,7 +73,6 @@ using BTCPayServer.Payouts;
using ExchangeSharp;
using Microsoft.Extensions.Localization;
using Microsoft.AspNetCore.Mvc.Localization;
-using System.Reflection;
using Microsoft.EntityFrameworkCore;
namespace BTCPayServer.Hosting
diff --git a/BTCPayServer/Views/UIPullPayment/ViewPullPayment.cshtml b/BTCPayServer/Views/UIPullPayment/ViewPullPayment.cshtml
index f5b88ec..cbf7379 100644
--- a/BTCPayServer/Views/UIPullPayment/ViewPullPayment.cshtml
+++ b/BTCPayServer/Views/UIPullPayment/ViewPullPayment.cshtml
@@ -93,7 +93,7 @@
<div class="bg-tile h-100 m-0 p-3 p-sm-5 rounded">
@if (!string.IsNullOrWhiteSpace(Model.Title))
{
- <h2 class="h4 mb-3">@Model.Title</h2>
+ <h2 class="h4 mb-3" data-testid="title">@Model.Title</h2>
}
<div class="d-flex align-items-center gap-2">
<span class="text-muted text-nowrap" text-translate="true">Start Date</span>
@@ -122,7 +122,7 @@
}
@if (!string.IsNullOrEmpty(Model.Description))
{
- <div class="mt-4">@Safe.Raw(Model.Description)</div>
+ <div class="mt-4" data-testid="description">@Safe.Raw(Model.Description)</div>
}
</div>
</div>
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.