From 41955a06011db64a5fd4431030bc86cac8666906 Mon Sep 17 00:00:00 2001 From: fiatjaf Date: Wed, 9 Feb 2022 13:40:26 -0300 Subject: [PATCH] serialization tests for events and filters. --- event.go | 11 ++++-- event_test.go | 34 ++++++++++++++++ filter_test.go | 105 +++++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 4 +- 4 files changed, 149 insertions(+), 5 deletions(-) create mode 100644 event_test.go create mode 100644 filter_test.go diff --git a/event.go b/event.go index 7d63f39..690aa0f 100644 --- a/event.go +++ b/event.go @@ -38,14 +38,13 @@ type Event struct { type Time time.Time func (tm *Time) UnmarshalJSON(payload []byte) error { - var unix int64 - err := json.Unmarshal(payload, &unix) + unix, err := strconv.ParseInt(string(payload), 10, 64) if err != nil { return fmt.Errorf("time must be a unix timestamp as an integer, not '%s': %w", string(payload), err) } t := Time(time.Unix(unix, 0)) - tm = &t + *tm = t return nil } @@ -53,6 +52,12 @@ func (t Time) MarshalJSON() ([]byte, error) { return []byte(strconv.FormatInt(time.Time(t).Unix(), 10)), nil } +// GetID serializes and returns the event ID as a string +func (evt *Event) GetID() string { + h := sha256.Sum256(evt.Serialize()) + return hex.EncodeToString(h[:]) +} + // Serialize outputs a byte array that can be hashed/signed to identify/authenticate func (evt *Event) Serialize() []byte { // the serialization process is just putting everything into a JSON array diff --git a/event_test.go b/event_test.go new file mode 100644 index 0000000..7be0282 --- /dev/null +++ b/event_test.go @@ -0,0 +1,34 @@ +package nostr + +import ( + "encoding/json" + "testing" +) + +func TestEventParsingAndVerifying(t *testing.T) { + raw := `{"id":"dc90c95f09947507c1044e8f48bcf6350aa6bff1507dd4acfc755b9239b5c962","pubkey":"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d","created_at":1644271588,"kind":1,"tags":[],"content":"now that https://blueskyweb.org/blog/2-7-2022-overview was announced we can stop working on nostr?","sig":"230e9d8f0ddaf7eb70b5f7741ccfa37e87a455c9a469282e3464e2052d3192cd63a167e196e381ef9d7e69e9ea43af2443b839974dc85d8aaab9efe1d9296524"}` + + var ev Event + err := json.Unmarshal([]byte(raw), &ev) + if err != nil { + t.Errorf("failed to parse event json: %w", err) + } + + if ev.GetID() != ev.ID { + t.Errorf("error serializing event id: %s != %s", ev.GetID(), ev.ID) + } + + if ok, _ := ev.CheckSignature(); !ok { + t.Error("signature verification failed when it should have succeeded") + } + + asjson, err := json.Marshal(ev) + if err != nil { + t.Errorf("failed to re marshal event as json: %w", err) + } + + if string(asjson) != raw { + t.Log(string(asjson)) + t.Error("json serialization broken") + } +} diff --git a/filter_test.go b/filter_test.go new file mode 100644 index 0000000..7a6c212 --- /dev/null +++ b/filter_test.go @@ -0,0 +1,105 @@ +package nostr + +import ( + "encoding/json" + "testing" + "time" +) + +func TestFilterUnmarshal(t *testing.T) { + raw := `{"ids": ["abc"],"#e":["zzz"],"#something":["nothing","bab"],"since":1644254609}` + var f Filter + err := json.Unmarshal([]byte(raw), &f) + if err != nil { + t.Errorf("failed to parse filter json: %w", err) + } + + if f.Since == nil || f.Since.Format("2006-01-02") != "2022-02-07" || + f.Until != nil || + f.Tags == nil || len(f.Tags) != 2 || !f.Tags["something"].Contains("bab") { + t.Error("failed to parse filter correctly") + } +} + +func TestFilterMarshal(t *testing.T) { + tm := time.Unix(12345678, 0) + + filterj, err := json.Marshal(Filter{ + Kinds: IntList{1, 2, 4}, + Tags: map[string]StringList{"fruit": {"banana", "mango"}}, + Until: &tm, + }) + if err != nil { + t.Errorf("failed to marshal filter json: %w", err) + } + + expected := `{"kinds":[1,2,4],"until":12345678,"#fruit":["banana","mango"]}` + if string(filterj) != expected { + t.Errorf("filter json was wrong: %s != %s", string(filterj), expected) + } +} + +func TestFilterMatching(t *testing.T) { + if (Filter{Kinds: IntList{4, 5}}).Matches(&Event{Kind: 6}) { + t.Error("matched event that shouldn't have matched") + } + + if !(Filter{Kinds: IntList{4, 5}}).Matches(&Event{Kind: 4}) { + t.Error("failed to match event by kind") + } + + if !(Filter{ + Kinds: IntList{4, 5}, + Tags: map[string]StringList{ + "p": {"ooo"}, + }, + IDs: StringList{"prefix"}, + }).Matches(&Event{ + Kind: 4, + Tags: Tags{{"p", "ooo", ",x,x,"}, {"m", "yywyw", "xxx"}}, + ID: "prefix123", + }) { + t.Error("failed to match event by kind+tags+id prefix") + } +} + +func TestFilterEquality(t *testing.T) { + if !FilterEqual( + Filter{Kinds: IntList{4, 5}}, + Filter{Kinds: IntList{4, 5}}, + ) { + t.Error("kinds filters should be equal") + } + + if !FilterEqual( + Filter{Kinds: IntList{4, 5}, Tags: map[string]StringList{"letter": {"a", "b"}}}, + Filter{Kinds: IntList{4, 5}, Tags: map[string]StringList{"letter": {"b", "a"}}}, + ) { + t.Error("kind+tags filters should be equal") + } + + tm := time.Now() + if !FilterEqual( + Filter{ + Kinds: IntList{4, 5}, + Tags: map[string]StringList{"letter": {"a", "b"}, "fruit": {"banana"}}, + Since: &tm, + IDs: StringList{"aaaa", "bbbb"}, + }, + Filter{ + Kinds: IntList{5, 4}, + Tags: map[string]StringList{"letter": {"a", "b"}, "fruit": {"banana"}}, + Since: &tm, + IDs: StringList{"aaaa", "bbbb"}, + }, + ) { + t.Error("kind+2tags+since+ids filters should be equal") + } + + if FilterEqual( + Filter{Kinds: IntList{1, 4, 5}}, + Filter{Kinds: IntList{4, 5, 6}}, + ) { + t.Error("kinds filters shouldn't be equal") + } +} diff --git a/go.mod b/go.mod index cabf8b9..a13b3d3 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,10 @@ module github.com/fiatjaf/go-nostr go 1.15 require ( - github.com/btcsuite/btcd v0.20.1-beta // indirect + github.com/btcsuite/btcd v0.20.1-beta github.com/fiatjaf/bip340 v1.1.1 github.com/gorilla/websocket v1.4.2 github.com/tyler-smith/go-bip32 v1.0.0 github.com/tyler-smith/go-bip39 v1.1.0 - github.com/valyala/fastjson v1.6.3 // indirect + github.com/valyala/fastjson v1.6.3 )