@@ -139,4 +139,107 @@ void xmlExclusions() {
139139 assertEquals (10 , writtenCount .get (), "Documents should be written since non-excluded content changed" );
140140 assertEquals (5 , skippedCount .get (), "Skip count should remain at 5" );
141141 }
142+
143+ /**
144+ * Verifies that JSON Pointer exclusions are only applied to JSON documents and are ignored for XML documents.
145+ * The XML document should use its full content for hashing since no XML exclusions are configured.
146+ */
147+ @ Test
148+ void jsonExclusionsIgnoredForXmlDocuments () {
149+ filter = IncrementalWriteFilter .newBuilder ()
150+ .jsonExclusions ("/timestamp" )
151+ .onDocumentsSkipped (docs -> skippedCount .addAndGet (docs .length ))
152+ .build ();
153+
154+ // Write one JSON doc and one XML doc
155+ docs = new ArrayList <>();
156+ ObjectNode jsonDoc = objectMapper .createObjectNode ();
157+ jsonDoc .put ("id" , 1 );
158+ jsonDoc .put ("timestamp" , "2025-01-01T10:00:00Z" );
159+ docs .add (new DocumentWriteOperationImpl ("/incremental/test/mixed-doc.json" , METADATA , new JacksonHandle (jsonDoc )));
160+
161+ String xmlDoc = "<doc><id>1</id><timestamp>2025-01-01T10:00:00Z</timestamp></doc>" ;
162+ docs .add (new DocumentWriteOperationImpl ("/incremental/test/mixed-doc.xml" , METADATA , new StringHandle (xmlDoc ).withFormat (Format .XML )));
163+
164+ writeDocs (docs );
165+ assertEquals (2 , writtenCount .get ());
166+ assertEquals (0 , skippedCount .get ());
167+
168+ // Write again with different timestamp values
169+ docs = new ArrayList <>();
170+ jsonDoc = objectMapper .createObjectNode ();
171+ jsonDoc .put ("id" , 1 );
172+ jsonDoc .put ("timestamp" , "2026-01-02T15:30:00Z" ); // Changed
173+ docs .add (new DocumentWriteOperationImpl ("/incremental/test/mixed-doc.json" , METADATA , new JacksonHandle (jsonDoc )));
174+
175+ xmlDoc = "<doc><id>1</id><timestamp>2026-01-02T15:30:00Z</timestamp></doc>" ; // Changed
176+ docs .add (new DocumentWriteOperationImpl ("/incremental/test/mixed-doc.xml" , METADATA , new StringHandle (xmlDoc ).withFormat (Format .XML )));
177+
178+ writeDocs (docs );
179+ assertEquals (3 , writtenCount .get (), "XML doc should be written since its timestamp changed and no XML exclusions are configured" );
180+ assertEquals (1 , skippedCount .get (), "JSON doc should be skipped since only the excluded timestamp field changed" );
181+ }
182+
183+ /**
184+ * Verifies that when canonicalizeJson is false, documents with logically identical content
185+ * but different key ordering will produce different hashes, causing a write to occur.
186+ */
187+ @ Test
188+ void jsonNotCanonicalizedCausesDifferentHashForReorderedKeys () {
189+ filter = IncrementalWriteFilter .newBuilder ()
190+ .canonicalizeJson (false )
191+ .onDocumentsSkipped (docs -> skippedCount .addAndGet (docs .length ))
192+ .build ();
193+
194+ // Write initial document with keys in a specific order
195+ docs = new ArrayList <>();
196+ String json1 = "{\" name\" :\" Test\" ,\" id\" :1,\" value\" :100}" ;
197+ docs .add (new DocumentWriteOperationImpl ("/incremental/test/non-canonical.json" , METADATA ,
198+ new StringHandle (json1 ).withFormat (Format .JSON )));
199+
200+ writeDocs (docs );
201+ assertEquals (1 , writtenCount .get ());
202+ assertEquals (0 , skippedCount .get ());
203+
204+ // Write again with same logical content but different key order
205+ docs = new ArrayList <>();
206+ String json2 = "{\" id\" :1,\" value\" :100,\" name\" :\" Test\" }" ;
207+ docs .add (new DocumentWriteOperationImpl ("/incremental/test/non-canonical.json" , METADATA ,
208+ new StringHandle (json2 ).withFormat (Format .JSON )));
209+
210+ writeDocs (docs );
211+ assertEquals (2 , writtenCount .get (), "Document should be written because key order differs and JSON is not canonicalized" );
212+ assertEquals (0 , skippedCount .get (), "No documents should be skipped" );
213+ }
214+
215+ /**
216+ * Verifies that with the default canonicalizeJson(true), documents with logically identical content
217+ * but different key ordering will produce the same hash, causing the document to be skipped.
218+ */
219+ @ Test
220+ void jsonCanonicalizedProducesSameHashForReorderedKeys () {
221+ filter = IncrementalWriteFilter .newBuilder ()
222+ .onDocumentsSkipped (docs -> skippedCount .addAndGet (docs .length ))
223+ .build ();
224+
225+ // Write initial document with keys in a specific order
226+ docs = new ArrayList <>();
227+ String json1 = "{\" name\" :\" Test\" ,\" id\" :1,\" value\" :100}" ;
228+ docs .add (new DocumentWriteOperationImpl ("/incremental/test/canonical.json" , METADATA ,
229+ new StringHandle (json1 ).withFormat (Format .JSON )));
230+
231+ writeDocs (docs );
232+ assertEquals (1 , writtenCount .get ());
233+ assertEquals (0 , skippedCount .get ());
234+
235+ // Write again with same logical content but different key order
236+ docs = new ArrayList <>();
237+ String json2 = "{\" id\" :1,\" value\" :100,\" name\" :\" Test\" }" ;
238+ docs .add (new DocumentWriteOperationImpl ("/incremental/test/canonical.json" , METADATA ,
239+ new StringHandle (json2 ).withFormat (Format .JSON )));
240+
241+ writeDocs (docs );
242+ assertEquals (1 , writtenCount .get (), "Document should be skipped because canonicalized JSON produces the same hash" );
243+ assertEquals (1 , skippedCount .get (), "One document should be skipped" );
244+ }
142245}
0 commit comments