@@ -3,9 +3,12 @@ package proof
33import (
44 "bytes"
55 "encoding/hex"
6+ "encoding/json"
7+ "errors"
68 "net/url"
79 "os"
810 "path/filepath"
11+ "reflect"
912 "strings"
1013 "testing"
1114
@@ -15,6 +18,7 @@ import (
1518 "github.com/lightninglabs/taproot-assets/internal/test"
1619 "github.com/lightningnetwork/lnd/tlv"
1720 "github.com/stretchr/testify/require"
21+ "pgregory.net/rapid"
1822)
1923
2024var (
@@ -71,7 +75,6 @@ func TestValidateMetaReveal(t *testing.T) {
7175 Type : MetaOpaque ,
7276 Data : nil ,
7377 },
74- expectedErr : ErrMetaDataMissing ,
7578 },
7679 {
7780 name : "too much data" ,
@@ -466,3 +469,162 @@ func TestDecodeNewMetaReveal(t *testing.T) {
466469 require .Equal (t , fn .None [[]url.URL ](), decoded .CanonicalUniverses )
467470 require .Equal (t , fn .None [btcec.PublicKey ](), decoded .DelegationKey )
468471}
472+
473+ // TestDecodeMetaJSONEmptyBytes tests DecodeMetaJSON with an empty byte slice.
474+ func TestDecodeMetaJSONEmptyBytes (t * testing.T ) {
475+ t .Parallel ()
476+
477+ _ , err := DecodeMetaJSON ([]byte {})
478+ require .ErrorIs (t , err , ErrInvalidJSON )
479+ }
480+
481+ // TestEncodeMetaJSONEmptyOrNilMap tests EncodeMetaJSON with nil and empty maps.
482+ func TestEncodeMetaJSONEmptyOrNilMap (t * testing.T ) {
483+ t .Parallel ()
484+
485+ t .Run ("nil map" , func (t * testing.T ) {
486+ nilMapBytes , err := EncodeMetaJSON (nil )
487+ require .NoError (t , err )
488+ require .Equal (
489+ t , []byte {}, nilMapBytes ,
490+ "encoding nil map should produce empty bytes" ,
491+ )
492+ })
493+
494+ t .Run ("empty map" , func (t * testing.T ) {
495+ emptyMap := make (map [string ]interface {})
496+ emptyMapBytes , err := EncodeMetaJSON (emptyMap )
497+ require .NoError (t , err )
498+ require .Equal (t , []byte ("{}" ), emptyMapBytes ,
499+ "Encoding empty map should produce '{}'" )
500+ })
501+ }
502+
503+ // simpleJSONMap is a helper struct for generating map[string]interface{}
504+ // values for property-based testing.
505+ type simpleJSONMap struct {
506+ StringEntries map [string ]string
507+ FloatEntries map [string ]float64
508+ BoolEntries map [string ]bool
509+ }
510+
511+ // toMap converts a simpleJSONMap to a map[string]interface{}.
512+ func (s simpleJSONMap ) toMap () map [string ]interface {} {
513+ m := make (map [string ]interface {})
514+ for k , v := range s .StringEntries {
515+ // To avoid key collisions if rapid generates identical keys for
516+ // different entry types, we could prefix them, but for
517+ // simplicity, we'll assume distinct keys or accept overwrites
518+ // if rapid generates identical string keys for different map
519+ // types (unlikely to be an issue for typical map generation).
520+ m [k ] = v
521+ }
522+ for k , v := range s .FloatEntries {
523+ m [k ] = v
524+ }
525+ for k , v := range s .BoolEntries {
526+ m [k ] = v
527+ }
528+ return m
529+ }
530+
531+ // TestMetaJsonEncodeDecodeProperties tests the EncodeMetaJSON and
532+ // DecodeMetaJSON functions using property-based testing. It ensures that
533+ // encoding and then decoding a map yields the original map.
534+ func TestMetaJsonEncodeDecodeProperties (t * testing.T ) {
535+ t .Parallel ()
536+
537+ rapid .Check (t , func (rt * rapid.T ) {
538+ // Generate a simpleJSONMap instance.
539+ sjm := rapid .Custom (func (t * rapid.T ) simpleJSONMap {
540+ return simpleJSONMap {
541+ StringEntries : rapid .MapOf (
542+ rapid .String (), rapid .String (),
543+ ).Draw (t , "StringEntries" ),
544+ FloatEntries : rapid .MapOf (
545+ rapid .String (), rapid .Float64 (),
546+ ).Draw (t , "FloatEntries" ),
547+ BoolEntries : rapid .MapOf (
548+ rapid .String (), rapid .Bool (),
549+ ).Draw (t , "BoolEntries" ),
550+ }
551+ }).Draw (rt , "sjm" )
552+
553+ originalMap := sjm .toMap ()
554+
555+ encodedBytes , err := EncodeMetaJSON (originalMap )
556+ if err != nil {
557+ // If the generated map is too large, EncodeMetaJSON
558+ // will return ErrMetaDataTooLarge. This is an expected
559+ // behavior, not a property failure.
560+ if errors .Is (err , ErrMetaDataTooLarge ) {
561+ return
562+ }
563+
564+ // Any other encoding error is a failure.
565+ rt .Fatalf ("EncodeMetaJSON failed for map %#v: %v" ,
566+ originalMap , err )
567+ }
568+
569+ decodedMap , err := DecodeMetaJSON (encodedBytes )
570+ if err != nil {
571+ rt .Fatalf ("DecodeMetaJSON failed for bytes %s " +
572+ "(from map %#v): %v" ,
573+ string (encodedBytes ), originalMap , err )
574+ }
575+
576+ if ! reflect .DeepEqual (originalMap , decodedMap ) {
577+ rt .Fatalf ("Decoded map does not match original map.\n " +
578+ "Original: %#v\n Encoded: %s\n Decoded: %#v" ,
579+ originalMap , string (encodedBytes ), decodedMap )
580+ }
581+ })
582+ }
583+
584+ // TestDecodeMetaJSONOversizedProperty tests that DecodeMetaJSON returns an
585+ // error when the input byte slice is larger than MetaDataMaxSizeBytes.
586+ func TestDecodeMetaJSONOversizedProperty (t * testing.T ) {
587+ t .Parallel ()
588+
589+ rapid .Check (t , func (rt * rapid.T ) {
590+ // Generate a byte slice that is guaranteed to be too large. We
591+ // add a small delta to avoid generating slices that are
592+ // excessively large and slow down the test.
593+ oversizedBytes := rapid .SliceOfN (
594+ rapid .Byte (),
595+ MetaDataMaxSizeBytes + 1 ,
596+ MetaDataMaxSizeBytes + 10 ,
597+ ).Draw (rt , "oversizedBytes" )
598+
599+ _ , err := DecodeMetaJSON (oversizedBytes )
600+ require .ErrorIs (rt , err , ErrMetaDataTooLarge )
601+ })
602+ }
603+
604+ // TestDecodeMetaJSONInvalidJSONProperty tests that DecodeMetaJSON returns an
605+ // error for byte slices that are not valid JSON but are within size limits.
606+ func TestDecodeMetaJSONInvalidJSONProperty (t * testing.T ) {
607+ t .Parallel ()
608+
609+ rapid .Check (t , func (rt * rapid.T ) {
610+ // Generate a byte slice that is within the size limit. There's
611+ // a high chance this random byte slice won't be valid JSON.
612+ inputBytes := rapid .SliceOfN (
613+ rapid .Byte (),
614+ 0 , MetaDataMaxSizeBytes ,
615+ ).Draw (rt , "inputBytes" )
616+
617+ // If, by chance, we generated valid JSON or an empty slice
618+ // (which DecodeMetaJSON handles as valid empty JSON), we skip
619+ // this iteration as we want to test invalid JSON.
620+ // DecodeMetaJSON considers empty bytes as valid (empty map).
621+ if json .Valid (inputBytes ) {
622+ rt .Skip ("Generated valid JSON or empty " +
623+ "slice, skipping to test invalid case" )
624+ return
625+ }
626+
627+ _ , err := DecodeMetaJSON (inputBytes )
628+ require .ErrorIs (rt , err , ErrInvalidJSON )
629+ })
630+ }
0 commit comments