clone

a self-hosted multitenant nostr relay with sites, file storage, git hosting, and inbox and outbox delivery

feat/tinyagent
tinyrelay / tinyclient / social_content_test.go
social_content_test.go 5.8K
1package tinyclient23import (4 "strings"5 "testing"6)78func TestSocialBodyRendersMarkdownAndMedia(t *testing.T) {9 row := map[string]any{10 "kind": "30023",11 "content": "# Hello\n\nA **note** with a table:\n\n| one | two |\n| --- | --- |\n| a | b |\n\n![sun](https://cdn.example.test/sun.png)",12 "tags": [][]string{{"imeta", "url https://cdn.example.test/sun.png", "m image/png", "alt sunset"}},13 }14 got := string(socialBody(row))15 for _, want := range []string{"<h1>Hello</h1>", "<strong>note</strong>", "data-markdown-table", "<img", `alt="sunset"`, `loading="lazy"`} {16 if !strings.Contains(got, want) {17 t.Errorf("social body missing %q: %s", want, got)18 }19 }20 if strings.Count(got, "sun.png") != 1 {21 t.Fatalf("media should have one image: %s", got)22 }23}2425func TestSocialBodySupportsMediaTypesAndIgnoresCode(t *testing.T) {26 row := map[string]any{27 "kind": "1",28 "content": "https://cdn.example.test/clip.mp4\n\nhttps://cdn.example.test/song.mp3\n\n```\nhttps://cdn.example.test/hidden.png\n```",29 "tags": [][]string{{"imeta", "url https://cdn.example.test/clip.mp4", "m video/mp4"}, {"imeta", "url https://cdn.example.test/song.mp3", "m audio/mpeg"}, {"imeta", "url https://cdn.example.test/hidden.png", "m image/png"}},30 }31 got := string(socialBody(row))32 if !strings.Contains(got, `<video controls`) || !strings.Contains(got, `<audio controls`) {33 t.Fatalf("native media missing: %s", got)34 }35 if !strings.Contains(got, "hidden.png") || strings.Contains(got, "<iframe") {36 t.Fatalf("code or iframe leaked into media: %s", got)37 }38}3940func TestSocialNoteIsPlaintext(t *testing.T) {41 got := string(socialBody(map[string]any{"kind": "1", "content": "**literal**\nhttps://cdn.example.test/note.png", "tags": [][]string{{"imeta", "url https://cdn.example.test/note.png", "m image/png"}}}))42 if strings.Contains(got, "<strong>") || !strings.Contains(got, "**literal**") || !strings.Contains(got, "<img") {43 t.Fatalf("note rendering = %s", got)44 }45}4647func TestSocialBodyRendersMetadataOnlyMediaOnce(t *testing.T) {48 row := map[string]any{"kind": 1, "content": "This week's conversation.", "tags": [][]string{49 {"imeta", "url https://cdn.test/episode", "m audio/mpeg"},50 {"imeta", "url https://cdn.test/episode", "m audio/mpeg"},51 }}52 got := string(socialBody(row))53 if strings.Count(got, "<audio controls") != 1 || !strings.Contains(got, `src="https://cdn.test/episode"`) {54 t.Fatalf("metadata audio missing or repeated: %s", got)55 }56}5758func TestSocialMediaSkipsInlineAndFencedCode(t *testing.T) {59 url := "https://cdn.example.test/hidden.png"60 row := map[string]any{"kind": "30023", "content": "`![x](" + url + ")`\n\n````\n![x](" + url + ")\n````", "tags": [][]string{{"imeta", "url " + url, "m image/png"}}}61 got := string(socialBody(row))62 if strings.Contains(got, "<img") || strings.Contains(got, "<figure") {63 t.Fatalf("code media was embedded: %s", got)64 }65 if !strings.Contains(got, "hidden.png") {66 t.Fatalf("code source missing: %s", got)67 }68}6970func TestSocialBodyEscapesUnsafeContent(t *testing.T) {71 got := string(socialBody(map[string]any{"content": `<script>alert(1)</script> [x](javascript:alert(1)) https://cdn.example.test/a.svg`, "tags": [][]string{{"imeta", "url https://cdn.example.test/a.svg", "m image/svg+xml"}}}))72 if strings.Contains(got, "<script") || strings.Contains(got, `href="javascript:`) || strings.Contains(got, "<iframe") {73 t.Fatalf("unsafe social markup: %s", got)74 }75 if !strings.Contains(got, "a.svg") {76 t.Fatalf("safe media link missing: %s", got)77 }78}7980func TestSocialPreviewAndMediaURL(t *testing.T) {81 if got := socialPreview(map[string]any{"content": "hello\nworld"}); got != "hello world" {82 t.Fatalf("preview = %q", got)83 }84 if got := socialMediaURL("javascript:alert(1)"); got != "" {85 t.Fatalf("unsafe URL = %q", got)86 }87 if got := socialMediaURL("https://example.test/a?x=1"); got != "https://example.test/a?x=1" {88 t.Fatalf("safe URL = %q", got)89 }90}9192func TestSocialArticleMediaWithoutMetadata(t *testing.T) {93 got := string(socialBody(map[string]any{"kind": 30023, "content": "![Cover](https://cdn.test/a.jpg)\n\n[Watch](https://cdn.test/a.mp4)\n\nhttps://cdn.test/a.mp3"}))94 for _, want := range []string{`<img src="https://cdn.test/a.jpg"`, `alt="Cover"`, `<video controls`, `<audio controls`} {95 if !strings.Contains(got, want) {96 t.Errorf("missing %s: %s", want, got)97 }98 }99 if strings.Contains(got, "!Cover") || strings.Contains(got, "<p><figure") {100 t.Fatal(got)101 }102}103func TestSocialNoteImageWithoutMetadataAndInvalidClientMIME(t *testing.T) {104 for _, tags := range [][][]string{nil, {{"imeta", "url https://cdn.test/a.jpg", "m jpeg"}}} {105 got := string(socialBody(map[string]any{"kind": 1, "content": "A photo\nhttps://cdn.test/a.jpg", "tags": tags}))106 if !strings.Contains(got, `<img src="https://cdn.test/a.jpg"`) {107 t.Fatal(got)108 }109 }110}111func TestSocialMediaTokenCannotBeForgedInCode(t *testing.T) {112 raw := "https://cdn.test/a.png"113 got := string(socialBody(map[string]any{"kind": 30023, "content": "`" + socialMediaToken(raw) + "`\n\n![A](" + raw + ")"}))114 if strings.Count(got, "<img") != 1 || !strings.Contains(got, "<code>"+socialMediaToken(raw)+"</code>") {115 t.Fatal(got)116 }117}118119func TestSocialMediaReservesValidMetadataDimensions(t *testing.T) {120 for _, dimension := range []string{"1170x1613", "1170.0x1613.0"} {121 got := string(socialBody(map[string]any{"kind": 1, "content": "https://cdn.test/a.jpg", "tags": [][]string{{"imeta", "url https://cdn.test/a.jpg", "m image/jpeg", "dim " + dimension}}}))122 if !strings.Contains(got, `width="1170" height="1613"`) {123 t.Fatal(got)124 }125 }126 for _, dimension := range []string{"0x3", "NaNx3", "1e90x3", `1" onload="bad`} {127 got := string(socialBody(map[string]any{"kind": 1, "content": "https://cdn.test/a.jpg", "tags": [][]string{{"imeta", "url https://cdn.test/a.jpg", "m image/jpeg", "dim " + dimension}}}))128 if strings.Contains(got, "width=") || strings.Contains(got, "onload=") {129 t.Fatal(got)130 }131 }132}