package web
import (
"html/template"
"net/url"
"strings"
"testing"
"gitea.dooplex.hu/admin/felhom-controller/internal/infra"
)
// The deep-link template, pinned against the shipped FileBrowser Quantum router (SPIKE P2).
func TestFileBrowserLink_Template(t *testing.T) {
const dom = "demo-felhom.eu"
for _, tc := range []struct{ name, source, rel, want string }{
{"ascii source, one segment", "hdd_1", "media/movies",
"https://files.demo-felhom.eu/files/hdd_1/media/movies"},
{"accented source (the import label)", infra.FileBrowserImportLabel, "paperless",
"https://files.demo-felhom.eu/files/Beolvas%C3%A1s/paperless"},
{"source root, empty relpath", infra.FileBrowserImportLabel, "",
"https://files.demo-felhom.eu/files/Beolvas%C3%A1s"},
{"leading/trailing slashes are ignored", "hdd_1", "/media/books/",
"https://files.demo-felhom.eu/files/hdd_1/media/books"},
{"accented path segment", "hdd_1", "media/könyvek",
"https://files.demo-felhom.eu/files/hdd_1/media/k%C3%B6nyvek"},
} {
if got := fileBrowserLink(dom, tc.source, tc.rel); got != tc.want {
t.Errorf("%s:\n got %q\n want %q", tc.name, got, tc.want)
}
}
}
// THE trap, measured in SPIKE P2. QueryEscape turns a space into "+", which in a path segment is a
// literal plus and lands the customer on a folder that does not exist. This test fails if anyone
// swaps the escaper.
func TestFileBrowserLink_UsesPathEscapeNotQueryEscape(t *testing.T) {
const spaced = "Média & könyvtár"
got := fileBrowserLink("x.eu", spaced, "a b")
if strings.Contains(got, "+") {
t.Errorf("link contains '+' — QueryEscape was used somewhere; a '+' in a path segment is a literal plus, not a space: %q", got)
}
if want := url.PathEscape(spaced); !strings.Contains(got, want) {
t.Errorf("source not PathEscape'd: got %q, want it to contain %q", got, want)
}
// Guard the exact divergence the spike measured, so the two escapers can never be confused here.
if url.PathEscape(spaced) == url.QueryEscape(spaced) {
t.Fatal("fixture no longer distinguishes the two escapers — pick a name where they differ")
}
if strings.Contains(got, url.QueryEscape(spaced)) {
t.Errorf("link used QueryEscape: %q", got)
}
}
// The link is embedded in HTML. Percent-encoding and attribute-escaping must COMPOSE: PathEscape
// leaves "&" bare, html/template turns it into "&", and the browser decodes it back to "&".
// Anything that double-encodes (a hand-rolled HTML escape before the template) breaks the path.
func TestFileBrowserLink_ComposesWithHTMLEscaping(t *testing.T) {
link := fileBrowserLink("x.eu", "Média & könyvtár", "docs")
var sb strings.Builder
tmpl := template.Must(template.New("a").Parse(`x`))
if err := tmpl.Execute(&sb, link); err != nil {
t.Fatal(err)
}
out := sb.String()
if !strings.Contains(out, "&") {
t.Errorf("html/template should escape the bare & in the href: %q", out)
}
// It must NOT have been percent-encoded a second time (%2526 etc. would be double-encoding).
if strings.Contains(out, "%25") {
t.Errorf("double percent-encoding detected — do not pre-escape before the template: %q", out)
}
// And no raw quote/angle escaped into the attribute.
if strings.Contains(out, `href=""`) {
t.Errorf("html/template refused the URL (would render an empty href): %q", out)
}
}
// importFolderLink targets the CANONICAL source, so a drop-zone link is identical regardless of
// which drive the app itself sits on.
func TestImportFolderLink_IsCanonical(t *testing.T) {
a := importFolderLink("demo-felhom.eu", "paperless")
b := importFolderLink("demo-felhom.eu", "calibre")
for _, l := range []string{a, b} {
if !strings.Contains(l, "/files/"+url.PathEscape(infra.FileBrowserImportLabel)+"/") {
t.Errorf("import link must go through the canonical source: %q", l)
}
if strings.Contains(l, "hdd_1") || strings.Contains(l, "felhom-drives") {
t.Errorf("import link must never name a data drive: %q", l)
}
}
if a == b {
t.Error("different apps must get different folders")
}
}