protowire_test.mx raw
1 package protowire
2
3 import (
4 "testing"
5
6 "git.smesh.lol/musiquay/pkg/protocol"
7 "git.smesh.lol/nostr/pkg/event"
8 "git.smesh.lol/nostr/pkg/signer/p8k"
9 "git.smesh.lol/nostr/pkg/tag"
10 )
11
12 // pwTags wraps a protocol tag list in the *tag.S an event carries. The
13 // protocol builds exactly-sized rows, so each row is copied into a tag with
14 // the same capacity.
15 func pwTags(rows [][]string) (s *tag.S) {
16 s = tag.NewSWithCap(int32(len(rows)))
17 for i := range rows {
18 tt := tag.NewWithCap(int32(len(rows[i])))
19 for j := range rows[i] {
20 tt.T = push(tt.T, rows[i][j])
21 }
22 s.T = push(s.T, tt)
23 }
24 return
25 }
26
27 func pwSigner(t *testing.T) (s *p8k.Signer) {
28 t.Helper()
29 s = p8k.MustNew()
30 if err := s.Generate(); err != nil {
31 t.Fatal(err)
32 }
33 return
34 }
35
36 func TestTagsOfRoundTrip(t *testing.T) {
37 s := pwSigner(t)
38 rows := [][]string{
39 []string{"d", "isrc-1"},
40 []string{"title", "A Song"},
41 []string{"x", "abc123"},
42 }
43 ev := &event.E{
44 CreatedAt: 1700000000,
45 Kind: protocol.KindTrack,
46 Tags: pwTags(rows),
47 Content: []byte("liner notes"),
48 }
49 if err := ev.Sign(s); err != nil {
50 t.Fatal(err)
51 }
52
53 got := TagsOf(ev)
54 if len(got) != len(rows) {
55 t.Fatalf("TagsOf length = %d, want %d", int32(len(got)), int32(len(rows)))
56 }
57 for i := range rows {
58 if len(got[i]) != len(rows[i]) {
59 t.Fatalf("tag %d length = %d, want %d", int32(i), int32(len(got[i])), int32(len(rows[i])))
60 }
61 for j := range rows[i] {
62 if got[i][j] != rows[i][j] {
63 t.Fatalf("tag %d elem %d = %q, want %q", int32(i), int32(j), got[i][j], rows[i][j])
64 }
65 }
66 }
67 }
68
69 func TestValidateEventTrack(t *testing.T) {
70 good := &protocol.Track{D: "isrc-1", Title: "Song"}
71 ev := &event.E{Kind: protocol.KindTrack, Tags: pwTags(good.Tags()), Content: []byte("")}
72 if err := ValidateEvent(ev); err != nil {
73 t.Fatalf("valid track rejected: %s", err.Error())
74 }
75
76 // music.mx's Track.Validate rejects an empty d tag; Tags() omits the tag
77 // entirely when D is empty.
78 bad := &protocol.Track{Title: "No identity"}
79 ev2 := &event.E{Kind: protocol.KindTrack, Tags: pwTags(bad.Tags()), Content: []byte("")}
80 if err := ValidateEvent(ev2); err == nil {
81 t.Fatal("track with an empty d tag was accepted")
82 }
83 }
84
85 func TestValidateEventUnparsedKind(t *testing.T) {
86 // The protocol defines 31922 but has no ParseCalendarDay; a payload for it
87 // must pass through, not be rejected by a parser that does not exist.
88 ev := &event.E{Kind: protocol.KindCalendarDay, Content: []byte("anything")}
89 if err := ValidateEvent(ev); err != nil {
90 t.Fatalf("unparsed kind rejected: %s", err.Error())
91 }
92 // A kind outside the vocabulary passes too.
93 ev2 := &event.E{Kind: 1, Content: []byte("note")}
94 if err := ValidateEvent(ev2); err != nil {
95 t.Fatalf("foreign kind rejected: %s", err.Error())
96 }
97 }
98
99 func TestValidateEventContentParsedKind(t *testing.T) {
100 good := &event.E{Kind: protocol.KindProduct, Content: []byte(`{"id":"p1","stall_id":"s1","name":"Vinyl","currency":"USD","price":20}`)}
101 if err := ValidateEvent(good); err != nil {
102 t.Fatalf("valid product rejected: %s", err.Error())
103 }
104 bad := &event.E{Kind: protocol.KindProduct, Content: []byte("not json")}
105 if err := ValidateEvent(bad); err == nil {
106 t.Fatal("product with non-JSON content was accepted")
107 }
108 }
109
110 // pwHex64 is a valid 64-lowercase-hex pubkey for address fixtures.
111 func pwHex64() (s string) {
112 return "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
113 }
114
115 func TestAssetOf(t *testing.T) {
116 pub := pwHex64()
117 asset := &protocol.Address{Kind: protocol.KindTrack, Pubkey: pub, D: "track-d"}
118 addr := asset.String()
119
120 track := &event.E{
121 Kind: protocol.KindTrack,
122 Tags: pwTags([][]string{[]string{"a", addr}}),
123 }
124 gotTrack, okTrack := AssetOf(track)
125 if !okTrack || gotTrack != addr {
126 t.Fatalf("AssetOf(track) = %q, %v", gotTrack, okTrack)
127 }
128
129 receipt := &event.E{
130 Kind: protocol.KindDeliveryReceipt,
131 Tags: pwTags([][]string{[]string{"a", addr}}),
132 }
133 gotRec, okRec := AssetOf(receipt)
134 if !okRec || gotRec != addr {
135 t.Fatalf("AssetOf(receipt) = %q, %v", gotRec, okRec)
136 }
137
138 filter := &event.E{
139 Kind: protocol.KindAccessFilter,
140 Tags: pwTags([][]string{[]string{"d", protocol.FanFilterD(asset)}}),
141 }
142 gotFilt, okFilt := AssetOf(filter)
143 if !okFilt || gotFilt != addr {
144 t.Fatalf("AssetOf(filter) = %q, %v", gotFilt, okFilt)
145 }
146
147 // A subscription filter names no asset.
148 sub := &event.E{
149 Kind: protocol.KindAccessFilter,
150 Tags: pwTags([][]string{[]string{"d", protocol.SubFilterD(pub, "channel")}}),
151 }
152 gotSub, okSub := AssetOf(sub)
153 if okSub {
154 t.Fatalf("subscription filter reported asset %q", gotSub)
155 }
156
157 // The wrong kind has no asset even with an a tag.
158 album := &event.E{
159 Kind: protocol.KindAlbum,
160 Tags: pwTags([][]string{[]string{"a", addr}}),
161 }
162 gotAlbum, okAlbum := AssetOf(album)
163 if okAlbum {
164 t.Fatalf("album reported asset %q", gotAlbum)
165 }
166 }
167
168