| 1 | package tinyclient |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "encoding/json" |
| 6 | "net/http" |
| 7 | "net/http/httptest" |
| 8 | "strings" |
| 9 | "testing" |
| 10 | |
| 11 | "github.com/FelineStateMachine/tinyrelay/internal/policy" |
| 12 | ) |
| 13 | |
| 14 | type publicBackend struct { |
| 15 | fakeBackend |
| 16 | eventQueries int |
| 17 | } |
| 18 | |
| 19 | func (b *publicBackend) Query(_ context.Context, method string, _ []json.RawMessage, actor string) (any, error) { |
| 20 | if method != "queryevents" { |
| 21 | return b.fakeBackend.Query(context.Background(), method, nil, actor) |
| 22 | } |
| 23 | b.eventQueries++ |
| 24 | return []map[string]any{{"id" : "event-1" , "pubkey" : actor, "content" : "visible result" }}, nil |
| 25 | } |
| 26 | |
| 27 | func TestHomeShowsRelayInformationWithoutQueryingSocialEvents(t *testing.T) { |
| 28 | owner := strings.Repeat("a" , 64) |
| 29 | for _, actor := range []string{"" , owner} { |
| 30 | backend := &publicBackend{fakeBackend: fakeBackend{policy: policy.Defaults(owner)}} |
| 31 | backend.policy.Description = "A place to connect." |
| 32 | app, err := New(backend, Options{Actor: func(*http.Request) (string, error) { return actor, nil }}) |
| 33 | if err != nil { |
| 34 | t.Fatal(err) |
| 35 | } |
| 36 | for _, path := range []string{"/" , "/?kinds=all" } { |
| 37 | recorder := httptest.NewRecorder() |
| 38 | app.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, path, nil)) |
| 39 | body := recorder.Body.String() |
| 40 | if recorder.Code != http.StatusOK || !strings.Contains(body, "A place to connect." ) || !strings.Contains(body, `href="/social"` ) { |
| 41 | t.Fatalf("home lost relay information or Social navigation: status=%d" , recorder.Code) |
| 42 | } |
| 43 | if backend.eventQueries != 0 || strings.Contains(body, "Recent activity" ) || strings.Contains(body, "visible result" ) { |
| 44 | t.Fatalf("home still loads the duplicate feed: queries=%d" , backend.eventQueries) |
| 45 | } |
| 46 | } |
| 47 | } |
| 48 | } |
| 49 | |
| 50 | type articleBackend struct{ fakeBackend } |
| 51 | |
| 52 | func (b *articleBackend) Query(_ context.Context, method string, _ []json.RawMessage, _ string) (any, error) { |
| 53 | if method == "queryevents" { |
| 54 | return []map[string]any{{"id" : strings.Repeat("b" , 64), "kind" : 30023, "created_at" : float64(1700000000), "content" : "A title\nbody" }}, nil |
| 55 | } |
| 56 | return nil, nil |
| 57 | } |
| 58 | |
| 59 | type articleDetailBackend struct{ fakeBackend } |
| 60 | |
| 61 | func (b *articleDetailBackend) Query(_ context.Context, method string, _ []json.RawMessage, _ string) (any, error) { |
| 62 | if method == "eventdetail" { |
| 63 | return map[string]any{"id" : "article-1" , "kind" : 30023, "created_at" : float64(1700000000), "pubkey" : strings.Repeat("a" , 64), "content" : "# Heading\n\nBody" }, nil |
| 64 | } |
| 65 | return nil, nil |
| 66 | } |
| 67 | |
| 68 | func TestArticleDetailRendersMarkdownContent(t *testing.T) { |
| 69 | backend := &articleDetailBackend{fakeBackend: fakeBackend{policy: policy.Defaults(strings.Repeat("a" , 64))}} |
| 70 | app, err := New(backend, Options{}) |
| 71 | if err != nil { |
| 72 | t.Fatal(err) |
| 73 | } |
| 74 | recorder := httptest.NewRecorder() |
| 75 | app.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/e/article-1" , nil)) |
| 76 | body := recorder.Body.String() |
| 77 | if recorder.Code != http.StatusOK || !strings.Contains(body, `<nostr-event id="event-article-1">` ) || !strings.Contains(body, "<article><h1>Heading</h1>" ) { |
| 78 | t.Fatalf("article detail did not render markdown within the shared event viewer: status=%d" , recorder.Code) |
| 79 | } |
| 80 | if strings.Contains(body, "<pre># Heading" ) { |
| 81 | t.Fatalf("article detail retained plaintext rendering: %s" , body) |
| 82 | } |
| 83 | } |
| 84 | |
| 85 | func TestPublicSearchAndInboxRenderBackendResults(t *testing.T) { |
| 86 | owner := strings.Repeat("a" , 64) |
| 87 | backend := &publicBackend{fakeBackend: fakeBackend{policy: policy.Defaults(owner)}} |
| 88 | app, err := New(backend, Options{Actor: func(*http.Request) (string, error) { return owner, nil }}) |
| 89 | if err != nil { |
| 90 | t.Fatal(err) |
| 91 | } |
| 92 | for _, path := range []string{"/search?q=visible" , "/inbox" , "/outbox" } { |
| 93 | recorder := httptest.NewRecorder() |
| 94 | app.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, path, nil)) |
| 95 | body := recorder.Body.String() |
| 96 | if recorder.Code != http.StatusOK || !strings.Contains(body, "visible result" ) || !strings.Contains(body, "event-1" ) { |
| 97 | t.Fatalf("%s: status=%d body=%s" , path, recorder.Code, body) |
| 98 | } |
| 99 | } |
| 100 | } |
| 101 | |
| 102 | func TestArticleFeedsContainStoredRecordsAndRespectPagesFeature(t *testing.T) { |
| 103 | backend := &articleBackend{fakeBackend: fakeBackend{policy: policy.Defaults(strings.Repeat("a" , 64))}} |
| 104 | app, err := New(backend, Options{}) |
| 105 | if err != nil { |
| 106 | t.Fatal(err) |
| 107 | } |
| 108 | for _, path := range []string{"/articles.json" , "/feed.xml" } { |
| 109 | recorder := httptest.NewRecorder() |
| 110 | app.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, path, nil)) |
| 111 | if recorder.Code != http.StatusOK || !strings.Contains(recorder.Body.String(), strings.Repeat("b" , 64)) || !strings.Contains(recorder.Body.String(), "A title" ) { |
| 112 | t.Fatalf("%s: status=%d body=%s" , path, recorder.Code, recorder.Body.String()) |
| 113 | } |
| 114 | } |
| 115 | backend.policy.Features.Pages = false |
| 116 | for _, path := range []string{"/articles.json" , "/feed.xml" , "/e/" + strings.Repeat("b" , 64), "/a/title" } { |
| 117 | recorder := httptest.NewRecorder() |
| 118 | app.ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, path, nil)) |
| 119 | if recorder.Code != http.StatusNotFound { |
| 120 | t.Fatalf("%s: status=%d" , path, recorder.Code) |
| 121 | } |
| 122 | } |
| 123 | } |