tinyrelayfile
tinyrelay/internal/daemon/social_regression_test.go/

internal/daemon/social_regression_test.go raw
1package daemon
2
3import (
4 "context"
5 "encoding/json"
6 "strings"
7 "testing"
8
9 "github.com/FelineStateMachine/tinyrelay/internal/event"
10 "github.com/FelineStateMachine/tinyrelay/internal/storage"
11)
12
13func saveSocialRegression(t *testing.T, tenant *Tenant, rows ...event.Event) {
14 t.Helper()
15 for _, row := range rows {
16 if _, err := tenant.store.Save(context.Background(), row, storage.SaveOptions{Now: row.CreatedAt, SearchMode: tenant.Policy().Features.Search}); err != nil {
17 t.Fatal(err)
18 }
19 }
20}
21
22func socialRegressionBrowse(t *testing.T, tenant *Tenant, params map[string]any) map[string]any {
23 return socialRegressionBrowseAs(t, tenant, tenant.Policy().Owner, params)
24}
25
26func socialRegressionBrowseAs(t *testing.T, tenant *Tenant, actor string, params map[string]any) map[string]any {
27 t.Helper()
28 value, err := tenant.Execute(context.Background(), actor, "browsesocial", []json.RawMessage{rawJSON(params)})
29 if err != nil {
30 t.Fatal(err)
31 }
32 return value.(map[string]any)
33}
34
35func TestSocialRegressionHiddenActivityAndProfilesAreNotFolded(t *testing.T) {
36 _, tenant := testTenant(t)
37 secret := strings.Repeat("a", 63) + "b"
38 note := signedEvent(t, secret, 1, 100, nil, "visible note")
39 comment := signedEvent(t, secret, 1, 101, [][]string{{"e", note.ID, "", "root", note.PubKey}}, "hidden comment")
40 reaction := signedEvent(t, secret, 7, 102, [][]string{{"e", note.ID}, {"p", note.PubKey}}, "+")
41 profile := signedEvent(t, secret, 0, 103, nil, `{"name":"leaked name","picture":"https://example.test/avatar.png"}`)
42 saveSocialRegression(t, tenant, note, comment, reaction, profile)
43 visible := socialRegressionBrowse(t, tenant, map[string]any{"limit": 10})["items"].([]socialItem)
44 if len(visible) != 1 || visible[0].ReplyCount != 1 || len(visible[0].Reactions) != 1 || visible[0].AuthorName != "leaked name" {
45 t.Fatalf("visible activity was not folded before hiding: %#v", visible)
46 }
47 ctx := context.Background()
48 for _, row := range []event.Event{comment, reaction, profile} {
49 if _, err := tenant.store.DB().ExecContext(ctx, `INSERT INTO hidden_events(id,reason) VALUES(?,'regression')`, row.ID); err != nil {
50 t.Fatal(err)
51 }
52 }
53 result := socialRegressionBrowse(t, tenant, map[string]any{"limit": 10})
54 items := result["items"].([]socialItem)
55 if len(items) != 1 || items[0].ID != note.ID {
56 t.Fatalf("items = %#v", items)
57 }
58 if items[0].ReplyCount != 0 || len(items[0].Reactions) != 0 {
59 t.Fatalf("hidden activity folded into note: %#v", items[0])
60 }
61 if items[0].AuthorName == "leaked name" || items[0].AuthorPicture != "" {
62 t.Fatalf("hidden profile leaked: %#v", items[0])
63 }
64 tenant.policy.Reads = "members"
65 _, err := tenant.Execute(context.Background(), "", "browsesocial", []json.RawMessage{rawJSON(map[string]any{"limit": 10})})
66 if err == nil || !strings.Contains(err.Error(), "members-only") {
67 t.Fatalf("members-only social feed guest error = %v", err)
68 }
69}
70
71func TestSocialRegressionSearchCursorCrossesSparsePages(t *testing.T) {
72 _, tenant := testTenant(t)
73 secret := strings.Repeat("b", 63) + "c"
74 newest := signedEvent(t, secret, 1, 1000, nil, "needle newest")
75 rows := []event.Event{newest}
76 for at := int64(999); at >= 850; at-- {
77 rows = append(rows, signedEvent(t, secret, 1, at, nil, "filler"))
78 }
79 oldest := signedEvent(t, secret, 1, 1, nil, "needle oldest")
80 rows = append(rows, oldest)
81 saveSocialRegression(t, tenant, rows...)
82 first := socialRegressionBrowse(t, tenant, map[string]any{"q": "needle", "limit": 1})
83 items := first["items"].([]socialItem)
84 if len(items) != 1 || items[0].ID != newest.ID || first["next_cursor"] == "" {
85 t.Fatalf("first search page = %#v", first)
86 }
87 second := socialRegressionBrowse(t, tenant, map[string]any{"q": "needle", "limit": 1, "cursor": first["next_cursor"]})
88 items = second["items"].([]socialItem)
89 if len(items) != 1 || items[0].ID != oldest.ID {
90 t.Fatalf("second search page = %#v", second)
91 }
92}
93
94func TestSocialRegressionAddressRevisionKeepsCommentsAndReactions(t *testing.T) {
95 _, tenant := testTenant(t)
96 authorSecret := strings.Repeat("c", 63) + "d"
97 commenterSecret := strings.Repeat("e", 63) + "f"
98 old := signedEvent(t, authorSecret, 30023, 100, [][]string{{"d", "release"}, {"title", "Old"}}, "old")
99 current := signedEvent(t, authorSecret, 30023, 200, [][]string{{"d", "release"}, {"title", "Current"}}, "current")
100 address := "30023:" + current.PubKey + ":release"
101 comment := signedEvent(t, commenterSecret, 1111, 201, [][]string{{"A", address}, {"K", "30023"}, {"P", current.PubKey}, {"a", address}, {"k", "30023"}, {"p", current.PubKey}}, "comment survives revision")
102 reaction := signedEvent(t, commenterSecret, 7, 202, [][]string{{"a", address}, {"e", old.ID}}, "+")
103 saveSocialRegression(t, tenant, old, current, comment, reaction)
104 result := socialRegressionBrowse(t, tenant, map[string]any{"kind": "articles", "limit": 10})
105 items := result["items"].([]socialItem)
106 if len(items) != 1 || items[0].Address != address || items[0].Title != "Current" {
107 t.Fatalf("canonical article = %#v", items)
108 }
109 if items[0].ReplyCount != 1 || len(items[0].Reactions) != 1 || items[0].Reactions[0].Count != 1 {
110 t.Fatalf("address activity lost: %#v", items[0])
111 }
112}
113
114func TestSocialRegressionNIP10MentionIsNotReply(t *testing.T) {
115 _, tenant := testTenant(t)
116 secret := strings.Repeat("d", 63) + "e"
117 mentioned := signedEvent(t, secret, 1, 100, nil, "mentioned")
118 note := signedEvent(t, secret, 1, 101, [][]string{{"e", mentioned.ID, "", "mention", mentioned.PubKey}, {"q", mentioned.ID}, {"p", mentioned.PubKey}}, "mention only")
119 saveSocialRegression(t, tenant, mentioned, note)
120 result := socialRegressionBrowse(t, tenant, map[string]any{"limit": 10})
121 items := result["items"].([]socialItem)
122 if len(items) != 2 {
123 t.Fatalf("mention-only note omitted: %#v", items)
124 }
125 for _, item := range items {
126 if item.ID == note.ID && (item.ParentID != "" || item.RootID != note.ID) {
127 t.Fatalf("mention-only note treated as reply: %#v", item)
128 }
129 }
130}

About

ownerc01503e8035f
visibilitypublic
headd730679639bf

Maintainers

  • c01503e8035f owner
  • ba1b5beed5b5 agent hermes

Clone

  • https://012.run/npub1cq2s86qrtadf36eqq3epm38wmhdynrrcwxf84eldk3f94dkv7ttsrulrz5/tinyrelay.git

Refs