fix: preserve compatibility query in plugin embeds

This commit is contained in:
thgO.O 2026-05-26 17:25:49 -03:00
parent eea9a3e410
commit 1a8fbaebb6
No known key found for this signature in database
GPG Key ID: EDC540B3D14756CB
5 changed files with 114 additions and 3 deletions

View File

@ -80,6 +80,49 @@ public class PluginDetailsUITests(ITestOutputHelper output) : PageTest
Assert.True(detailsBox.Y < reviewsBox.Y, "Metadata should stay above reviews in embedded details.");
}
[Fact]
public async Task PluginDetails_EmbedSelection_PreservesCompatibilityQuery()
{
await using var tester = new PlaywrightTester(_log);
tester.Server.ReuseDatabase = false;
await tester.StartAsync();
var ownerId = await tester.Server.CreateFakeUserAsync("embed-selection-owner@x.com", confirmEmail: true, githubVerified: true);
const string firstSlug = "embed-select-a";
const string secondSlug = "embed-select-b";
await tester.Server.CreateAndBuildPluginAsync(ownerId, firstSlug);
await tester.Server.CreateAndBuildPluginAsync(ownerId, secondSlug);
var detailsUrl = new Uri(tester.ServerUri!, $"/public/plugins/{firstSlug}?embed=1&btcpayVersion=2.3.6&includePreRelease=true");
await tester.Page!.SetContentAsync($"""
<iframe id="details" src="{detailsUrl}"></iframe>
""");
await tester.Page.WaitForFunctionAsync("""
() => document.querySelector('#details')?.contentWindow?.document?.querySelector('[data-embed-page="details"]')
""");
await tester.Page.EvaluateAsync($$"""
() => document.querySelector('#details').contentWindow.postMessage({
type: 'btcpay:host-context',
selectedSlug: '{{secondSlug}}'
}, window.location.origin)
""");
var finalUrlHandle = await tester.Page.WaitForFunctionAsync($$"""
() => {
const href = document.querySelector('#details')?.contentWindow?.location?.href || '';
return href.includes('/public/plugins/{{secondSlug}}') ? href : false;
}
""");
var finalUrl = await finalUrlHandle.JsonValueAsync<string>();
Assert.Contains($"/public/plugins/{secondSlug}", finalUrl);
Assert.Contains("embed=1", finalUrl);
Assert.Contains("btcpayVersion=2.3.6", finalUrl);
Assert.Contains("includePreRelease=true", finalUrl);
}
[Fact]
public async Task PluginDetails_Reviews_Flow_Works()
{

View File

@ -17,6 +17,45 @@ public class PublicDirectoryUITests(ITestOutputHelper output) : PageTest
{
private readonly XUnitLogger _log = new("PublicDirectoryUITests", output);
[Fact]
public async Task PublicDirectory_EmbedLinks_PreserveCompatibilityQuery()
{
await using var tester = new PlaywrightTester(_log);
tester.Server.ReuseDatabase = false;
await tester.StartAsync();
await using var conn = await tester.Server.GetService<DBConnectionFactory>().Open();
var ownerId = await tester.Server.CreateFakeUserAsync(confirmEmail: true, githubVerified: true);
const string pluginSlug = "public-directory-embed-query";
var fullBuildId = await tester.Server.CreateAndBuildPluginAsync(ownerId, pluginSlug);
var manifestInfoJson = await conn.QuerySingleAsync<string>(
"SELECT manifest_info FROM builds WHERE plugin_slug = @PluginSlug AND id = @BuildId",
new { PluginSlug = pluginSlug, fullBuildId.BuildId });
var manifest = PluginManifest.Parse(manifestInfoJson);
await conn.SetVersionBuild(fullBuildId, manifest.Version, manifest.BTCPayMinVersion, manifest.BTCPayMaxVersion, false);
await conn.SetPluginSettings(pluginSlug, null, PluginVisibilityEnum.Listed);
await tester.GoToUrl("/public/plugins?embed=1&btcpayVersion=2.3.6-rc2&includePreRelease=true&sort=rating");
await tester.AssertNoError();
await Expect(tester.Page!.Locator("form input[name='btcpayVersion']")).ToHaveValueAsync("2.3.6-rc2");
await Expect(tester.Page.Locator("form input[name='includePreRelease']")).ToHaveValueAsync("true");
var detailsHref = await tester.Page.Locator($"a[data-plugin-slug='{pluginSlug}']").GetAttributeAsync("href");
Assert.NotNull(detailsHref);
Assert.Contains($"/public/plugins/{pluginSlug}", detailsHref);
Assert.Contains("embed=1", detailsHref);
Assert.Contains("btcpayVersion=2.3.6-rc2", detailsHref);
Assert.Contains("includePreRelease=true", detailsHref);
var alphaSortHref = await tester.Page.Locator("a.dropdown-item[href*='sort=alpha']").GetAttributeAsync("href");
Assert.NotNull(alphaSortHref);
Assert.Contains("embed=1", alphaSortHref);
Assert.Contains("btcpayVersion=2.3.6-rc2", alphaSortHref);
Assert.Contains("includePreRelease=true", alphaSortHref);
}
[Fact]
public async Task PublicDirectory_RespectsPluginVisibility()
{

View File

@ -210,8 +210,8 @@ public class HomeController(
[HttpGet("public/plugins")]
[EnableRateLimiting(Policies.PublicApiRateLimit)]
public async Task<IActionResult> AllPlugins(
[ModelBinder(typeof(PluginVersionModelBinder))]
PluginVersion? btcpayVersion = null, string? searchPluginName = null, string sort = "smart")
[ModelBinder(typeof(BtcPayHostVersionModelBinder))]
PluginVersion? btcpayVersion = null, bool includePreRelease = false, string? searchPluginName = null, string sort = "smart")
{
searchPluginName = searchPluginName.StripControlCharacters();
@ -285,7 +285,7 @@ public class HomeController(
new
{
btcpayVersion = btcpayVersion?.VersionParts,
includePreRelease = false,
includePreRelease,
searchPattern = $"%{searchPluginName}%",
hasSearchTerm = !string.IsNullOrWhiteSpace(searchPluginName)
});

View File

@ -11,6 +11,10 @@
var currentSearch = Context.Request.Query["searchPluginName"].ToString();
var currentSort = (string?)Context.Request.Query["sort"] ?? "smart";
var currentBtcpayVersion = Context.Request.Query["btcpayVersion"].ToString();
var currentIncludePreRelease = Context.Request.Query["includePreRelease"].ToString();
var btcpayVersionRoute = string.IsNullOrEmpty(currentBtcpayVersion) ? null : currentBtcpayVersion;
var includePreReleaseRoute = string.IsNullOrEmpty(currentIncludePreRelease) ? null : currentIncludePreRelease;
if (string.IsNullOrEmpty(currentSort)) currentSort = "smart";
var sortLabel = currentSort switch
@ -55,6 +59,14 @@
{
<input type="hidden" name="embed" value="1" />
}
@if (!string.IsNullOrEmpty(currentBtcpayVersion))
{
<input type="hidden" name="btcpayVersion" value="@currentBtcpayVersion" />
}
@if (!string.IsNullOrEmpty(currentIncludePreRelease))
{
<input type="hidden" name="includePreRelease" value="@currentIncludePreRelease" />
}
<input type="text"
name="searchPluginName"
@ -75,6 +87,8 @@
<a class="dropdown-item @(currentSort == "smart" ? "active" : "")"
asp-action="AllPlugins"
asp-route-searchPluginName="@currentSearch"
asp-route-btcpayVersion="@btcpayVersionRoute"
asp-route-includePreRelease="@includePreReleaseRoute"
asp-route-embed="@embedRoute"
asp-route-sort="smart">
Relevance
@ -82,6 +96,8 @@
<a class="dropdown-item @(currentSort == "rating" ? "active" : "")"
asp-action="AllPlugins"
asp-route-searchPluginName="@currentSearch"
asp-route-btcpayVersion="@btcpayVersionRoute"
asp-route-includePreRelease="@includePreReleaseRoute"
asp-route-embed="@embedRoute"
asp-route-sort="rating">
Highest rated
@ -89,6 +105,8 @@
<a class="dropdown-item @(currentSort == "recent" ? "active" : "")"
asp-action="AllPlugins"
asp-route-searchPluginName="@currentSearch"
asp-route-btcpayVersion="@btcpayVersionRoute"
asp-route-includePreRelease="@includePreReleaseRoute"
asp-route-embed="@embedRoute"
asp-route-sort="recent">
Most recent
@ -96,6 +114,8 @@
<a class="dropdown-item @(currentSort == "alpha" ? "active" : "")"
asp-action="AllPlugins"
asp-route-searchPluginName="@currentSearch"
asp-route-btcpayVersion="@btcpayVersionRoute"
asp-route-includePreRelease="@includePreReleaseRoute"
asp-route-embed="@embedRoute"
asp-route-sort="alpha">
Alphabetical
@ -132,6 +152,8 @@
<h3 style="margin-top: 0; margin-bottom: 5px;">
<a asp-action="GetPluginDetails"
asp-route-embed="@embedRoute"
asp-route-btcpayVersion="@btcpayVersionRoute"
asp-route-includePreRelease="@includePreReleaseRoute"
data-plugin-slug="@plugin.ProjectSlug"
data-plugin-identifier="@plugin.ManifestInfo?["Identifier"]?.ToString()"
asp-route-pluginSlug="@plugin.ProjectSlug">@plugin.PluginTitle</a>

View File

@ -134,6 +134,13 @@
function buildDetailsUrl(slug) {
const url = new URL("/public/plugins/" + encodeURIComponent(slug), window.location.origin);
const currentUrl = new URL(window.location.href);
["btcpayVersion", "includePreRelease"].forEach(function (param) {
const value = currentUrl.searchParams.get(param);
if (value) {
url.searchParams.set(param, value);
}
});
url.searchParams.set("embed", "1");
return url.toString();
}