diff --git a/compat-0.3/server-conversion/src/test/java/org/a2aproject/sdk/compat03/conversion/mappers/domain/NullData_v0_3_Test.java b/compat-0.3/server-conversion/src/test/java/org/a2aproject/sdk/compat03/conversion/mappers/domain/NullData_v0_3_Test.java index 8c9611026..71fdf0419 100644 --- a/compat-0.3/server-conversion/src/test/java/org/a2aproject/sdk/compat03/conversion/mappers/domain/NullData_v0_3_Test.java +++ b/compat-0.3/server-conversion/src/test/java/org/a2aproject/sdk/compat03/conversion/mappers/domain/NullData_v0_3_Test.java @@ -8,8 +8,12 @@ import java.util.Collections; import java.util.Map; import org.a2aproject.sdk.compat03.spec.DataPart_v0_3; +import org.a2aproject.sdk.compat03.spec.Part_v0_3; +import org.a2aproject.sdk.compat03.spec.TextPart_v0_3; import org.a2aproject.sdk.spec.DataPart; import org.a2aproject.sdk.spec.InvalidRequestError; +import org.a2aproject.sdk.spec.Part; +import org.a2aproject.sdk.spec.TextPart; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; @@ -31,6 +35,36 @@ void objectNullsSurviveBothConversionDirections() { assertEquals(original, PartMapper_v0_3.INSTANCE.toV10(legacy)); } + @ParameterizedTest + @ValueSource(strings = {"text", "data"}) + void partMetadataNullsSurviveBothConversionDirections(String kind) { + Map metadata = Collections.singletonMap("optional", null); + Map data = Map.of("value", "hello"); + Part current = + kind.equals("text") + ? new TextPart("hello", metadata) + : new DataPart(data, metadata); + Part_v0_3 legacy = + kind.equals("text") + ? new TextPart_v0_3("hello", metadata) + : new DataPart_v0_3(data, metadata); + + Part_v0_3 downgraded = PartMapper_v0_3.INSTANCE.fromV10(current); + Part upgraded = PartMapper_v0_3.INSTANCE.toV10(legacy); + assertEquals(legacy, downgraded); + assertEquals(current, upgraded); + assertEquals( + metadata, + kind.equals("text") + ? assertInstanceOf(TextPart_v0_3.class, downgraded).metadata() + : assertInstanceOf(DataPart_v0_3.class, downgraded).metadata()); + assertEquals( + metadata, + kind.equals("text") + ? assertInstanceOf(TextPart.class, upgraded).metadata() + : assertInstanceOf(DataPart.class, upgraded).metadata()); + } + @ParameterizedTest @ValueSource(strings = {"null", "[]", "42", "true", "\"text\""}) void nonObjectDataCannotBeDowngraded(String json) { diff --git a/compat-0.3/spec/src/test/java/org/a2aproject/sdk/compat03/spec/NullValues_v0_3_Test.java b/compat-0.3/spec/src/test/java/org/a2aproject/sdk/compat03/spec/NullValues_v0_3_Test.java index 385641502..ea96820e3 100644 --- a/compat-0.3/spec/src/test/java/org/a2aproject/sdk/compat03/spec/NullValues_v0_3_Test.java +++ b/compat-0.3/spec/src/test/java/org/a2aproject/sdk/compat03/spec/NullValues_v0_3_Test.java @@ -84,6 +84,22 @@ void dataObjectWithNullValuesIsDefensivelyCopied() { assertThrows(UnsupportedOperationException.class, () -> part.data().put("later", true)); } + @ParameterizedTest + @ValueSource(strings = {"text", "data"}) + void partMetadataWithNullValuesIsDefensivelyCopied(String kind) { + Map metadata = new HashMap<>(); + metadata.put("optional", null); + Map copiedMetadata = + kind.equals("text") + ? new TextPart_v0_3("hello", metadata).metadata() + : new DataPart_v0_3(Map.of("value", "hello"), metadata).metadata(); + metadata.put("optional", "changed"); + metadata.put("later", true); + + assertEquals(Collections.singletonMap("optional", null), copiedMetadata); + assertThrows(UnsupportedOperationException.class, () -> copiedMetadata.put("later", true)); + } + @Test void nullDataObjectRemainsInvalid() { assertThrows(IllegalArgumentException.class, () -> new DataPart_v0_3(null));