From 99fb8917fad82f081a0f156166a818e90193ad89 Mon Sep 17 00:00:00 2001 From: Vasiliy Mikhailov Date: Sun, 12 Jul 2026 04:04:43 +0000 Subject: [PATCH] Add unit tests for Metadata reserved-key blocking and write-limiter delegation --- .../apache/tika/metadata/TestMetadata.java | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) diff --git a/tika-core/src/test/java/org/apache/tika/metadata/TestMetadata.java b/tika-core/src/test/java/org/apache/tika/metadata/TestMetadata.java index 2ce1b8b6c69..4fde53ec490 100644 --- a/tika-core/src/test/java/org/apache/tika/metadata/TestMetadata.java +++ b/tika-core/src/test/java/org/apache/tika/metadata/TestMetadata.java @@ -18,6 +18,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -27,6 +28,7 @@ import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; +import java.util.Map; import java.util.Properties; import java.util.Random; import java.util.TimeZone; @@ -39,6 +41,7 @@ import org.junit.jupiter.api.Test; import org.apache.tika.TikaTest; +import org.apache.tika.metadata.writefilter.MetadataWriteLimiter; import org.apache.tika.utils.DateUtils; //Junit imports @@ -492,6 +495,124 @@ public void testToStringWithSingleEntry() { assertEquals("key=value1", m.toString()); } + /** + * Two Metadata instances that differ only in a value under the same key + * must not be equal, so value-distinct metadata are never collapsed. + */ + @Test + public void testValueDistinctMetadataNotEqual() { + Metadata m1 = new Metadata(); + m1.add("k1", "v1"); + Metadata m2 = new Metadata(); + m2.add("k1", "v2"); + assertNotEquals(m1, m2); + } + + /** + * Reserved keys must be ignored when written to an untrusted Metadata + * instance (the default), so untrusted callers cannot inject them. + */ + @Test + public void testReservedKeyBlockedOnUntrustedMetadata() { + String reservedKey = TikaCoreProperties.TIKA_META_PREFIX + "reserved"; + Metadata m = new Metadata(); + m.add(reservedKey, "value"); + assertNull(m.get(reservedKey)); + } + + /** + * Once a Metadata instance is marked trusted, reserved keys are accepted. + */ + @Test + public void testReservedKeyAllowedOnTrustedMetadata() { + String reservedKey = TikaCoreProperties.TIKA_META_PREFIX + "reserved"; + Metadata m = new Metadata(); + m.setTrusted(true); + m.add(reservedKey, "value"); + assertEquals("value", m.get(reservedKey)); + } + + /** + * A write limiter that drops everything must prevent {@code set} from + * persisting a value, so persistence stays under the limiter's control. + */ + @Test + public void testWriteLimiterBlocksSet() { + Metadata m = new Metadata(blockingLimiter()); + m.set("key", "value"); + assertNull(m.get("key")); + } + + /** + * {@code set(String, String)} must delegate to the limiter with the exact + * field and value it was given. + */ + @Test + public void testWriteLimiterInvokedOnSet() { + RecordingLimiter limiter = new RecordingLimiter(); + Metadata m = new Metadata(limiter); + m.set("key", "value"); + assertTrue(limiter.setCalled); + assertEquals("key", limiter.lastSetField); + assertEquals("value", limiter.lastSetValue); + } + + /** + * The {@code set(Property, String)} overload must delegate to the limiter + * using the property's name and the given value. + */ + @Test + public void testWriteLimiterInvokedOnSetProperty() { + RecordingLimiter limiter = new RecordingLimiter(); + Metadata m = new Metadata(limiter); + m.set(TikaCoreProperties.TITLE, "title"); + assertTrue(limiter.setCalled); + assertEquals(TikaCoreProperties.TITLE.getName(), limiter.lastSetField); + assertEquals("title", limiter.lastSetValue); + } + + /** + * A write limiter that ignores every field, so nothing is persisted. + */ + private static MetadataWriteLimiter blockingLimiter() { + return new MetadataWriteLimiter() { + @Override + public void add(String field, String value, Map data) { + } + + @Override + public void set(String field, String value, Map data) { + } + }; + } + + /** + * A spy limiter that records the field and value it was invoked with on + * {@code set} and writes the value through to the backing map. + */ + private static final class RecordingLimiter implements MetadataWriteLimiter { + private boolean setCalled; + private String lastSetField; + private String lastSetValue; + + @Override + public void add(String field, String value, Map data) { + if (value != null) { + data.put(field, new String[]{value}); + } + } + + @Override + public void set(String field, String value, Map data) { + setCalled = true; + lastSetField = field; + lastSetValue = value; + if (value != null) { + data.put(field, new String[]{value}); + } + } + } + private static class MetadataDateAdder implements Callable { private final Random random = new Random();