| 1 | package daemon |
| 2 | |
| 3 | import ( |
| 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 | |
| 13 | // These fixtures use the wire shapes from NIP-10 and NIP-22 rather than the |
| 14 | // relay's internal metadata vocabulary. They are intentionally signed and |
| 15 | // executed through the tenant RPC so filtering, moderation and visibility are |
| 16 | // exercised together. |
| 17 | func TestSocialProtocolReferencesAndVisibility(t *testing.T) { |
| 18 | _, tenant := testTenant(t) |
| 19 | owner := tenant.Policy().Owner |
| 20 | authorSecret := strings.Repeat("1" , 63) + "2" |
| 21 | commenterSecret := strings.Repeat("3" , 63) + "4" |
| 22 | note := signedEvent(t, authorSecret, 1, 500, nil, "root note" ) |
| 23 | reply := signedEvent(t, commenterSecret, 1, 501, [][]string{{"e" , note.ID, "" , "root" , note.PubKey}}, "reply" ) |
| 24 | nested := signedEvent(t, authorSecret, 1, 502, [][]string{{"e" , note.ID, "" , "root" , note.PubKey}, {"e" , reply.ID, "" , "reply" , reply.PubKey}}, "nested" ) |
| 25 | article := signedEvent(t, authorSecret, 30023, 600, [][]string{{"d" , "essay:edition" }, {"title" , "Current" }}, "article" ) |
| 26 | address := "30023:" + article.PubKey + ":essay:edition" |
| 27 | articleComment := signedEvent(t, commenterSecret, 1111, 601, [][]string{ |
| 28 | {"A" , address}, {"K" , "30023" }, {"P" , article.PubKey}, |
| 29 | {"a" , address}, {"k" , "30023" }, {"p" , article.PubKey}, |
| 30 | }, "article comment" ) |
| 31 | reaction := signedEvent(t, commenterSecret, 7, 602, [][]string{{"e" , article.ID}, {"a" , address}}, "" ) |
| 32 | unrelated := signedEvent(t, commenterSecret, 1111, 603, [][]string{{"E" , strings.Repeat("f" , 64)}, {"K" , "30023" }}, "unrelated" ) |
| 33 | for _, row := range []event.Event{note, reply, nested, article, articleComment, reaction, unrelated} { |
| 34 | if _, err := tenant.store.Save(context.Background(), row, storage.SaveOptions{Now: row.CreatedAt, SearchMode: tenant.Policy().Features.Search}); err != nil { |
| 35 | t.Fatal(err) |
| 36 | } |
| 37 | } |
| 38 | value, err := tenant.Execute(context.Background(), owner, "browsesocial" , []json.RawMessage{rawJSON(map[string]any{"limit" : 20})}) |
| 39 | if err != nil { |
| 40 | t.Fatal(err) |
| 41 | } |
| 42 | items := value.(map[string]any)["items" ].([]socialItem) |
| 43 | if len(items) != 2 || items[0].Address != address || items[1].ID != note.ID { |
| 44 | t.Fatalf("feed roots = %#v" , items) |
| 45 | } |
| 46 | if items[0].ReplyCount != 1 || len(items[0].Reactions) != 1 || items[0].Reactions[0].Content != "+" { |
| 47 | t.Fatalf("article folding = %#v" , items[0]) |
| 48 | } |
| 49 | detail, err := tenant.Execute(context.Background(), owner, "browsesocialthread" , []json.RawMessage{rawJSON(map[string]any{"id" : nested.ID, "limit" : 20})}) |
| 50 | if err != nil { |
| 51 | t.Fatal(err) |
| 52 | } |
| 53 | selected := detail.(map[string]any)["post" ].(socialItem) |
| 54 | if selected.ParentID != reply.ID || selected.RootID != note.ID { |
| 55 | t.Fatalf("thread metadata = %#v" , selected) |
| 56 | } |
| 57 | rootDetail, err := tenant.Execute(context.Background(), owner, "browsesocialthread" , []json.RawMessage{rawJSON(map[string]any{"id" : note.ID})}) |
| 58 | if err != nil { |
| 59 | t.Fatal(err) |
| 60 | } |
| 61 | comments := rootDetail.(map[string]any)["comments" ].([]socialItem) |
| 62 | if len(comments) != 2 || comments[0].ParentID != reply.ID { |
| 63 | t.Fatalf("root conversation = %#v" , comments) |
| 64 | } |
| 65 | articleDetail, err := tenant.Execute(context.Background(), owner, "browsesocialthread" , []json.RawMessage{rawJSON(map[string]any{"address" : address, "limit" : 20})}) |
| 66 | if err != nil { |
| 67 | t.Fatal(err) |
| 68 | } |
| 69 | if len(articleDetail.(map[string]any)["comments" ].([]socialItem)) != 1 { |
| 70 | t.Fatal("article comment missing" ) |
| 71 | } |
| 72 | } |