From d534247c5a1decabc6f904125f4356a835b63287 Mon Sep 17 00:00:00 2001 From: Dmitry Verkhoturov Date: Sun, 23 Aug 2026 13:51:54 +0100 Subject: [PATCH] Pin the public path #2203 fixed, which shipped without a test The bundler used to bake a fixed public path into every entry, so an instance mounted under a prefix, which manuals/subdomain documents, asked the domain root for its provider icons and got nothing. #2197 derived the path from the URL the bundle was loaded from instead, and covered it with nothing: publicPath appears only in webpack.config.js and neither suite touched asset paths. The check is on the assignment webpack emits for its runtime public path, a string literal when the path is fixed and an expression when it is derived. Asset filenames are bare in both builds, so the obvious assertion, looking for a rooted "/web/name.svg" string, finds nothing either way and proves nothing. I wrote that one first and it passed against a deliberately broken build. Every emitted bundle is checked rather than the entry the bug was reported against: it put fifteen icons in remark.mjs and one in last-comments.mjs, so a case reading a single entry would have gone green with half of it still live. Verified by rebuilding the frontend with the public path baked back in: the assertion fails on that build and passes on master's. --- e2e/webfiles_test.go | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/e2e/webfiles_test.go b/e2e/webfiles_test.go index eca0a55e..68cb907c 100644 --- a/e2e/webfiles_test.go +++ b/e2e/webfiles_test.go @@ -11,6 +11,7 @@ import ( "net/http" "os" "os/exec" + "regexp" "slices" "strings" "sync" @@ -264,3 +265,39 @@ func pauseForWebLimit() { } lastWebGet = time.Now() } + +// TestWeb_NoBundleHardcodesTheWebRoot pins the fix for #2203, which shipped with no test at any +// level. The bundler used to bake a fixed public path into every entry, so an instance mounted +// under a prefix, which manuals/subdomain documents, asked the domain root for its provider icons +// and got nothing. The path is derived from the URL the bundle was loaded from now, which is +// correct for both arrangements. +// +// What separates the two builds is the assignment webpack emits for its runtime public path: a +// literal when the path is fixed, and a computed value when it is derived. Asset filenames appear +// bare either way, so a case looking for a rooted "/web/name.svg" string finds nothing in either +// build and proves nothing; this asserts the assignment instead. +// +// Every emitted bundle is checked rather than the obvious one: the original defect put fifteen +// icons in remark.mjs and one in last-comments.mjs, so a case reading a single entry would have +// gone green with half of it still live. +func TestWeb_NoBundleHardcodesTheWebRoot(t *testing.T) { + names := emittedBundles(t) + require.Contains(t, names, "remark.mjs", "listing is not the served web root: %v", names) + require.Contains(t, names, "last-comments.mjs", + "the entry carrying the second half of #2203 is missing from the listing: %v", names) + + // webpack writes its public path to the `p` property of the runtime object. A baked-in path + // is a string literal there; a derived one is an expression + baked := regexp.MustCompile("\\.p\\s*=\\s*[\"'`]/web/[\"'`]") + + for _, name := range names { + t.Run(name, func(t *testing.T) { + body := getWeb(t, "/web/"+name).body + require.NotEmpty(t, body, "%s serves nothing, so this asserts nothing", name) + + assert.NotRegexp(t, baked, body, + "%s bakes the public path in rather than deriving it, so an instance mounted under "+ + "a prefix fetches its assets from the domain root", name) + }) + } +}