1package nostr23import (4 "bytes"5 "crypto/sha256"6 "encoding/hex"7 "encoding/json"8 "strings"9 "testing"10)1112func TestSignParseValidateCanonical(t *testing.T) {13 secret := strings.Repeat("0", 63) + "1"14 e := Event{CreatedAt: 1, Kind: 1, Tags: [][]string{{"t", "go"}}, Content: "hello"}15 if err := Sign(&e, secret); err != nil {16 t.Fatalf("sign: %v", err)17 }18 if err := Validate(e); err != nil {19 t.Fatalf("validate: %v", err)20 }21 got, err := Parse(mustJSON(t, e))22 if err != nil {23 t.Fatalf("parse: %v", err)24 }25 canonical, err := Canonical(e)26 if err != nil {27 t.Fatalf("canonical: %v", err)28 }29 if string(canonical) != string(mustJSON(t, got)) {30 t.Fatalf("canonical changed event: %s", canonical)31 }32}3334func TestFilterTagJSONAndEmptyLists(t *testing.T) {35 f, err := ParseFilter([]byte(`{"#t":["go"],"kinds":[],"limit":0}`))36 if err != nil {37 t.Fatalf("parse filter: %v", err)38 }39 if len(f.Tags["t"]) != 1 || len(f.Kinds) != 0 || f.Kinds == nil {40 t.Fatalf("filter fields lost: %#v", f)41 }42 e := Event{ID: strings.Repeat("a", 64), PubKey: strings.Repeat("b", 64), Kind: 1, Tags: [][]string{{"t", "go"}}}43 if Matches(f, e) {44 t.Fatal("empty kinds must match nothing")45 }46 f.Kinds = nil47 if !Matches(f, e) {48 t.Fatal("tag filter should match")49 }50 raw, err := json.Marshal(f)51 if err != nil || !strings.Contains(string(raw), `"#t"`) {52 t.Fatalf("marshal tags: %s (%v)", raw, err)53 }54}5556func TestSearchTermsAndKinds(t *testing.T) {57 terms := SearchTerms("hello lang:en world:")58 if len(terms) != 1 || terms[0] != "hello" {59 t.Fatalf("terms: %#v", terms)60 }61 if !IsEphemeral(20001) || IsEphemeral(30000) || !IsReplaceable(10001) || !IsAddressable(30001) {62 t.Fatal("kind classification mismatch")63 }64}6566func TestNIP01CanonicalEscapingMatchesJSONStringify(t *testing.T) {67 e := Event{CreatedAt: 1700000000, Kind: 1, Tags: [][]string{{"x", "a\b\f\u2028\u2029é"}}, Content: "quote \" slash \\ newline\n <tag>"}68 got, err := serializeForID(e)69 if err != nil {70 t.Fatal(err)71 }72 if bytes.Contains(got, []byte(`\u003c`)) || !bytes.Contains(got, []byte(`\b`)) || !bytes.Contains(got, []byte(`\f`)) {73 t.Fatalf("unexpected HTML/control escaping: %s", got)74 }75 if strings.Contains(string(got), `\u2028`) || strings.Contains(string(got), `\u2029`) {76 t.Fatalf("U+2028/U+2029 must remain literal like JSON.stringify: %s", got)77 }78}7980func TestParseRequiresEveryWireField(t *testing.T) {81 e := Event{CreatedAt: 1, Kind: 1, Tags: [][]string{}, Content: ""}82 if err := Sign(&e, strings.Repeat("0", 63)+"1"); err != nil {83 t.Fatal(err)84 }85 raw, err := json.Marshal(e)86 if err != nil {87 t.Fatal(err)88 }89 for _, field := range []string{"id", "pubkey", "created_at", "kind", "tags", "content", "sig"} {90 var values map[string]json.RawMessage91 if err := json.Unmarshal(raw, &values); err != nil {92 t.Fatal(err)93 }94 delete(values, field)95 missing, err := json.Marshal(values)96 if err != nil {97 t.Fatal(err)98 }99 if _, err := Parse(missing); err == nil {100 t.Errorf("missing %s accepted", field)101 }102 }103}104105func TestNIP01IDIsSHA256OfCanonicalArray(t *testing.T) {106 e := Event{CreatedAt: 1, Kind: 1, Tags: [][]string{}, Content: "hello"}107 if err := Sign(&e, strings.Repeat("0", 63)+"1"); err != nil {108 t.Fatal(err)109 }110 serialized, err := serializeForID(e)111 if err != nil {112 t.Fatal(err)113 }114 sum := sha256.Sum256(serialized)115 if e.ID != hex.EncodeToString(sum[:]) {116 t.Fatalf("id %s does not hash canonical array %x", e.ID, sum)117 }118}119120func mustJSON(t *testing.T, value any) []byte {121 t.Helper()122 b, err := json.Marshal(value)123 if err != nil {124 t.Fatalf("marshal: %v", err)125 }126 return b127}