package web import ( "errors" "io" "net/http" "os" "strings" "testing" "testing/fstest" ) func TestSharePageRenderer_Render(t *testing.T) { t.Run("injects marshaled metadata", func(t *testing.T) { fs := fstest.MapFS{ "share.html": {Data: []byte(``)}, } r := NewSharePageRenderer(http.FS(fs)) page, err := r.Render(map[string]string{"stream_url": "/s/abc/stream"}) if err != nil { t.Fatalf("unexpected error: %v", err) } if !strings.Contains(page.HTML, `"stream_url":"/s/abc/stream"`) { t.Fatalf("expected injected JSON, got %q", page.HTML) } if page.Name != "share.html" { t.Fatalf("expected name share.html, got %q", page.Name) } }) t.Run("returns marshal error", func(t *testing.T) { fs := fstest.MapFS{ "share.html": {Data: []byte(``)}, } r := NewSharePageRenderer(http.FS(fs)) _, err := r.Render(map[string]any{"bad": make(chan int)}) if err == nil { t.Fatal("expected marshal error") } if !strings.Contains(err.Error(), "marshal share metadata") { t.Fatalf("expected wrapped marshal error, got %v", err) } }) t.Run("nil renderer", func(t *testing.T) { var r *SharePageRenderer if _, err := r.Render(nil); err == nil { t.Fatal("expected error from nil renderer") } }) t.Run("missing template", func(t *testing.T) { r := NewSharePageRenderer(http.FS(fstest.MapFS{})) _, err := r.Render(nil) if err == nil { t.Fatal("expected open error") } }) t.Run("stat error propagates", func(t *testing.T) { r := NewSharePageRenderer(statErrorFS{}) _, err := r.Render(nil) if err == nil { t.Fatal("expected stat error") } }) } func TestInjectShareMedia(t *testing.T) { html, err := injectShareMedia(`a b`, "", "v") if err != nil { t.Fatalf("unexpected error: %v", err) } if html != `a "v" b` { t.Fatalf("unexpected result: %q", html) } } func TestSanitizeFileName(t *testing.T) { t.Run("short clean name unchanged", func(t *testing.T) { got := SanitizeFileName("movie.mp4") if got != "movie.mp4" { t.Fatalf("expected %q, got %q", "movie.mp4", got) } }) t.Run("html special chars are escaped", func(t *testing.T) { got := SanitizeFileName(`.mp4`) if strings.Contains(got, "<") || strings.Contains(got, ">") { t.Fatalf("expected HTML-escaped output, got %q", got) } if !strings.Contains(got, "<") { t.Fatalf("expected < in output, got %q", got) } }) t.Run("long filename is truncated to MaxFileNameLength runes", func(t *testing.T) { // Build a filename longer than MaxFileNameLength characters. long := strings.Repeat("a", MaxFileNameLength+100) got := SanitizeFileName(long) if len([]rune(got)) > MaxFileNameLength { t.Fatalf("expected at most %d runes, got %d", MaxFileNameLength, len([]rune(got))) } }) t.Run("multibyte runes counted correctly", func(t *testing.T) { // Each '日' is 3 UTF-8 bytes but 1 rune; we want exactly MaxFileNameLength runes. long := strings.Repeat("日", MaxFileNameLength+10) got := SanitizeFileName(long) if len([]rune(got)) > MaxFileNameLength { t.Fatalf("expected at most %d runes, got %d", MaxFileNameLength, len([]rune(got))) } }) t.Run("ampersand is escaped", func(t *testing.T) { got := SanitizeFileName("a&b.mp4") if got != "a&b.mp4" { t.Fatalf("expected %q, got %q", "a&b.mp4", got) } }) } // statErrorFS returns a file whose Stat() fails — used to cover the // rare error path in Render where the template is openable but cannot // be stat'd. type statErrorFS struct{} func (statErrorFS) Open(string) (http.File, error) { return statErrorFile{}, nil } type statErrorFile struct{} func (statErrorFile) Close() error { return nil } func (statErrorFile) Read([]byte) (int, error) { return 0, io.EOF } func (statErrorFile) Seek(int64, int) (int64, error) { return 0, nil } func (statErrorFile) Readdir(int) ([]os.FileInfo, error) { return nil, nil } func (statErrorFile) Stat() (os.FileInfo, error) { return nil, errors.New("stat failed") }