@@ -627,6 +627,133 @@ def test_zero_length(self):
627627 assert ms .decode_row (b"" ) == {}
628628
629629
630+ class TestJSONBinaryCodec :
631+ def test_encode_requires_binary (self ):
632+ ms = tskit .MetadataSchema ({"codec" : "json+binary" })
633+ with pytest .raises (
634+ exceptions .MetadataEncodingError ,
635+ match = "requires top-level '_binary' bytes-like value" ,
636+ ):
637+ ms .validate_and_encode_row ({})
638+
639+ def test_zero_length_blob (self ):
640+ ms = tskit .MetadataSchema ({"codec" : "json+binary" })
641+ encoded = ms .validate_and_encode_row ({"_binary" : b"" })
642+ decoded = ms .decode_row (encoded )
643+ assert isinstance (decoded ["_binary" ], memoryview )
644+ assert len (decoded ["_binary" ]) == 0
645+ # JSON portion was empty
646+ assert set (decoded .keys ()) == {"_binary" }
647+
648+ def test_round_trip_with_blob_and_json (self ):
649+ ms = tskit .MetadataSchema ({"codec" : "json+binary" })
650+ blob = b"\x00 \x01 \x02 hello"
651+ row = {"label" : "alpha" , "count" : 7 , "_binary" : blob }
652+ encoded = ms .validate_and_encode_row (row )
653+ out = ms .decode_row (encoded )
654+ assert out ["label" ] == "alpha"
655+ assert out ["count" ] == 7
656+ assert isinstance (out ["_binary" ], memoryview )
657+ assert out ["_binary" ].tobytes () == blob
658+
659+ def test_decode_without_magic_errors (self ):
660+ ms = tskit .MetadataSchema ({"codec" : "json+binary" })
661+ # Plain JSON is not acceptable for this codec
662+ with pytest .raises (ValueError , match = "missing magic header" ):
663+ ms .decode_row (b"{}" )
664+
665+ def test_simple_default (self ):
666+ schema = {
667+ "codec" : "json+binary" ,
668+ "type" : "object" ,
669+ "properties" : {"number" : {"type" : "number" , "default" : 5 }},
670+ }
671+ ms = tskit .MetadataSchema (schema )
672+ # With json+binary, we need to provide _binary even for empty metadata
673+ assert ms .decode_row (ms .validate_and_encode_row ({"_binary" : b"" })) == {
674+ "number" : 5 ,
675+ "_binary" : memoryview (b"" ),
676+ }
677+ assert ms .decode_row (
678+ ms .validate_and_encode_row ({"_binary" : b"" , "number" : 42 })
679+ ) == {"number" : 42 , "_binary" : memoryview (b"" )}
680+
681+ def test_nested_default_error (self ):
682+ schema = {
683+ "codec" : "json+binary" ,
684+ "type" : "object" ,
685+ "properties" : {
686+ "obj" : {
687+ "type" : "object" ,
688+ "properties" : {
689+ "nested_obj_no_default" : {
690+ "type" : "object" ,
691+ "properties" : {},
692+ },
693+ "nested_obj" : {
694+ "type" : "object" ,
695+ "properties" : {},
696+ "default" : {"foo" : "bar" },
697+ },
698+ },
699+ }
700+ },
701+ }
702+ with pytest .raises (
703+ tskit .MetadataSchemaValidationError ,
704+ match = "Defaults can only be specified at the top level for JSON codec" ,
705+ ):
706+ tskit .MetadataSchema (schema )
707+
708+ def test_bad_type_error (self ):
709+ ms = tskit .MetadataSchema ({"codec" : "json+binary" })
710+ # json+binary first checks for _binary key, so we need a dict with _binary
711+ # but other fields that can't be JSON encoded
712+ with pytest .raises (
713+ exceptions .MetadataEncodingError ,
714+ match = "Could not encode metadata of type TableCollection" ,
715+ ):
716+ ms .validate_and_encode_row (
717+ {"_binary" : b"" , "bad_field" : tskit .TableCollection (1 )}
718+ )
719+
720+ def test_skip_validation (self ):
721+ ms = tskit .MetadataSchema ({"codec" : "json+binary" })
722+ assert ms ._bypass_validation
723+ with patch .object (ms , "_validate_row" , return_value = True ) as mocked_validate :
724+ ms .validate_and_encode_row ({"_binary" : b"" })
725+ assert mocked_validate .call_count == 0
726+
727+ def test_dont_skip_validation (self ):
728+ ms = tskit .MetadataSchema ({"codec" : "json+binary" , "properties" : {"foo" : {}}})
729+ assert not ms ._bypass_validation
730+ with patch .object (ms , "_validate_row" , return_value = True ) as mocked_validate :
731+ ms .validate_and_encode_row ({"_binary" : b"" })
732+ assert mocked_validate .call_count == 1
733+
734+ def test_binary_requires_buffer_protocol (self ):
735+ ms = tskit .MetadataSchema ({"codec" : "json+binary" })
736+ with pytest .raises (
737+ exceptions .MetadataEncodingError ,
738+ match = "_binary must be bytes-like \\ (buffer protocol\\ )" ,
739+ ):
740+ ms .validate_and_encode_row ({"_binary" : "not bytes" })
741+
742+ def test_decode_version_mismatch (self ):
743+ ms = tskit .MetadataSchema ({"codec" : "json+binary" })
744+ header = metadata .JSONBinaryCodec ._HDR .pack (
745+ metadata .JSONBinaryCodec .MAGIC ,
746+ metadata .JSONBinaryCodec .VERSION + 1 ,
747+ len (b"{}" ),
748+ 0 ,
749+ )
750+ with pytest .raises (
751+ ValueError ,
752+ match = "Unsupported json\\ +binary version" ,
753+ ):
754+ ms .decode_row (header + b"{}" )
755+
756+
630757class TestStructCodec :
631758 def encode_decode (self , method_name , sub_schema , obj , buffer ):
632759 assert (
0 commit comments