2020
2121import static org .testng .Assert .assertEquals ;
2222import static org .testng .Assert .assertNotNull ;
23+ import com .fasterxml .jackson .databind .JsonNode ;
24+ import com .fasterxml .jackson .databind .ObjectMapper ;
25+ import com .fasterxml .jackson .databind .node .ObjectNode ;
26+ import com .google .protobuf .DescriptorProtos ;
2327import com .google .protobuf .Descriptors ;
28+ import com .google .protobuf .InvalidProtocolBufferException ;
29+ import com .google .protobuf .util .JsonFormat ;
2430import io .netty .buffer .ByteBuf ;
2531import io .netty .buffer .ByteBufAllocator ;
2632import java .nio .charset .StandardCharsets ;
33+ import java .util .Base64 ;
2734import java .util .Collections ;
2835import java .util .HashMap ;
2936import lombok .extern .slf4j .Slf4j ;
3037import org .apache .pulsar .common .schema .SchemaType ;
38+ import org .json .JSONException ;
39+ import org .json .JSONObject ;
40+ import org .skyscreamer .jsonassert .JSONAssert ;
41+ import org .skyscreamer .jsonassert .JSONCompareMode ;
3142import org .testng .Assert ;
3243import org .testng .annotations .Test ;
3344
@@ -46,6 +57,28 @@ public class ProtobufNativeSchemaTest {
4657 + "gBQjUKJW9yZy5hcGFjaGUucHVsc2FyLmNsaWVudC5zY2hlbWEucHJvdG9CDEV4dGVybmFsVGVzdGIGcHJvdG8z\" ,\" rootMessageT"
4758 + "ypeName\" :\" proto.TestMessage\" ,\" rootFileDescriptorName\" :\" Test.proto\" }" ;
4859
60+ private static final ObjectMapper MAPPER = new ObjectMapper ();
61+
62+ /**
63+ * Decode a base64-encoded Protobuf FileDescriptorSet into a canonical JSON tree.
64+ * @param b64 a base64 string
65+ * @return a normalized JSON tree that can be compared with JSONAssert, ensuring deterministic
66+ * equality checks regardless of field ordering.
67+ * @throws IllegalArgumentException if b64 isn't valid Base64
68+ * @throws InvalidProtocolBufferException if the decoded bytes are not a valid FileDescriptorSet
69+ * @throws JSONException if the JSON string is invalid
70+ */
71+ private static JSONObject fdsToJson (String b64 )
72+ throws IllegalArgumentException , InvalidProtocolBufferException , JSONException {
73+ DescriptorProtos .FileDescriptorSet fds =
74+ DescriptorProtos .FileDescriptorSet .parseFrom (Base64 .getDecoder ().decode (b64 ));
75+ String json = JsonFormat .printer ()
76+ .includingDefaultValueFields ()
77+ .omittingInsignificantWhitespace ()
78+ .print (fds );
79+ return new JSONObject (json );
80+ }
81+
4982 @ Test
5083 public void testEncodeAndDecode () {
5184 final String stringFieldValue = "StringFieldValue" ;
@@ -61,15 +94,49 @@ public void testEncodeAndDecode() {
6194 }
6295
6396 @ Test
64- public void testSchema () {
97+ public void testSchema () throws Exception {
6598 ProtobufNativeSchema <org .apache .pulsar .client .schema .proto .Test .TestMessage > protobufSchema =
6699 ProtobufNativeSchema .of (org .apache .pulsar .client .schema .proto .Test .TestMessage .class );
67100
68101 assertEquals (protobufSchema .getSchemaInfo ().getType (), SchemaType .PROTOBUF_NATIVE );
69102
70103 assertNotNull (ProtobufNativeSchemaUtils .deserialize (protobufSchema .getSchemaInfo ().getSchema ()));
71- assertEquals (new String (protobufSchema .getSchemaInfo ().getSchema (),
72- StandardCharsets .UTF_8 ), EXPECTED_SCHEMA_JSON );
104+
105+ // Parse the actual/expected JSON into trees
106+ String actualJson = new String (protobufSchema .getSchemaInfo ().getSchema (), StandardCharsets .UTF_8 );
107+ JsonNode actualRoot = MAPPER .readTree (actualJson );
108+ JsonNode expectedRoot = MAPPER .readTree (EXPECTED_SCHEMA_JSON );
109+
110+ // Extract and validate the FileDescriptorSet field for semantic comparison
111+ // (When decoded, Protobuf descriptors can serialize fields in varying orders
112+ // causing hard coded string comparisons to fail)
113+ String fdSetField = "fileDescriptorSet" ;
114+ JsonNode actualB64Node = actualRoot .path (fdSetField );
115+ JsonNode expectedB64Node = expectedRoot .path (fdSetField );
116+ Assert .assertFalse (actualB64Node .isMissingNode ());
117+ Assert .assertFalse (expectedB64Node .isMissingNode ());
118+ Assert .assertTrue (actualB64Node .isValueNode ());
119+ Assert .assertTrue (expectedB64Node .isValueNode ());
120+
121+ // Decode FileDescriptorSets to JSON and compare semantically (order-insensitive)
122+ JSONObject actualFdsObj = fdsToJson (actualB64Node .asText ());
123+ JSONObject expectedFdsObj = fdsToJson (expectedB64Node .asText ());
124+ JSONAssert .assertEquals (
125+ "FileDescriptorSet mismatch: decoded Protobuf descriptors have mismatched schema contents" ,
126+ expectedFdsObj ,
127+ actualFdsObj ,
128+ JSONCompareMode .NON_EXTENSIBLE
129+ );
130+
131+ // Remove the already verified field and compare remaining schema attributes, order does not matter
132+ ((ObjectNode ) actualRoot ).remove (fdSetField );
133+ ((ObjectNode ) expectedRoot ).remove (fdSetField );
134+ JSONAssert .assertEquals (
135+ "Schema metadata mismatch: remaining JSON fields differ after verifying FileDescriptorSet" ,
136+ MAPPER .writeValueAsString (expectedRoot ),
137+ MAPPER .writeValueAsString (actualRoot ),
138+ JSONCompareMode .NON_EXTENSIBLE
139+ );
73140 }
74141
75142 @ Test
0 commit comments