2020
2121import static org .testng .Assert .assertEquals ;
2222import static org .testng .Assert .assertNotNull ;
23+ import com .fasterxml .jackson .core .JsonProcessingException ;
24+ import com .fasterxml .jackson .databind .JsonNode ;
25+ import com .fasterxml .jackson .databind .ObjectMapper ;
26+ import com .fasterxml .jackson .databind .node .ObjectNode ;
27+ import com .google .protobuf .DescriptorProtos ;
2328import com .google .protobuf .Descriptors ;
29+ import com .google .protobuf .InvalidProtocolBufferException ;
30+ import com .google .protobuf .util .JsonFormat ;
2431import io .netty .buffer .ByteBuf ;
2532import io .netty .buffer .ByteBufAllocator ;
2633import java .nio .charset .StandardCharsets ;
34+ import java .util .Base64 ;
2735import java .util .Collections ;
2836import java .util .HashMap ;
2937import lombok .extern .slf4j .Slf4j ;
3038import org .apache .pulsar .common .schema .SchemaType ;
39+ import org .skyscreamer .jsonassert .JSONAssert ;
40+ import org .skyscreamer .jsonassert .JSONCompareMode ;
3141import org .testng .Assert ;
3242import org .testng .annotations .Test ;
3343
@@ -46,6 +56,28 @@ public class ProtobufNativeSchemaTest {
4656 + "gBQjUKJW9yZy5hcGFjaGUucHVsc2FyLmNsaWVudC5zY2hlbWEucHJvdG9CDEV4dGVybmFsVGVzdGIGcHJvdG8z\" ,\" rootMessageT"
4757 + "ypeName\" :\" proto.TestMessage\" ,\" rootFileDescriptorName\" :\" Test.proto\" }" ;
4858
59+ private static final ObjectMapper MAPPER = new ObjectMapper ();
60+
61+ /**
62+ * Decode a base64-encoded Protobuf FileDescriptorSet into a canonical JSON tree.
63+ * @param b64 a base64 string
64+ * @return a normalized JSON tree that can be compared with JSONAssert, ensuring deterministic
65+ * equality checks regardless of field ordering.
66+ * @throws IllegalArgumentException if b64 isn't valid Base64
67+ * @throws InvalidProtocolBufferException if the decoded bytes are not a valid FileDescriptorSet
68+ * @throws JsonProcessingException if the JSON string is invalid
69+ */
70+ private static JsonNode fdsToJson (String b64 )
71+ throws IllegalArgumentException , InvalidProtocolBufferException , JsonProcessingException {
72+ DescriptorProtos .FileDescriptorSet fds =
73+ DescriptorProtos .FileDescriptorSet .parseFrom (Base64 .getDecoder ().decode (b64 ));
74+ String json = JsonFormat .printer ()
75+ .includingDefaultValueFields ()
76+ .omittingInsignificantWhitespace ()
77+ .print (fds );
78+ return MAPPER .readTree (json );
79+ }
80+
4981 @ Test
5082 public void testEncodeAndDecode () {
5183 final String stringFieldValue = "StringFieldValue" ;
@@ -61,15 +93,34 @@ public void testEncodeAndDecode() {
6193 }
6294
6395 @ Test
64- public void testSchema () {
96+ public void testSchema () throws Exception {
6597 ProtobufNativeSchema <org .apache .pulsar .client .schema .proto .Test .TestMessage > protobufSchema =
6698 ProtobufNativeSchema .of (org .apache .pulsar .client .schema .proto .Test .TestMessage .class );
6799
68100 assertEquals (protobufSchema .getSchemaInfo ().getType (), SchemaType .PROTOBUF_NATIVE );
69101
70102 assertNotNull (ProtobufNativeSchemaUtils .deserialize (protobufSchema .getSchemaInfo ().getSchema ()));
71- assertEquals (new String (protobufSchema .getSchemaInfo ().getSchema (),
72- StandardCharsets .UTF_8 ), EXPECTED_SCHEMA_JSON );
103+
104+ // Parse the produced/expected JSON into trees
105+ String actualJson = new String (protobufSchema .getSchemaInfo ().getSchema (), StandardCharsets .UTF_8 );
106+ JsonNode actualRoot = MAPPER .readTree (actualJson );
107+ JsonNode expectedRoot = MAPPER .readTree (EXPECTED_SCHEMA_JSON );
108+
109+ // Normalize the base64-encoded "fileDescriptorSet" field to remove order-dependent differences.
110+ // When decoded, Protobuf descriptors can serialize fields in varying orders, which causes
111+ // string-based comparisons to fail. By parsing into JsonNodes, we allow for re-ordering.
112+ String fdSetField = "fileDescriptorSet" ;
113+ JsonNode actualFdsNode = fdsToJson (actualRoot .path (fdSetField ).asText ());
114+ JsonNode expectedFdsNode = fdsToJson (expectedRoot .path (fdSetField ).asText ());
115+ ((ObjectNode ) actualRoot ).set (fdSetField , actualFdsNode );
116+ ((ObjectNode ) expectedRoot ).set (fdSetField , expectedFdsNode );
117+
118+ // Ensure the fields and values are the same, order does not matter
119+ JSONAssert .assertEquals (
120+ MAPPER .writeValueAsString (expectedRoot ),
121+ MAPPER .writeValueAsString (actualRoot ),
122+ JSONCompareMode .NON_EXTENSIBLE
123+ );
73124 }
74125
75126 @ Test
0 commit comments