1package tinyclient23import (4 "strings"5 "testing"6)78func TestInjectBasePreservesExternalURLsAndContent(t *testing.T) {9 for _, tc := range []struct {10 name, input, want string11 }{12 {"local link", `<a href="/repo?path=a%26b">code</a>`, `<a href="/r/alice/repo?path=a%26b">code</a>`},13 {"external link", `<a href="//example.org/file">external</a>`, `<a href="//example.org/file">external</a>`},14 {"external image", `<img src="//example.org/icon.png">`, `<img src="//example.org/icon.png">`},15 {"already scoped", `<a href="/r/alice/files">files</a>`, `<a href="/r/alice/files">files</a>`},16 {"script source", `<script src="/fixi.js"></script>`, `<script src="/r/alice/fixi.js"></script>`},17 {"room avatar fallback", `<room-avatar fallback="/avatars/v1/avatar.svg"><img src="/avatars/v1/avatar.svg"></room-avatar>`, `<room-avatar fallback="/r/alice/avatars/v1/avatar.svg"><img src="/r/alice/avatars/v1/avatar.svg"></room-avatar>`},18 {"script text", `<script>const example = ' href="/example"';</script>`, `<script>const example = ' href="/example"';</script>`},19 {"textarea", `<textarea> href="/example"</textarea>`, `<textarea> href="/example"</textarea>`},20 {"local action", `<button fx-action="/connect/fragment">preview</button>`, `<button fx-action="/r/alice/connect/fragment">preview</button>`},21 } {22 t.Run(tc.name, func(t *testing.T) {23 if got := injectBase(tc.input, "/r/alice"); got != tc.want {24 t.Fatalf("got %q, want %q", got, tc.want)25 }26 })27 }28}2930func FuzzInjectBasePreservesText(f *testing.F) {31 for _, seed := range []string{"hello", ` href="/file"`, `fetch("/example")`, `new URL("/file")`, "emoji 🧪", "a\x00b"} {32 f.Add(seed)33 }34 f.Fuzz(func(t *testing.T, content string) {35 // Textarea content can contain quotes and URL examples literally.36 content = strings.ReplaceAll(content, "<", "<")37 input := "<textarea>" + content + "</textarea>"38 if got := injectBase(input, "/r/alice"); got != input {39 t.Fatalf("content changed: got %q, want %q", got, input)40 }41 })42}