package protowire import ( "testing" "git.smesh.lol/musiquay/pkg/protocol" "git.smesh.lol/nostr/pkg/event" "git.smesh.lol/nostr/pkg/signer/p8k" "git.smesh.lol/nostr/pkg/tag" ) // pwTags wraps a protocol tag list in the *tag.S an event carries. The // protocol builds exactly-sized rows, so each row is copied into a tag with // the same capacity. func pwTags(rows [][]string) (s *tag.S) { s = tag.NewSWithCap(int32(len(rows))) for i := range rows { tt := tag.NewWithCap(int32(len(rows[i]))) for j := range rows[i] { tt.T = push(tt.T, rows[i][j]) } s.T = push(s.T, tt) } return } func pwSigner(t *testing.T) (s *p8k.Signer) { t.Helper() s = p8k.MustNew() if err := s.Generate(); err != nil { t.Fatal(err) } return } func TestTagsOfRoundTrip(t *testing.T) { s := pwSigner(t) rows := [][]string{ []string{"d", "isrc-1"}, []string{"title", "A Song"}, []string{"x", "abc123"}, } ev := &event.E{ CreatedAt: 1700000000, Kind: protocol.KindTrack, Tags: pwTags(rows), Content: []byte("liner notes"), } if err := ev.Sign(s); err != nil { t.Fatal(err) } got := TagsOf(ev) if len(got) != len(rows) { t.Fatalf("TagsOf length = %d, want %d", int32(len(got)), int32(len(rows))) } for i := range rows { if len(got[i]) != len(rows[i]) { t.Fatalf("tag %d length = %d, want %d", int32(i), int32(len(got[i])), int32(len(rows[i]))) } for j := range rows[i] { if got[i][j] != rows[i][j] { t.Fatalf("tag %d elem %d = %q, want %q", int32(i), int32(j), got[i][j], rows[i][j]) } } } } func TestValidateEventTrack(t *testing.T) { good := &protocol.Track{D: "isrc-1", Title: "Song"} ev := &event.E{Kind: protocol.KindTrack, Tags: pwTags(good.Tags()), Content: []byte("")} if err := ValidateEvent(ev); err != nil { t.Fatalf("valid track rejected: %s", err.Error()) } // music.mx's Track.Validate rejects an empty d tag; Tags() omits the tag // entirely when D is empty. bad := &protocol.Track{Title: "No identity"} ev2 := &event.E{Kind: protocol.KindTrack, Tags: pwTags(bad.Tags()), Content: []byte("")} if err := ValidateEvent(ev2); err == nil { t.Fatal("track with an empty d tag was accepted") } } func TestValidateEventUnparsedKind(t *testing.T) { // The protocol defines 31922 but has no ParseCalendarDay; a payload for it // must pass through, not be rejected by a parser that does not exist. ev := &event.E{Kind: protocol.KindCalendarDay, Content: []byte("anything")} if err := ValidateEvent(ev); err != nil { t.Fatalf("unparsed kind rejected: %s", err.Error()) } // A kind outside the vocabulary passes too. ev2 := &event.E{Kind: 1, Content: []byte("note")} if err := ValidateEvent(ev2); err != nil { t.Fatalf("foreign kind rejected: %s", err.Error()) } } func TestValidateEventContentParsedKind(t *testing.T) { good := &event.E{Kind: protocol.KindProduct, Content: []byte(`{"id":"p1","stall_id":"s1","name":"Vinyl","currency":"USD","price":20}`)} if err := ValidateEvent(good); err != nil { t.Fatalf("valid product rejected: %s", err.Error()) } bad := &event.E{Kind: protocol.KindProduct, Content: []byte("not json")} if err := ValidateEvent(bad); err == nil { t.Fatal("product with non-JSON content was accepted") } } // pwHex64 is a valid 64-lowercase-hex pubkey for address fixtures. func pwHex64() (s string) { return "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" } func TestAssetOf(t *testing.T) { pub := pwHex64() asset := &protocol.Address{Kind: protocol.KindTrack, Pubkey: pub, D: "track-d"} addr := asset.String() track := &event.E{ Kind: protocol.KindTrack, Tags: pwTags([][]string{[]string{"a", addr}}), } gotTrack, okTrack := AssetOf(track) if !okTrack || gotTrack != addr { t.Fatalf("AssetOf(track) = %q, %v", gotTrack, okTrack) } receipt := &event.E{ Kind: protocol.KindDeliveryReceipt, Tags: pwTags([][]string{[]string{"a", addr}}), } gotRec, okRec := AssetOf(receipt) if !okRec || gotRec != addr { t.Fatalf("AssetOf(receipt) = %q, %v", gotRec, okRec) } filter := &event.E{ Kind: protocol.KindAccessFilter, Tags: pwTags([][]string{[]string{"d", protocol.FanFilterD(asset)}}), } gotFilt, okFilt := AssetOf(filter) if !okFilt || gotFilt != addr { t.Fatalf("AssetOf(filter) = %q, %v", gotFilt, okFilt) } // A subscription filter names no asset. sub := &event.E{ Kind: protocol.KindAccessFilter, Tags: pwTags([][]string{[]string{"d", protocol.SubFilterD(pub, "channel")}}), } gotSub, okSub := AssetOf(sub) if okSub { t.Fatalf("subscription filter reported asset %q", gotSub) } // The wrong kind has no asset even with an a tag. album := &event.E{ Kind: protocol.KindAlbum, Tags: pwTags([][]string{[]string{"a", addr}}), } gotAlbum, okAlbum := AssetOf(album) if okAlbum { t.Fatalf("album reported asset %q", gotAlbum) } }