package kind import "testing" type nameCase struct { k uint16 want string } type classificationCase struct { k uint16 want string } type atoiCase struct { in []byte want int32 } type itoaCase struct { n int32 want string } // TestGetKindData pins the shape of the parsed embedded table: the four range // definitions and a couple of representative entries. func TestGetKindData(t *testing.T) { kd := GetKindData() if kd.Ranges.Regular.Start != 1000 || kd.Ranges.Regular.End != 9999 { t.Fatalf("regular range = %d..%d", kd.Ranges.Regular.Start, kd.Ranges.Regular.End) } if kd.Ranges.Replaceable.Start != 10000 || kd.Ranges.Replaceable.End != 19999 { t.Fatalf("replaceable range = %d..%d", kd.Ranges.Replaceable.Start, kd.Ranges.Replaceable.End) } if kd.Ranges.Ephemeral.Start != 20000 || kd.Ranges.Ephemeral.End != 29999 { t.Fatalf("ephemeral range = %d..%d", kd.Ranges.Ephemeral.Start, kd.Ranges.Ephemeral.End) } if kd.Ranges.Parameterized.Start != 30000 || kd.Ranges.Parameterized.End != 39999 { t.Fatalf("parameterized range = %d..%d", kd.Ranges.Parameterized.Start, kd.Ranges.Parameterized.End) } if len(kd.Kinds) != 184 { t.Fatalf("kinds table length = %d", len(kd.Kinds)) } if kd.Kinds["1"].Name != "Short Text Note" { t.Fatalf("kind 1 name = %s", kd.Kinds["1"].Name) } if kd.Kinds["30023"].Name != "Long-form Content" { t.Fatalf("kind 30023 name = %s", kd.Kinds["30023"].Name) } } func TestEnsure(t *testing.T) { Ensure() kd := GetKindData() if kd == nil { t.Fatal("Ensure should materialize the table") } if GetString(1) != "Short Text Note" { t.Fatalf("kind 1 after Ensure = %s", GetString(1)) } } func TestGetKindInfo(t *testing.T) { info, ok := GetKindInfo(1) if !ok { t.Fatal("kind 1 should be known") } if info.Name != "Short Text Note" { t.Fatalf("kind 1 name = %s", info.Name) } if info.NIP == nil || *info.NIP != "01" { t.Fatal("kind 1 nip") } if info.Description != "Short-form text post (like a tweet)" { t.Fatalf("kind 1 description = %s", info.Description) } info2, ok2 := GetKindInfo(12345) if !ok2 { t.Fatal("kind 12345 should be known") } if info2.NIP != nil { t.Fatal("kind 12345 carries a null nip") } if info2.Spec != "orly" || info2.Classification != "replaceable" { t.Fatalf("kind 12345 spec=%s classification=%s", info2.Spec, info2.Classification) } if _, ok3 := GetKindInfo(65535); ok3 { t.Fatal("kind 65535 should be unknown") } } func TestGetDescription(t *testing.T) { cases := []nameCase{ {1, "Short-form text post (like a tweet)"}, {30023, "Blog post, article in markdown"}, {65535, ""}, } for _, c := range cases { if got := GetDescription(c.k); got != c.want { t.Fatalf("GetDescription(%d) = %s, want %s", c.k, got, c.want) } } } func TestGetNIP(t *testing.T) { cases := []nameCase{ {1, "01"}, {30023, "23"}, {12345, ""}, {65535, ""}, } for _, c := range cases { if got := GetNIP(c.k); got != c.want { t.Fatalf("GetNIP(%d) = %s, want %s", c.k, got, c.want) } } } func TestIsDeprecated(t *testing.T) { if !IsDeprecated(2) { t.Fatal("kind 2 is deprecated") } if !IsDeprecated(4) { t.Fatal("kind 4 is deprecated") } if IsDeprecated(1) { t.Fatal("kind 1 is not deprecated") } if IsDeprecated(65535) { t.Fatal("an unknown kind is not deprecated") } } // TestGetClassification covers the explicit classification, every numeric // range bucket and both sides of each boundary. func TestGetClassification(t *testing.T) { cases := []classificationCase{ {0, "replaceable"}, {1, "regular"}, {3, "replaceable"}, {1018, "regular"}, {10000, "replaceable"}, {19998, "replaceable"}, {19999, "regular"}, {20000, "ephemeral"}, {21000, "ephemeral"}, {29998, "ephemeral"}, {29999, "regular"}, {30000, "parameterized"}, {39999, "parameterized"}, {40000, "regular"}, {65535, "regular"}, } for _, c := range cases { if got := GetClassification(c.k); got != c.want { t.Fatalf("GetClassification(%d) = %s, want %s", c.k, got, c.want) } } // Kinds 0 and 3 carry an explicit classification, so the numeric fallback // for them is only reachable with those rows temporarily removed. kd := GetKindData() i0 := kd.Kinds["0"] i3 := kd.Kinds["3"] delete(kd.Kinds, "0") delete(kd.Kinds, "3") c0 := GetClassification(0) c3 := GetClassification(3) kd.Kinds["0"] = i0 kd.Kinds["3"] = i3 if c0 != "replaceable" { t.Fatalf("fallback classification for 0 = %s", c0) } if c3 != "replaceable" { t.Fatalf("fallback classification for 3 = %s", c3) } if GetClassification(0) != "replaceable" { t.Fatal("kind 0 classification after restore") } } func TestKindAccessors(t *testing.T) { k := New(uint16(30023)) if k.ToInt() != 30023 { t.Fatalf("ToInt = %d", k.ToInt()) } if k.ToI32() != 30023 { t.Fatalf("ToI32 = %d", k.ToI32()) } if k.ToU16() != 30023 { t.Fatalf("ToU16 = %d", k.ToU16()) } if k.ToU64() != 30023 { t.Fatalf("ToU64 = %d", k.ToU64()) } var nk *K if nk.ToInt() != 0 || nk.ToI32() != 0 || nk.ToU16() != 0 || nk.ToU64() != 0 { t.Fatal("a nil kind reads as zero") } } func TestKindName(t *testing.T) { cases := []nameCase{ {1, "Short Text Note"}, {30023, "Long-form Content"}, {65535, ""}, } for _, c := range cases { if got := string(New(c.k).Name()); got != c.want { t.Fatalf("Name(%d) = %s, want %s", c.k, got, c.want) } } } func TestKindEqual(t *testing.T) { k := New(uint16(30023)) if !k.Equal(30023) { t.Fatal("kind 30023 equals itself") } if k.Equal(1) { t.Fatal("kind 30023 does not equal 1") } var nk *K if nk.Equal(0) { t.Fatal("a nil kind equals nothing") } } func TestKindMarshalUnmarshal(t *testing.T) { k := New(uint16(30023)) if string(k.Marshal(nil)) != "30023" { t.Fatalf("Marshal = %s", string(k.Marshal(nil))) } if string(k.Marshal([]byte("k="))) != "k=30023" { t.Fatalf("Marshal into dst = %s", string(k.Marshal([]byte("k=")))) } if string(New(uint16(0)).Marshal(nil)) != "0" { t.Fatal("Marshal zero") } ku := New(uint16(0)) rem, err := ku.Unmarshal([]byte("30023rest")) if err != nil { t.Fatal(err) } if ku.K != 30023 { t.Fatalf("Unmarshal value = %d", ku.K) } if string(rem) != "rest" { t.Fatalf("Unmarshal remainder = %s", string(rem)) } bad := New(uint16(0)) if _, err2 := bad.Unmarshal(nil); err2 == nil { t.Fatal("K.Unmarshal of an empty input should fail") } } func TestGetString(t *testing.T) { if GetString(30023) != "Long-form Content" { t.Fatalf("GetString(30023) = %s", GetString(30023)) } if GetString(1) != "Short Text Note" { t.Fatalf("GetString(1) = %s", GetString(1)) } if GetString(65535) != "" { t.Fatalf("GetString(65535) = %s", GetString(65535)) } } func TestIsChannelKind(t *testing.T) { if !IsChannelKind(40) || !IsChannelKind(42) || !IsChannelKind(44) { t.Fatal("kinds 40..44 are channel kinds") } if IsChannelKind(39) || IsChannelKind(45) || IsChannelKind(65535) { t.Fatal("kinds outside 40..44 are not channel kinds") } } func TestIsDiscoverableChannelKind(t *testing.T) { if !IsDiscoverableChannelKind(40) { t.Fatal("channel creation is discoverable") } if !IsDiscoverableChannelKind(41) { t.Fatal("channel metadata is discoverable") } if IsDiscoverableChannelKind(42) { t.Fatal("channel message is not discoverable") } } func TestIsPrivileged(t *testing.T) { if !IsPrivileged(4) || !IsPrivileged(13) || !IsPrivileged(1059) || !IsPrivileged(30078) { t.Fatal("expected privileged kinds") } if IsPrivileged(1) || IsPrivileged(65535) { t.Fatal("kind 1 and unknown kinds are not privileged") } if !NewS(New(uint16(4))).IsPrivileged() { t.Fatal("S with a privileged kind") } if !NewS(New(uint16(1)), New(uint16(30078))).IsPrivileged() { t.Fatal("S with a privileged kind later in the list") } if NewS(New(uint16(1))).IsPrivileged() { t.Fatal("S without a privileged kind") } if NewS().IsPrivileged() { t.Fatal("empty S is not privileged") } } func TestIsDirectoryEvent(t *testing.T) { if !IsDirectoryEvent(0) || !IsDirectoryEvent(5) || !IsDirectoryEvent(10000) { t.Fatal("expected directory kinds") } if IsDirectoryEvent(1) || IsDirectoryEvent(65535) { t.Fatal("kind 1 and unknown kinds are not directory events") } } func TestIsEphemeral(t *testing.T) { if !IsEphemeral(20000) || !IsEphemeral(29998) { t.Fatal("kinds in the ephemeral range") } if IsEphemeral(19999) || IsEphemeral(29999) { t.Fatal("ephemeral range is half-open") } } func TestIsReplaceable(t *testing.T) { if !IsReplaceable(0) || !IsReplaceable(3) || !IsReplaceable(10000) || !IsReplaceable(19998) { t.Fatal("expected replaceable kinds") } if IsReplaceable(1) || IsReplaceable(9999) || IsReplaceable(19999) { t.Fatal("unexpected replaceable kinds") } } func TestIsParameterizedReplaceable(t *testing.T) { if !IsParameterizedReplaceable(30000) || !IsParameterizedReplaceable(39999) { t.Fatal("kinds in the parameterized range") } if IsParameterizedReplaceable(29999) || IsParameterizedReplaceable(40000) { t.Fatal("parameterized range is half-open") } } func TestFromIntSlice(t *testing.T) { s := FromIntSlice([]int32{1, 5, 0, 30023, 65535}) if s.Len() != 5 { t.Fatalf("Len = %d", s.Len()) } if s.K[0].K != 1 || s.K[1].K != 5 || s.K[2].K != 0 || s.K[3].K != 30023 || s.K[4].K != 65535 { t.Fatalf("values = %d %d %d %d %d", s.K[0].K, s.K[1].K, s.K[2].K, s.K[3].K, s.K[4].K) } e := FromIntSlice(nil) if e.Len() != 0 { t.Fatal("an empty int slice yields an empty S") } } func TestSLenLessSwap(t *testing.T) { s := NewS(New(uint16(1)), New(uint16(2)), New(uint16(3))) if s.Len() != 3 { t.Fatalf("Len = %d", s.Len()) } if !s.Less(0, 2) { t.Fatal("1 < 3") } if s.Less(2, 0) { t.Fatal("3 < 1 is false") } s.Swap(0, 2) if s.K[0].K != 3 || s.K[2].K != 1 { t.Fatalf("Swap left %d .. %d", s.K[0].K, s.K[2].K) } e := NewS() if e.Len() != 0 { t.Fatal("empty S length") } var ns *S if ns.Len() != 0 { t.Fatal("nil S length") } } func TestNewWithCap(t *testing.T) { s := NewWithCap(8) if s.Len() != 0 { t.Fatalf("Len = %d", s.Len()) } s.K = push(s.K, New(uint16(7))) if s.Len() != 1 || s.K[0].K != 7 { t.Fatal("push into a presized S") } } func TestSContains(t *testing.T) { s := NewS(New(uint16(1)), New(uint16(5)), New(uint16(30023))) if !s.Contains(5) { t.Fatal("contains 5") } if !s.Contains(30023) { t.Fatal("contains 30023") } if s.Contains(2) { t.Fatal("does not contain 2") } if NewS().Contains(1) { t.Fatal("an empty S contains nothing") } } func TestSEquals(t *testing.T) { s := NewS(New(uint16(1)), New(uint16(2))) if !s.Equals(s) { t.Fatal("Equals self") } c := s.Clone() if !s.Equals(c) { t.Fatal("Equals clone") } if s.Equals(NewS(New(uint16(1)))) { t.Fatal("different length is not equal") } if s.Equals(NewS(New(uint16(1)), New(uint16(9)))) { t.Fatal("different element is not equal") } } func TestSMarshalAndString(t *testing.T) { s := NewS(New(uint16(1)), New(uint16(2)), New(uint16(10000))) if string(s.Marshal(nil)) != "[1,2,10000]" { t.Fatalf("Marshal = %s", string(s.Marshal(nil))) } if string(s.Marshal([]byte("k="))) != "k=[1,2,10000]" { t.Fatalf("Marshal into dst = %s", string(s.Marshal([]byte("k=")))) } if s.String() != "[1,2,10000]" { t.Fatalf("String = %s", s.String()) } if string(NewS().Marshal(nil)) != "[]" { t.Fatalf("empty Marshal = %s", string(NewS().Marshal(nil))) } } func TestSClone(t *testing.T) { s := NewS(New(uint16(1)), New(uint16(30023))) c := s.Clone() if c.Len() != 2 { t.Fatalf("clone len = %d", c.Len()) } if c.K[0].K != 1 || c.K[1].K != 30023 { t.Fatalf("clone values = %d %d", c.K[0].K, c.K[1].K) } if !c.Equals(s) { t.Fatal("clone equals its source") } } func TestSToUint16(t *testing.T) { s := NewS(New(uint16(1)), New(uint16(30023)), New(uint16(65535))) o := s.ToUint16() if len(o) != 3 { t.Fatalf("len = %d", len(o)) } if o[0] != 1 || o[1] != 30023 || o[2] != 65535 { t.Fatalf("values = %d %d %d", o[0], o[1], o[2]) } if len(NewS().ToUint16()) != 0 { t.Fatal("empty ToUint16") } } func TestSUnmarshal(t *testing.T) { s := &S{} rem, err := s.Unmarshal([]byte("[1,2,3]")) if err != nil { t.Fatal(err) } if s.Len() != 3 { t.Fatalf("parsed len = %d", s.Len()) } if s.K[0].K != 1 || s.K[2].K != 3 { t.Fatalf("parsed values = %d .. %d", s.K[0].K, s.K[2].K) } if len(rem) != 0 { t.Fatalf("remainder = %s", string(rem)) } s2 := &S{} rem2, err2 := s2.Unmarshal([]byte("[30023,1]tail")) if err2 != nil { t.Fatal(err2) } if s2.Len() != 2 || s2.K[0].K != 30023 || s2.K[1].K != 1 { t.Fatal("two-element parse") } if string(rem2) != "tail" { t.Fatalf("remainder2 = %s", string(rem2)) } // More kinds than push's first lazy-init batch exercises the reserve. s3 := &S{} rem3, err3 := s3.Unmarshal([]byte("[1,2,3,4,5,6]")) if err3 != nil { t.Fatal(err3) } if s3.Len() != 6 || s3.K[5].K != 6 { t.Fatalf("six-element parse len = %d", s3.Len()) } if len(rem3) != 0 { t.Fatalf("remainder3 = %s", string(rem3)) } s4 := &S{} if _, err4 := s4.Unmarshal([]byte("1,2")); err4 == nil { t.Fatal("a missing opening bracket should fail") } if s4.Len() != 0 { t.Fatal("a failed parse leaves no kinds") } s5 := &S{} if _, err5 := s5.Unmarshal(nil); err5 == nil { t.Fatal("empty input should fail") } // An immediately closed list produces no kinds. se := &S{} if _, erre := se.Unmarshal([]byte("[]")); erre != nil { t.Fatal(erre) } if se.Len() != 0 { t.Fatal("an empty list parses to no kinds") } // Leading and doubled commas are skipped between elements. sl := &S{} reml, errl := sl.Unmarshal([]byte("[,1,,2]")) if errl != nil { t.Fatal(errl) } if sl.Len() != 2 || sl.K[0].K != 1 || sl.K[1].K != 2 { t.Fatal("commas between elements") } if len(reml) != 0 { t.Fatalf("remainder4 = %s", string(reml)) } // A non-numeric element aborts the parse. sx := &S{} if _, errx := sx.Unmarshal([]byte("[x]")); errx == nil { t.Fatal("a non-numeric element should fail") } if sx.Len() != 0 { t.Fatal("a failed element parse leaves no kinds") } // An opened list with no elements simply ends. sb := &S{} if _, errb := sb.Unmarshal([]byte("[")); errb != nil { t.Fatal(errb) } if sb.Len() != 0 { t.Fatal("an opened empty list leaves no kinds") } } func TestAtoi(t *testing.T) { cases := []atoiCase{ {[]byte("0"), 0}, {[]byte("1"), 1}, {[]byte("30023"), 30023}, {[]byte("65535"), 65535}, {[]byte(""), -1}, {nil, -1}, {[]byte("12a"), -1}, {[]byte("-1"), -1}, {[]byte("1 2"), -1}, } for _, c := range cases { if got := atoi(c.in); got != c.want { t.Fatalf("atoi(%s) = %d, want %d", string(c.in), got, c.want) } } } func TestItoa(t *testing.T) { cases := []itoaCase{ {0, "0"}, {1, "1"}, {7, "7"}, {30023, "30023"}, {65535, "65535"}, {-7, "-7"}, {-123, "-123"}, } for _, c := range cases { if got := itoa(c.n); got != c.want { t.Fatalf("itoa(%d) = %s, want %s", c.n, got, c.want) } } } func TestIndexBytes(t *testing.T) { if indexBytes([]byte("hello world"), []byte("world")) != 6 { t.Fatal("substring at offset 6") } if indexBytes([]byte("hello"), []byte("hello")) != 0 { t.Fatal("whole-string match") } if indexBytes([]byte("hello"), []byte("xyz")) != -1 { t.Fatal("absent needle") } if indexBytes([]byte("ab"), []byte("abc")) != -1 { t.Fatal("needle longer than haystack") } if indexBytes(nil, []byte("a")) != -1 { t.Fatal("nil haystack") } if indexBytes([]byte("xab"), []byte("ab")) != 1 { t.Fatal("match after a partial prefix") } } func TestSkipWS(t *testing.T) { if string(skipWS([]byte(" \t\n\r abc"))) != "abc" { t.Fatal("leading whitespace") } if string(skipWS([]byte("abc"))) != "abc" { t.Fatal("no whitespace") } if len(skipWS([]byte(" "))) != 0 { t.Fatal("all whitespace") } if len(skipWS(nil)) != 0 { t.Fatal("nil input") } } func TestMatchBrace(t *testing.T) { if matchBrace([]byte("{}")) != 1 { t.Fatal("empty object") } if matchBrace([]byte(`{"a":1}`)) != 6 { t.Fatal("flat object") } if matchBrace([]byte(`{"a":{"b":2}}`)) != 12 { t.Fatal("nested object") } if matchBrace([]byte(`{"a":"}"}`)) != 8 { t.Fatal("brace inside a string") } if matchBrace([]byte(`{"a":"\"}"}`)) != 10 { t.Fatal("escaped quote must not close the string") } if matchBrace([]byte(`{"a":1`)) != 5 { t.Fatal("unterminated object returns the last index") } if matchBrace(nil) != -1 { t.Fatal("empty input") } } func TestParseQuoted(t *testing.T) { v, rest := parseQuoted([]byte(`"abc"tail`)) if string(v) != "abc" || string(rest) != "tail" { t.Fatalf("simple: %s %s", string(v), string(rest)) } v2, rest2 := parseQuoted([]byte(`"a\"b"rest`)) if string(v2) != `a\"b` || string(rest2) != "rest" { t.Fatalf("escaped: %s %s", string(v2), string(rest2)) } v3, rest3 := parseQuoted([]byte(`"abc`)) if v3 != nil || rest3 != nil { t.Fatal("unterminated string") } v4, rest4 := parseQuoted([]byte("abc")) if v4 != nil || rest4 != nil { t.Fatal("input does not start with a quote") } v5, rest5 := parseQuoted(nil) if v5 != nil || rest5 != nil { t.Fatal("nil input") } v6, rest6 := parseQuoted([]byte(`""x`)) if len(v6) != 0 || string(rest6) != "x" { t.Fatal("empty quoted string") } } func TestGetStringField(t *testing.T) { obj := []byte(`{"name":"Foo","sp" : "Spaced","raw":5}`) if string(getStringField(obj, []byte(`"name"`))) != "Foo" { t.Fatal("name field") } if string(getStringField(obj, []byte(`"sp"`))) != "Spaced" { t.Fatal("value separated by spaces") } if getStringField(obj, []byte(`"raw"`)) != nil { t.Fatal("an unquoted value is nil") } if getStringField(obj, []byte(`"absent"`)) != nil { t.Fatal("an absent key is nil") } if getStringField(nil, []byte(`"name"`)) != nil { t.Fatal("nil object") } if string(getStringField([]byte(`{"name":""}`), []byte(`"name"`))) != "" { t.Fatal("empty value") } } func TestGetIntField(t *testing.T) { obj := []byte(`{"start":1000,"end": 9999,"x":"7"}`) if getIntField(obj, []byte(`"start"`)) != 1000 { t.Fatal("start") } if getIntField(obj, []byte(`"end"`)) != 9999 { t.Fatal("end with leading space") } if getIntField(obj, []byte(`"absent"`)) != 0 { t.Fatal("absent key is zero") } if getIntField(obj, []byte(`"x"`)) != 0 { t.Fatal("non-numeric value is zero") } if getIntField(nil, []byte(`"start"`)) != 0 { t.Fatal("nil object") } } func TestParseRange(t *testing.T) { data := []byte(`{"regular":{"start":1000,"end":9999,"description":"Reg"},"ephemeral":{"start":20000,"end":29999,"description":"Eph"}}`) r := parseRange(data, []byte(`"regular"`)) if r.Start != 1000 || r.End != 9999 || r.Description != "Reg" { t.Fatalf("regular = %d..%d %s", r.Start, r.End, r.Description) } r2 := parseRange(data, []byte(`"ephemeral"`)) if r2.Start != 20000 || r2.End != 29999 || r2.Description != "Eph" { t.Fatalf("ephemeral = %d..%d %s", r2.Start, r2.End, r2.Description) } r3 := parseRange(data, []byte(`"missing"`)) if r3.Start != 0 || r3.End != 0 || r3.Description != "" { t.Fatal("a missing key yields the zero Range") } r4 := parseRange([]byte(`"regular": 5`), []byte(`"regular"`)) if r4.Start != 0 || r4.End != 0 { t.Fatal("a key without an object yields the zero Range") } r5 := parseRange([]byte(`"regular" : {"start":7}`), []byte(`"regular"`)) if r5.Start != 7 || r5.End != 0 { t.Fatalf("whitespace before brace: start=%d", r5.Start) } } func TestParseKindInfoObj(t *testing.T) { obj := []byte(`{"name":"Foo","nip":"42","description":"Desc","classification":"regular","deprecated":true,"deprecatedBy":"NIP-99","spec":"spec-x","rangeEnd":1234}`) info, rest := parseKindInfoObj(obj) if info.Name != "Foo" || info.Description != "Desc" || info.Classification != "regular" { t.Fatalf("basic fields: %s", info.Name) } if info.DeprecatedBy != "NIP-99" || info.Spec != "spec-x" || info.RangeEnd != 1234 { t.Fatalf("extra fields: %s %s %d", info.DeprecatedBy, info.Spec, info.RangeEnd) } if !info.Deprecated { t.Fatal("deprecated true") } if info.NIP == nil || *info.NIP != "42" { t.Fatal("nip") } if len(rest) != 0 { t.Fatalf("rest = %s", string(rest)) } info2, rest2 := parseKindInfoObj([]byte(`{"name":"T"}tail`)) if info2.Name != "T" || string(rest2) != "tail" { t.Fatalf("trailing rest = %s", string(rest2)) } info3, _ := parseKindInfoObj([]byte(`{"name":"Baz","nip":null}`)) if info3.NIP != nil { t.Fatal("a null nip stays nil") } if info3.Deprecated { t.Fatal("absent deprecated is false") } info4, _ := parseKindInfoObj([]byte(`{"name":"Sp","deprecated": true}`)) if !info4.Deprecated { t.Fatal("deprecated with a space") } info5, rest5 := parseKindInfoObj([]byte("x{}")) if rest5 != nil || info5.Name != "" { t.Fatal("non-object input yields the zero KindInfo") } info6, rest6 := parseKindInfoObj(nil) if rest6 != nil || info6.Name != "" { t.Fatal("empty input yields the zero KindInfo") } info7, rest7 := parseKindInfoObj([]byte(`{"name":"N","description":"has { and } inside"}`)) if info7.Description != "has { and } inside" || len(rest7) != 0 { t.Fatalf("braces inside a string: %s", info7.Description) } } func TestParseKindsJSON(t *testing.T) { kd := GetKindData() before := len(kd.Kinds) // No kinds key at all: the table must be untouched. parseKindsJSON([]byte(`{"version":"x"}`)) if len(kd.Kinds) != before { t.Fatal("json without a kinds key changed the table") } // Kinds key present but no object follows. parseKindsJSON([]byte(`"kinds" oops`)) if len(kd.Kinds) != before { t.Fatal("kinds key without an object changed the table") } // Malformed entries must break out without inserting anything. parseKindsJSON([]byte(`{"kinds":{"60010"}}`)) if kd.Kinds["60010"].Name != "" { t.Fatal("entry without a colon was inserted") } parseKindsJSON([]byte(`{"kinds":{x}}`)) parseKindsJSON([]byte(`{"kinds":{"60011":x}}`)) if kd.Kinds["60011"].Name != "" { t.Fatal("entry without an object was inserted") } // A well-formed object, including a leading comma, a spaced value, a // missing nip and a non-numeric key. parseKindsJSON([]byte(`{"kinds":{, "60001":{"name":"Custom One","nip":"99","description":"d1","classification":"regular","deprecated":true,"deprecatedBy":"NIP-99","spec":"s","rangeEnd":60009},"60002":{"name":"Custom Two"},"abc":{"name":"NonNumeric"}}}`)) i1 := kd.Kinds["60001"] if i1.Name != "Custom One" || i1.Description != "d1" || i1.Classification != "regular" { t.Fatalf("custom entry fields: %s", i1.Name) } if i1.DeprecatedBy != "NIP-99" || i1.Spec != "s" || i1.RangeEnd != 60009 || !i1.Deprecated { t.Fatalf("custom entry extras: %s %s %d", i1.DeprecatedBy, i1.Spec, i1.RangeEnd) } if i1.NIP == nil || *i1.NIP != "99" { t.Fatal("custom entry nip") } if kd.Kinds["60002"].Name != "Custom Two" { t.Fatal("second custom entry") } if kd.Kinds["abc"].Name != "NonNumeric" { t.Fatal("a non-numeric key is kept in the table") } if Map[uint16(60001)] != "Custom One" || Map[uint16(60002)] != "Custom Two" { t.Fatal("numeric custom names missing from Map") } delete(kd.Kinds, "60001") delete(kd.Kinds, "60002") delete(kd.Kinds, "abc") delete(Map, uint16(60001)) delete(Map, uint16(60002)) if len(kd.Kinds) != before { t.Fatalf("custom entries not cleaned up: %d", len(kd.Kinds)) } } // A truncated kinds array read one past the end of its input and the relay // died on a malformed REQ (`{"kinds":[1`). The parse stays lenient about the // missing bracket, as `[` already documented, but it must not read past the // end. func TestSUnmarshalTruncatedArray(t *testing.T) { s := NewWithCap(0) if _, err := s.Unmarshal([]byte("[1,2")); err != nil { t.Fatal(err) return } if s.Len() != 2 { t.Fatalf("a truncated array parsed %d kinds", s.Len()) } s2 := NewWithCap(0) if _, err := s2.Unmarshal([]byte("[")); err != nil { t.Fatal(err) return } if s2.Len() != 0 { t.Fatal("an opened empty list leaves no kinds") } // The bound did not change the complete forms. s3 := NewWithCap(0) if _, err := s3.Unmarshal([]byte("[1,2]")); err != nil { t.Fatal(err) return } if s3.Len() != 2 { t.Fatalf("parsed kinds = %d", s3.Len()) } }