BackRefWithPolymorphicContainer3304Test.java

package tools.jackson.databind.struct;

import org.junit.jupiter.api.Test;

import com.fasterxml.jackson.annotation.*;

import tools.jackson.databind.*;
import tools.jackson.databind.exc.InvalidDefinitionException;
import tools.jackson.databind.testutil.DatabindTestUtil;

import static org.junit.jupiter.api.Assertions.*;

// [databind#3304]: Better error message when @JsonBackReference is only on
// subtypes, not on the declared (abstract/interface) element type of a container.
public class BackRefWithPolymorphicContainer3304Test extends DatabindTestUtil
{
    @JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
            include = JsonTypeInfo.As.PROPERTY,
            property = "type")
    @JsonSubTypes({
        @JsonSubTypes.Type(value = SimpleChild.class, name = "SIMPLE"),
        @JsonSubTypes.Type(value = RefChild.class, name = "REF")
    })
    interface IChild { }

    static class SimpleChild implements IChild {
        public String value;

        public SimpleChild() { }
        public SimpleChild(String v) { value = v; }
    }

    // Back reference is ONLY on this subtype, NOT on the interface
    static class RefChild implements IChild {
        public String data;

        @JsonBackReference
        public Container parent;

        public RefChild() { }
        public RefChild(String d) { data = d; }
    }

    static class Container {
        @JsonManagedReference
        public IChild[] children;
    }

    // Variant with List instead of array
    static class ContainerWithList {
        @JsonManagedReference
        public java.util.List<IChild> children;
    }

    private final ObjectMapper MAPPER = newJsonMapper();

    // Verify error message mentions the abstract type issue
    @Test
    public void testBackRefOnSubtypeOnly_array() {
        InvalidDefinitionException ex = assertThrows(
                InvalidDefinitionException.class,
                () -> MAPPER.readValue(
                        "{\"children\":[{\"type\":\"SIMPLE\",\"value\":\"x\"}]}",
                        Container.class));
        String msg = ex.getMessage();
        assertNotNull(msg);
        // Verify improved error message mentions the abstract/interface issue
        assertTrue(msg.contains("abstract"),
                "Error message should mention abstract type issue, got: " + msg);
        assertTrue(msg.contains("@JsonBackReference"),
                "Error message should mention @JsonBackReference, got: " + msg);
    }

    @Test
    public void testBackRefOnSubtypeOnly_list() {
        InvalidDefinitionException ex = assertThrows(
                InvalidDefinitionException.class,
                () -> MAPPER.readValue(
                        "{\"children\":[{\"type\":\"SIMPLE\",\"value\":\"x\"}]}",
                        ContainerWithList.class));
        String msg = ex.getMessage();
        assertNotNull(msg);
        assertTrue(msg.contains("abstract"),
                "Error message should mention abstract type issue, got: " + msg);
        assertTrue(msg.contains("@JsonBackReference"),
                "Error message should mention @JsonBackReference, got: " + msg);
    }
}