clone

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

feat/ui-updates
tinyrelay / tinyclient / social_pages_test.go
social_pages_test.go 5.8K
1package tinyclient23import (4 "context"5 "encoding/json"6 "net/http"7 "net/http/httptest"8 "strings"9 "testing"1011 "github.com/FelineStateMachine/tinyrelay/internal/policy"12)1314type socialBackend struct {15 fakeBackend16 method string17 query map[string]any18}1920func (b *socialBackend) Query(ctx context.Context, method string, params []json.RawMessage, actor string) (any, error) {21 if method != "browsesocial" && method != "browsesocialthread" {22 return b.fakeBackend.Query(ctx, method, params, actor)23 }24 b.method = method25 if len(params) > 0 {26 if err := json.Unmarshal(params[0], &b.query); err != nil {27 return nil, err28 }29 }30 post := map[string]any{"id": strings.Repeat("b", 64), "kind": 1, "pubkey": strings.Repeat("a", 64), "created_at": 1789000000, "content": "A small moment worth sharing.", "author_name": "River", "author_picture": "https://example.com/avatar.png", "reply_count": 2, "reactions": []any{map[string]any{"content": "+", "count": 3, "reacted": false}, map[string]any{"content": ":party:", "image": "https://example.com/party.png", "count": 1}}, "read_path": "/social/" + strings.Repeat("b", 64)}31 if method == "browsesocialthread" {32 return map[string]any{"post": post, "comments": []any{}, "next_cursor": ""}, nil33 }34 return map[string]any{"items": []any{post}, "next_cursor": "older-page", "stats": map[string]any{"notes": 1, "articles": 0, "comments": 2}}, nil35}3637func TestSocialPageRendersFeedWithoutJavaScript(t *testing.T) {38 b := &socialBackend{fakeBackend: fakeBackend{policy: policy.Defaults(strings.Repeat("a", 64))}}39 a, err := New(b, Options{})40 if err != nil {41 t.Fatal(err)42 }43 r := httptest.NewRecorder()44 a.ServeHTTP(r, httptest.NewRequest(http.MethodGet, "/social?kind=notes&author="+strings.Repeat("a", 64)+"&cursor=older&q=moment", nil))45 for _, want := range []string{"Social", "A small moment worth sharing.", "River", "/social", "older-page", `data-reaction-emoji`, `alt=":party:"`} {46 if r.Code != http.StatusOK || !strings.Contains(r.Body.String(), want) {47 t.Fatalf("missing %q: status=%d body=%s", want, r.Code, r.Body.String())48 }49 }50 if b.method != "browsesocial" || b.query["kind"] != "notes" || b.query["cursor"] != "older" || b.query["q"] != "moment" {51 t.Fatalf("query: %s %#v", b.method, b.query)52 }53}5455func TestSocialThreadAndLegacyArticleRoute(t *testing.T) {56 b := &socialBackend{fakeBackend: fakeBackend{policy: policy.Defaults(strings.Repeat("a", 64))}}57 a, err := New(b, Options{})58 if err != nil {59 t.Fatal(err)60 }61 r := httptest.NewRecorder()62 a.ServeHTTP(r, httptest.NewRequest(http.MethodGet, "/social/"+strings.Repeat("b", 64), nil))63 if r.Code != http.StatusOK || b.method != "browsesocialthread" || b.query["id"] != strings.Repeat("b", 64) || !strings.Contains(r.Body.String(), "A small moment worth sharing.") {64 t.Fatalf("thread: %d %s", r.Code, r.Body.String())65 }66 r = httptest.NewRecorder()67 a.ServeHTTP(r, httptest.NewRequest(http.MethodGet, "/articles?author=alice", nil))68 if r.Code != http.StatusMovedPermanently || r.Header().Get("Location") != "/social?author=alice" {69 t.Fatalf("legacy: %d %s", r.Code, r.Header().Get("Location"))70 }71}7273func TestNostrReferenceRoutesUseSocialThread(t *testing.T) {74 owner := strings.Repeat("a", 64)75 b := &socialBackend{fakeBackend: fakeBackend{policy: policy.Defaults(owner)}}76 a, err := New(b, Options{})77 if err != nil {78 t.Fatal(err)79 }80 id := strings.Repeat("b", 64)81 r := httptest.NewRecorder()82 a.ServeHTTP(r, httptest.NewRequest(http.MethodGet, "/e/"+id, nil))83 if r.Code != http.StatusOK || b.method != "browsesocialthread" || b.query["id"] != id || !strings.Contains(r.Body.String(), "A small moment worth sharing.") {84 t.Fatalf("event reference: status=%d method=%s query=%#v", r.Code, b.method, b.query)85 }8687 b.method, b.query = "", nil88 address := "30023:" + owner + ":hello"89 r = httptest.NewRecorder()90 a.ServeHTTP(r, httptest.NewRequest(http.MethodGet, "/a/"+address, nil))91 if r.Code != http.StatusOK || b.method != "browsesocialthread" || b.query["address"] != address {92 t.Fatalf("article reference: status=%d method=%s query=%#v", r.Code, b.method, b.query)93 }94}9596func TestNostrReferenceRoutesKeepLegacyFallback(t *testing.T) {97 owner := strings.Repeat("a", 64)98 b := &fakeBackend{policy: policy.Defaults(owner)}99 a, err := New(b, Options{})100 if err != nil {101 t.Fatal(err)102 }103 r := httptest.NewRecorder()104 a.ServeHTTP(r, httptest.NewRequest(http.MethodGet, "/a/legacy-name", nil))105 if r.Code != http.StatusOK || b.call != "queryevents:" {106 t.Fatalf("legacy article fallback: status=%d call=%q", r.Code, b.call)107 }108}109110func TestSocialRespectsPagesFeature(t *testing.T) {111 b := &socialBackend{fakeBackend: fakeBackend{policy: policy.Defaults(strings.Repeat("a", 64))}}112 b.policy.Features.Pages = false113 a, err := New(b, Options{})114 if err != nil {115 t.Fatal(err)116 }117 for _, path := range []string{"/social", "/social/" + strings.Repeat("b", 64), "/social.json", "/articles", "/social/profile", "/social/podcasts.rss"} {118 r := httptest.NewRecorder()119 a.ServeHTTP(r, httptest.NewRequest(http.MethodGet, path, nil))120 if r.Code != http.StatusNotFound {121 t.Fatalf("%s status %d", path, r.Code)122 }123 }124}125126func TestSocialContextLinksToParentAndRoot(t *testing.T) {127 root, parent := strings.Repeat("a", 64), strings.Repeat("b", 64)128 data := PageData{Tab: "social-thread", Event: map[string]any{"post": map[string]any{"id": root}}}129 row := socialContext(map[string]any{"id": strings.Repeat("c", 64), "parent_id": parent, "root_id": root}, data)130 if row["parent_url"] != "/social/"+parent || row["root_url"] != "/social/"+root || row["nested"] != true {131 t.Fatalf("missing nested context: %#v", row)132 }133 address := "30023:" + root + ":essay:part-one"134 row = socialContext(map[string]any{"id": parent, "parent_id": root, "parent_address": address, "root_address": address}, data)135 if row["nested"] != false || !strings.HasPrefix(plainString(row["parent_url"]), "/social/30023:") {136 t.Fatalf("missing article parent: %#v", row)137 }138}