Issue4339Test.java

package io.swagger.v3.core.issues;

import com.fasterxml.jackson.databind.node.IntNode;
import com.fasterxml.jackson.databind.node.NullNode;
import io.swagger.v3.core.converter.AnnotatedType;
import io.swagger.v3.core.converter.ModelConverterContextImpl;
import io.swagger.v3.core.jackson.ModelResolver;
import io.swagger.v3.core.util.Configuration;
import io.swagger.v3.core.util.Json;
import io.swagger.v3.core.util.Json31;
import io.swagger.v3.oas.annotations.media.Schema;
import org.testng.annotations.Test;

import java.math.BigDecimal;

import static org.testng.Assert.*;

/**
 * Reproduces GitHub Issue #4339
 * Schema annotation processing isn't working with nullable and "null" example or default value
 *
 * Expected behavior:
 * All data types should respect nullable=true and produce null values in example/default fields
 * when example="null" or defaultValue="null" is specified.
 *
 * Tests both OAS 3.0 and OAS 3.1 specifications.
 *
 * @see <a href="https://github.com/swagger-api/swagger-core/issues/4339">...</a>
 */
public class Issue4339Test {

    //OAS 3.0 Tests
    @Test
    public void testNullableStringWithNullExampleAndDefault_OAS30() {
        final ModelResolver modelResolver = new ModelResolver(Json.mapper());
        Configuration configuration = new Configuration();
        configuration.setOpenAPI31(false);
        modelResolver.setConfiguration(configuration);
        final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);

        final io.swagger.v3.oas.models.media.Schema model = context
                .resolve(new AnnotatedType(NullableStringModel.class));

        assertNotNull(model);
        assertNotNull(model.getProperties());

        io.swagger.v3.oas.models.media.Schema nullableStringField =
                (io.swagger.v3.oas.models.media.Schema) model.getProperties().get("nullableStringField");
        assertNotNull(nullableStringField, "nullableStringField property should exist");
        assertEquals(nullableStringField.getNullable(), Boolean.TRUE, "Field should be nullable");
        
        assertNull(nullableStringField.getExample(),
            "Example should be null for nullable field with example=\"null\", but got: " + nullableStringField.getExample());
        assertNull(nullableStringField.getDefault(), 
            "Default should be null for nullable field with defaultValue=\"null\", but got: " + nullableStringField.getDefault());
    }

    @Test
    public void testNullableIntegerWithNullExampleAndDefault_OAS30() {
        final ModelResolver modelResolver = new ModelResolver(Json.mapper());
        Configuration configuration = new Configuration();
        configuration.setOpenAPI31(false);
        modelResolver.setConfiguration(configuration);
        final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);

        final io.swagger.v3.oas.models.media.Schema model = context
                .resolve(new AnnotatedType(NullableIntegerModel.class));

        assertNotNull(model);
        assertNotNull(model.getProperties());

        io.swagger.v3.oas.models.media.Schema nullableIntegerField =
                (io.swagger.v3.oas.models.media.Schema) model.getProperties().get("nullableIntegerField");
        assertNotNull(nullableIntegerField, "nullableIntegerField property should exist");
        assertEquals(nullableIntegerField.getNullable(), Boolean.TRUE, "Field should be nullable");
        
        assertNull(nullableIntegerField.getExample(),
            "Example should be null for nullable integer field with example=\"null\"");
        assertNull(nullableIntegerField.getDefault(), 
            "Default should be null for nullable integer field with defaultValue=\"null\"");
    }


    @Test
    public void testNullableBigDecimalWithNullExampleAndDefault_OAS30() {
        final ModelResolver modelResolver = new ModelResolver(Json.mapper());
        Configuration configuration = new Configuration();
        configuration.setOpenAPI31(false);
        modelResolver.setConfiguration(configuration);
        final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);

        final io.swagger.v3.oas.models.media.Schema model = context
                .resolve(new AnnotatedType(NullableBigDecimalModel.class));

        assertNotNull(model);
        assertNotNull(model.getProperties());

        io.swagger.v3.oas.models.media.Schema nullableBigDecimalField =
                (io.swagger.v3.oas.models.media.Schema) model.getProperties().get("nullableBigDecimalField");
        assertNotNull(nullableBigDecimalField, "nullableBigDecimalField property should exist");
        assertEquals(nullableBigDecimalField.getNullable(), Boolean.TRUE, "Field should be nullable");
        
        assertNull(nullableBigDecimalField.getExample(),
            "Example should be null for nullable number field with example=\"null\"");
        assertNull(nullableBigDecimalField.getDefault(), 
            "Default should be null for nullable number field with defaultValue=\"null\"");
    }

    @Test
    public void testNullableBooleanWithNullExampleAndDefault_OAS30() {
        final ModelResolver modelResolver = new ModelResolver(Json.mapper());
        Configuration configuration = new Configuration();
        configuration.setOpenAPI31(false);
        modelResolver.setConfiguration(configuration);
        final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);

        final io.swagger.v3.oas.models.media.Schema model = context
                .resolve(new AnnotatedType(NullableBooleanModel.class));


        assertNotNull(model);
        assertNotNull(model.getProperties());

        io.swagger.v3.oas.models.media.Schema nullableBooleanField =
                (io.swagger.v3.oas.models.media.Schema) model.getProperties().get("nullableBooleanField");
        assertNotNull(nullableBooleanField, "nullableBooleanField property should exist");
        assertEquals(nullableBooleanField.getNullable(), Boolean.TRUE, "Field should be nullable");

        assertNull(nullableBooleanField.getExample(), 
            "Example should be null for nullable boolean field with example=\"null\", but got: " + nullableBooleanField.getExample());
        assertNull(nullableBooleanField.getDefault(), 
            "Default should be null for nullable boolean field with defaultValue=\"null\", but got: " + nullableBooleanField.getDefault());
    }

    @Test
    public void testNullableObjectWithNullExampleAndDefault_OAS30() {
        final ModelResolver modelResolver = new ModelResolver(Json.mapper());
        Configuration configuration = new Configuration();
        configuration.setOpenAPI31(false);
        modelResolver.setConfiguration(configuration);
        final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);

        final io.swagger.v3.oas.models.media.Schema model = context
                .resolve(new AnnotatedType(NullableObjectModel.class));

        assertNotNull(model);
        assertNotNull(model.getProperties());

        io.swagger.v3.oas.models.media.Schema nullableObjectField =
                (io.swagger.v3.oas.models.media.Schema) model.getProperties().get("nullableObjectField");
        assertNotNull(nullableObjectField, "nullableObjectField property should exist");
        assertEquals(nullableObjectField.getNullable(), Boolean.TRUE, "Field should be nullable");
        
        assertNull(nullableObjectField.getExample(), "Object example should be null");
        assertNull(nullableObjectField.getDefault(), "Object default should be null");
    }

    // OAS 3.1 Tests


    @Test
    public void testNullableStringWithNullExampleAndDefault_OAS31() {
        final ModelResolver modelResolver = new ModelResolver(Json31.mapper());
        Configuration configuration = new Configuration();
        configuration.setOpenAPI31(true);
        modelResolver.setConfiguration(configuration);
        final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);

        final io.swagger.v3.oas.models.media.Schema model = context
                .resolve(new AnnotatedType(NullableStringModel.class));

        assertNotNull(model);
        assertNotNull(model.getProperties());

        io.swagger.v3.oas.models.media.Schema nullableStringField =
                (io.swagger.v3.oas.models.media.Schema) model.getProperties().get("nullableStringField");
        assertNotNull(nullableStringField, "nullableStringField property should exist");
        assertTrue(isNullableInOAS31(nullableStringField), "Field should be nullable (type array should contain 'null')");

        assertNull(nullableStringField.getExample(), 
            "Example should be null for nullable field with example=\"null\", but got: " + nullableStringField.getExample());
        assertNull(nullableStringField.getDefault(), 
            "Default should be null for nullable field with defaultValue=\"null\", but got: " + nullableStringField.getDefault());
    }

    @Test
    public void testNullableIntegerWithNullExampleAndDefault_OAS31() {
        final ModelResolver modelResolver = new ModelResolver(Json31.mapper());
        Configuration configuration = new Configuration();
        configuration.setOpenAPI31(true);
        modelResolver.setConfiguration(configuration);
        final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);

        final io.swagger.v3.oas.models.media.Schema model = context
                .resolve(new AnnotatedType(NullableIntegerModel.class));

        assertNotNull(model);
        assertNotNull(model.getProperties());

        io.swagger.v3.oas.models.media.Schema nullableIntegerField =
                (io.swagger.v3.oas.models.media.Schema) model.getProperties().get("nullableIntegerField");
        assertNotNull(nullableIntegerField, "nullableIntegerField property should exist");
        assertTrue(isNullableInOAS31(nullableIntegerField), "Field should be nullable (type array should contain 'null')");
        
        assertNull(nullableIntegerField.getExample(),
            "Example should be null for nullable integer field with example=\"null\"");
        assertNull(nullableIntegerField.getDefault(), 
            "Default should be null for nullable integer field with defaultValue=\"null\"");
    }

    @Test
    public void testNullableBigDecimalWithNullExampleAndDefault_OAS31() {
        final ModelResolver modelResolver = new ModelResolver(Json31.mapper());
        Configuration configuration = new Configuration();
        configuration.setOpenAPI31(true);
        modelResolver.setConfiguration(configuration);
        final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);

        final io.swagger.v3.oas.models.media.Schema model = context
                .resolve(new AnnotatedType(NullableBigDecimalModel.class));

        assertNotNull(model);
        assertNotNull(model.getProperties());

        io.swagger.v3.oas.models.media.Schema nullableBigDecimalField =
                (io.swagger.v3.oas.models.media.Schema) model.getProperties().get("nullableBigDecimalField");
        assertNotNull(nullableBigDecimalField, "nullableBigDecimalField property should exist");
        assertTrue(isNullableInOAS31(nullableBigDecimalField), "Field should be nullable (type array should contain 'null')");
        
        assertNull(nullableBigDecimalField.getExample(),
            "Example should be null for nullable number field with example=\"null\"");
        assertNull(nullableBigDecimalField.getDefault(), 
            "Default should be null for nullable number field with defaultValue=\"null\"");
    }


    @Test
    public void testNullableBooleanWithNullExampleAndDefault_OAS31() {
        final ModelResolver modelResolver = new ModelResolver(Json31.mapper());
        Configuration configuration = new Configuration();
        configuration.setOpenAPI31(true);
        modelResolver.setConfiguration(configuration);
        final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);

        final io.swagger.v3.oas.models.media.Schema model = context
                .resolve(new AnnotatedType(NullableBooleanModel.class));

        assertNotNull(model);
        assertNotNull(model.getProperties());

        io.swagger.v3.oas.models.media.Schema nullableBooleanField =
                (io.swagger.v3.oas.models.media.Schema) model.getProperties().get("nullableBooleanField");
        assertNotNull(nullableBooleanField, "nullableBooleanField property should exist");
        assertTrue(isNullableInOAS31(nullableBooleanField), "Field should be nullable (type array should contain 'null')");
        

        assertNull(nullableBooleanField.getExample(), 
            "Example should be null for nullable boolean field with example=\"null\", but got: " + nullableBooleanField.getExample());
        assertNull(nullableBooleanField.getDefault(), 
            "Default should be null for nullable boolean field with defaultValue=\"null\", but got: " + nullableBooleanField.getDefault());
    }


    @Test
    public void testNullableObjectWithNullExampleAndDefault_OAS31() {
        final ModelResolver modelResolver = new ModelResolver(Json31.mapper());
        Configuration configuration = new Configuration();
        configuration.setOpenAPI31(true);
        modelResolver.setConfiguration(configuration);
        final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);

        final io.swagger.v3.oas.models.media.Schema model = context
                .resolve(new AnnotatedType(NullableObjectModel.class));

        assertNotNull(model);
        assertNotNull(model.getProperties());

        io.swagger.v3.oas.models.media.Schema nullableObjectField =
                (io.swagger.v3.oas.models.media.Schema) model.getProperties().get("nullableObjectField");
        assertNotNull(nullableObjectField, "nullableObjectField property should exist");
        assertTrue(isNullableInOAS31(nullableObjectField), "Field should be nullable (type array should contain 'null')");
        
        assertNull(nullableObjectField.getExample(), "Object example should be null");
        assertNull(nullableObjectField.getDefault(), "Object default should be null");
    }

    // Tests for omitted example/default values


    @Test
    public void testNullableWithoutExampleOrDefault_OAS30() {
        final ModelResolver modelResolver = new ModelResolver(Json.mapper());
        Configuration configuration = new Configuration();
        configuration.setOpenAPI31(false);
        modelResolver.setConfiguration(configuration);
        final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);

        final io.swagger.v3.oas.models.media.Schema model = context
                .resolve(new AnnotatedType(NullableWithoutExampleDefaultModel.class));

        assertNotNull(model);
        assertNotNull(model.getProperties());

        io.swagger.v3.oas.models.media.Schema stringField =
                (io.swagger.v3.oas.models.media.Schema) model.getProperties().get("stringField");
        assertNotNull(stringField, "stringField property should exist");
        assertEquals(stringField.getNullable(), Boolean.TRUE, "Field should be nullable");
        assertNull(stringField.getExample(), "Example should be null (not set)");
        assertNull(stringField.getDefault(), "Default should be null (not set)");

        io.swagger.v3.oas.models.media.Schema integerField =
                (io.swagger.v3.oas.models.media.Schema) model.getProperties().get("integerField");
        assertNotNull(integerField, "integerField property should exist");
        assertEquals(integerField.getNullable(), Boolean.TRUE, "Field should be nullable");
        assertNull(integerField.getExample(), "Example should be null (not set)");
        assertNull(integerField.getDefault(), "Default should be null (not set)");

        io.swagger.v3.oas.models.media.Schema booleanField =
                (io.swagger.v3.oas.models.media.Schema) model.getProperties().get("booleanField");
        assertNotNull(booleanField, "booleanField property should exist");
        assertEquals(booleanField.getNullable(), Boolean.TRUE, "Field should be nullable");
        assertNull(booleanField.getExample(), "Example should be null (not set)");
        assertNull(booleanField.getDefault(), "Default should be null (not set)");
    }

    @Test
    public void testNullableWithoutExampleOrDefault_OAS31() {
        final ModelResolver modelResolver = new ModelResolver(Json31.mapper());
        Configuration configuration = new Configuration();
        configuration.setOpenAPI31(true);
        modelResolver.setConfiguration(configuration);
        final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);

        final io.swagger.v3.oas.models.media.Schema model = context
                .resolve(new AnnotatedType(NullableWithoutExampleDefaultModel.class));

        assertNotNull(model);
        assertNotNull(model.getProperties());

        io.swagger.v3.oas.models.media.Schema stringField =
                (io.swagger.v3.oas.models.media.Schema) model.getProperties().get("stringField");
        assertNotNull(stringField, "stringField property should exist");
        assertTrue(isNullableInOAS31(stringField), "Field should be nullable (type array should contain 'null')");
        assertNull(stringField.getExample(), "Example should be null (not set)");
        assertNull(stringField.getDefault(), "Default should be null (not set)");

        io.swagger.v3.oas.models.media.Schema integerField =
                (io.swagger.v3.oas.models.media.Schema) model.getProperties().get("integerField");
        assertNotNull(integerField, "integerField property should exist");
        assertTrue(isNullableInOAS31(integerField), "Field should be nullable (type array should contain 'null')");
        assertNull(integerField.getExample(), "Example should be null (not set)");
        assertNull(integerField.getDefault(), "Default should be null (not set)");

        io.swagger.v3.oas.models.media.Schema booleanField =
                (io.swagger.v3.oas.models.media.Schema) model.getProperties().get("booleanField");
        assertNotNull(booleanField, "booleanField property should exist");
        assertTrue(isNullableInOAS31(booleanField), "Field should be nullable (type array should contain 'null')");
        assertNull(booleanField.getExample(), "Example should be null (not set)");
        assertNull(booleanField.getDefault(), "Default should be null (not set)");
    }

    @Test
    public void testNullableWithDefaultOnly_OAS30() {
        final ModelResolver modelResolver = new ModelResolver(Json.mapper());
        Configuration configuration = new Configuration();
        configuration.setOpenAPI31(false);
        modelResolver.setConfiguration(configuration);
        final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);

        final io.swagger.v3.oas.models.media.Schema model = context
                .resolve(new AnnotatedType(NullableWithDefaultOnlyModel.class));

        assertNotNull(model);
        assertNotNull(model.getProperties());

        io.swagger.v3.oas.models.media.Schema stringField =
                (io.swagger.v3.oas.models.media.Schema) model.getProperties().get("stringField");
        assertNotNull(stringField, "stringField property should exist");
        assertNull(stringField.getExample(), "Example should be null (not set)");
        assertEquals(stringField.getDefault(), "defaultValue", "Default should be 'defaultValue'");
    }

    @Test
    public void testNullableWithExampleOnly_OAS30() {
        final ModelResolver modelResolver = new ModelResolver(Json.mapper());
        Configuration configuration = new Configuration();
        configuration.setOpenAPI31(false);
        modelResolver.setConfiguration(configuration);
        final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);

        final io.swagger.v3.oas.models.media.Schema model = context
                .resolve(new AnnotatedType(NullableWithExampleOnlyModel.class));

        assertNotNull(model);
        assertNotNull(model.getProperties());

        io.swagger.v3.oas.models.media.Schema stringField =
                (io.swagger.v3.oas.models.media.Schema) model.getProperties().get("stringField");
        assertNotNull(stringField, "stringField property should exist");
        assertEquals(stringField.getExample(), "exampleValue", "Example should be 'exampleValue'");
        assertNull(stringField.getDefault(), "Default should be null (not set)");
    }

    // ========== Tests for nullable=false with example/default="null" ==========

    /**
     * Test non-nullable field with example="null" and defaultValue="null" - OAS 3.0
     * When nullable=false, "null" string should NOT be parsed as null value
     * It should be treated as the literal string "null"
     */
    @Test
    public void testNonNullableStringWithNullExampleAndDefault_OAS30() {
        final ModelResolver modelResolver = new ModelResolver(Json.mapper());
        Configuration configuration = new Configuration();
        configuration.setOpenAPI31(false);
        modelResolver.setConfiguration(configuration);
        final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);

        final io.swagger.v3.oas.models.media.Schema model = context
                .resolve(new AnnotatedType(NonNullableStringModel.class));


        assertNotNull(model);
        assertNotNull(model.getProperties());

        io.swagger.v3.oas.models.media.Schema stringField =
                (io.swagger.v3.oas.models.media.Schema) model.getProperties().get("stringField");
        assertNotNull(stringField, "stringField property should exist");
        assertNull(stringField.getNullable(), "Field should not have nullable set (or be false)");

        // When nullable=false, "null" string should be treated as literal string "null", not null value
        assertEquals(stringField.getExample(), "null",
                "Example should be the string \"null\", not null value when nullable=false");
        assertEquals(stringField.getDefault(), "null",
                "Default should be the string \"null\", not null value when nullable=false");
    }

    /**
     * Test non-nullable field with example="null" and defaultValue="null" - OAS 3.1
     * When nullable=false, "null" string should NOT be parsed as null value
     */
    @Test
    public void testNonNullableStringWithNullExampleAndDefault_OAS31() {
        final ModelResolver modelResolver = new ModelResolver(Json31.mapper());
        Configuration configuration = new Configuration();
        configuration.setOpenAPI31(true);
        modelResolver.setConfiguration(configuration);
        final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);

        final io.swagger.v3.oas.models.media.Schema model = context
                .resolve(new AnnotatedType(NonNullableStringModel.class));

        assertNotNull(model);
        assertNotNull(model.getProperties());

        io.swagger.v3.oas.models.media.Schema stringField =
                (io.swagger.v3.oas.models.media.Schema) model.getProperties().get("stringField");
        assertNotNull(stringField, "stringField property should exist");
        assertNull(stringField.getNullable(), "Field should not have nullable set in OAS 3.1");

        // When nullable=false, "null" string should be treated as literal string "null", not null value
        assertEquals(stringField.getExample(), "null",
                "Example should be the string \"null\", not null value when nullable=false");
        assertEquals(stringField.getDefault(), "null",
                "Default should be the string \"null\", not null value when nullable=false");
    }

    /**
     * Test non-nullable Boolean field - OAS 3.0
     */
    @Test
    public void testNonNullableBooleanWithNullExampleAndDefault_OAS30() {
        final ModelResolver modelResolver = new ModelResolver(Json.mapper());
        Configuration configuration = new Configuration();
        configuration.setOpenAPI31(false);
        modelResolver.setConfiguration(configuration);
        final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);

        final io.swagger.v3.oas.models.media.Schema model = context
                .resolve(new AnnotatedType(NonNullableBooleanModel.class));


        assertNotNull(model);
        assertNotNull(model.getProperties());

        io.swagger.v3.oas.models.media.Schema booleanField =
                (io.swagger.v3.oas.models.media.Schema) model.getProperties().get("booleanField");
        assertNotNull(booleanField, "booleanField property should exist");
        assertNull(booleanField.getNullable(), "Field should not have nullable set");

        assertEquals(booleanField.getExample(), false,
                "Should convert to false");
        assertEquals(booleanField.getDefault(), false,
                "Should convert to false");
    }

    /**
     * Test non-nullable Integer field - OAS 3.1
     * Should treat "null" as string, not parse to null value
     */
    @Test
    public void testNonNullableIntegerWithNullExampleAndDefault_OAS31() {
        final ModelResolver modelResolver = new ModelResolver(Json31.mapper());
        Configuration configuration = new Configuration();
        configuration.setOpenAPI31(true);
        modelResolver.setConfiguration(configuration);
        final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);

        final io.swagger.v3.oas.models.media.Schema model = context
                .resolve(new AnnotatedType(NonNullableIntegerModel.class));

        assertNotNull(model);
        assertNotNull(model.getProperties());

        io.swagger.v3.oas.models.media.Schema integerField =
                (io.swagger.v3.oas.models.media.Schema) model.getProperties().get("integerField");
        assertNotNull(integerField, "integerField property should exist");

        assertEquals(integerField.getExample(), NullNode.getInstance(),
                "Example should be the NullNode \"null\", not null value");
        assertEquals(integerField.getDefault(), "null",
                "Default should be the string \"null\", not null value");
    }

    // ========== Test for Issue #4229 (same root cause as #4339) ==========

    /**
     * Test case from Issue #4229: Nullable Integer ignores example value "null" and defaults to 0
     * https://github.com/swagger-api/swagger-core/issues/4229
     * This should now be fixed by the same changes that fix #4339
     */
    @Test
    public void testIssue4229_NullableIntegerWithNullExample() {
        final ModelResolver modelResolver = new ModelResolver(Json.mapper());
        Configuration configuration = new Configuration();
        configuration.setOpenAPI31(false);
        modelResolver.setConfiguration(configuration);
        final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);

        final io.swagger.v3.oas.models.media.Schema model = context
                .resolve(new AnnotatedType(Issue4229TestData.class));

        assertNotNull(model);
        assertNotNull(model.getProperties());

        io.swagger.v3.oas.models.media.Schema idSchema =
                (io.swagger.v3.oas.models.media.Schema) model.getProperties().get("id");
        assertNotNull(idSchema, "id property should exist");
        assertEquals(idSchema.getNullable(), Boolean.TRUE, "Field should be nullable");

        // Issue #4229: exampleSetFlag should be true and example should be null, not 0
        assertTrue(idSchema.getExampleSetFlag(),
                "exampleSetFlag should be true when example=\"null\" is set");
        assertNull(idSchema.getExample(),
                "Example should be null, not 0 (fixes issue #4229)");
    }

    // ========== Tests for OAS 3.1 "examples" array field ==========

    /**
     * Test OAS 3.1 examples array with null value
     * In OAS 3.1, there's an "examples" array field in addition to the single "example" field
     */
    @Test
    public void testNullableStringWithNullInExamplesArray_OAS31() {
        final ModelResolver modelResolver = new ModelResolver(Json31.mapper());
        Configuration configuration = new Configuration();
        configuration.setOpenAPI31(true);
        modelResolver.setConfiguration(configuration);
        final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);

        final io.swagger.v3.oas.models.media.Schema model = context
                .resolve(new AnnotatedType(NullableStringWithExamplesModel.class));

        assertNotNull(model);
        assertNotNull(model.getProperties());

        io.swagger.v3.oas.models.media.Schema stringField =
                (io.swagger.v3.oas.models.media.Schema) model.getProperties().get("stringField");
        assertNotNull(stringField, "stringField property should exist");
        assertTrue(isNullableInOAS31(stringField), "Field should be nullable (type array should contain 'null')");

        assertNotNull(stringField.getExamples(), "examples array should exist");
        assertEquals(stringField.getExamples().size(), 3, "examples array should have 3 elements");

        assertTrue(stringField.getExamples().contains("validValue"), "examples should contain 'validValue'");
        assertTrue(stringField.getExamples().contains(null), "examples should contain null value");
        assertTrue(stringField.getExamples().contains("anotherValue"), "examples should contain 'anotherValue'");
    }

    /**
     * Test OAS 3.1 examples array with multiple values including null
     */
    @Test
    public void testNullableIntegerWithMultipleExamplesIncludingNull_OAS31() {
        final ModelResolver modelResolver = new ModelResolver(Json31.mapper());
        Configuration configuration = new Configuration();
        configuration.setOpenAPI31(true);
        modelResolver.setConfiguration(configuration);
        final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);

        final io.swagger.v3.oas.models.media.Schema model = context
                .resolve(new AnnotatedType(NullableIntegerWithMultipleExamplesModel.class));

        assertNotNull(model);
        assertNotNull(model.getProperties());

        io.swagger.v3.oas.models.media.Schema integerField =
                (io.swagger.v3.oas.models.media.Schema) model.getProperties().get("integerField");
        assertNotNull(integerField, "integerField property should exist");

        assertNotNull(integerField.getExamples(), "examples array should exist");
        assertEquals(integerField.getExamples().size(), 3, "examples array should have 3 elements");

        assertTrue(integerField.getExamples().contains(IntNode.valueOf(1)), "examples should contain 1 as number");
        assertTrue(integerField.getExamples().contains(null), "examples should contain null value");
        assertTrue(integerField.getExamples().contains(IntNode.valueOf(100)), "examples should contain 100 as number");
    }

    /**
     * Test that examples array is NOT set in OAS 3.0 (only OAS 3.1)
     * 
     * Note: In OAS 3.0, the 'examples' field (plural, as array) is only valid on:
     * - Parameter Object (parameters[*].examples) - as Map<String, Example>
     * - Media Type Object (content[media-type].examples) - as Map<String, Example>
     * - Header Object (headers[*].examples) - as Map<String, Example>
     * 
     * For Schema objects, OAS 3.0 only supports the singular 'example' field.
     * The 'examples' array was added to Schema objects in OAS 3.1.
     */
    @Test
    public void testExamplesArrayNotSetInOAS30() {
        final ModelResolver modelResolver = new ModelResolver(Json.mapper());
        Configuration configuration = new Configuration();
        configuration.setOpenAPI31(false);
        modelResolver.setConfiguration(configuration);
        final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);

        final io.swagger.v3.oas.models.media.Schema model = context
                .resolve(new AnnotatedType(NullableStringWithExamplesModel.class));

        assertNotNull(model);
        assertNotNull(model.getProperties());

        io.swagger.v3.oas.models.media.Schema stringField =
                (io.swagger.v3.oas.models.media.Schema) model.getProperties().get("stringField");
        assertNotNull(stringField, "stringField property should exist");
        assertEquals(stringField.getNullable(), Boolean.TRUE, "Field should be nullable");

        assertNull(stringField.getExamples(), "examples array should NOT be set in OAS 3.0");
    }

    /**
     * Test that when both example and examples are set, only examples is used in OAS 3.1
     * The single example field is ignored by design when examples array is present
     */
    @Test
    public void testExamplesArrayTakesPrecedenceOverExample_OAS31() {
        final ModelResolver modelResolver = new ModelResolver(Json31.mapper());
        Configuration configuration = new Configuration();
        configuration.setOpenAPI31(true);
        modelResolver.setConfiguration(configuration);
        final ModelConverterContextImpl context = new ModelConverterContextImpl(modelResolver);

        final io.swagger.v3.oas.models.media.Schema model = context
                .resolve(new AnnotatedType(BothExampleAndExamplesModel.class));

        assertNotNull(model);
        assertNotNull(model.getProperties());

        io.swagger.v3.oas.models.media.Schema stringField =
                (io.swagger.v3.oas.models.media.Schema) model.getProperties().get("stringField");
        assertNotNull(stringField, "stringField property should exist");
        assertTrue(isNullableInOAS31(stringField), "Field should be nullable (type array should contain 'null')");

        assertNull(stringField.getExample(),
                "example field should be null/not set when examples array is present");
        assertNotNull(stringField.getExamples(), "examples array should be set");
        assertEquals(stringField.getExamples().size(), 3, "examples array should have 3 elements");

        assertTrue(stringField.getExamples().contains("arrayValue1"), "examples should contain 'arrayValue1'");
        assertTrue(stringField.getExamples().contains(null), "examples should contain null");
        assertTrue(stringField.getExamples().contains("arrayValue3"), "examples should contain 'arrayValue3'");

        assertFalse(stringField.getExamples().contains("singleExampleValue"),
                "examples should NOT contain the value from single example field");
    }

    private boolean isNullableInOAS31(io.swagger.v3.oas.models.media.Schema schema) {
        if (schema.getTypes() != null && schema.getTypes().contains("null")) {
            return true;
        }
        return false;
    }

    // Test model classes

    public static class NullableStringModel {
        @Schema(nullable = true, example = "null", defaultValue = "null")
        private String nullableStringField;

        public String getNullableStringField() {
            return nullableStringField;
        }

        public void setNullableStringField(String nullableStringField) {
            this.nullableStringField = nullableStringField;
        }
    }

    public static class NullableIntegerModel {
        @Schema(nullable = true, example = "null", defaultValue = "null")
        private Integer nullableIntegerField;

        public Integer getNullableIntegerField() {
            return nullableIntegerField;
        }

        public void setNullableIntegerField(Integer nullableIntegerField) {
            this.nullableIntegerField = nullableIntegerField;
        }
    }

    public static class NullableBigDecimalModel {
        @Schema(nullable = true, example = "null", defaultValue = "null")
        private BigDecimal nullableBigDecimalField;

        public BigDecimal getNullableBigDecimalField() {
            return nullableBigDecimalField;
        }

        public void setNullableBigDecimalField(BigDecimal nullableBigDecimalField) {
            this.nullableBigDecimalField = nullableBigDecimalField;
        }
    }

    public static class NullableBooleanModel {
        @Schema(nullable = true, example = "null", defaultValue = "null")
        private Boolean nullableBooleanField;

        public Boolean getNullableBooleanField() {
            return nullableBooleanField;
        }

        public void setNullableBooleanField(Boolean nullableBooleanField) {
            this.nullableBooleanField = nullableBooleanField;
        }
    }

    public static class NullableObjectModel {
        @Schema(nullable = true, example = "null", defaultValue = "null")
        private Object nullableObjectField;

        public Object getNullableObjectField() {
            return nullableObjectField;
        }

        public void setNullableObjectField(Object nullableObjectField) {
            this.nullableObjectField = nullableObjectField;
        }
    }


    public static class NullableWithoutExampleDefaultModel {
        @Schema(nullable = true)
        private String stringField;

        @Schema(nullable = true)
        private Integer integerField;

        @Schema(nullable = true)
        private Boolean booleanField;

        public String getStringField() {
            return stringField;
        }

        public void setStringField(String stringField) {
            this.stringField = stringField;
        }

        public Integer getIntegerField() {
            return integerField;
        }

        public void setIntegerField(Integer integerField) {
            this.integerField = integerField;
        }

        public Boolean getBooleanField() {
            return booleanField;
        }

        public void setBooleanField(Boolean booleanField) {
            this.booleanField = booleanField;
        }
    }

    public static class NullableWithDefaultOnlyModel {
        @Schema(nullable = true, defaultValue = "defaultValue")
        private String stringField;

        public String getStringField() {
            return stringField;
        }

        public void setStringField(String stringField) {
            this.stringField = stringField;
        }
    }

    public static class NullableWithExampleOnlyModel {
        @Schema(nullable = true, example = "exampleValue")
        private String stringField;

        public String getStringField() {
            return stringField;
        }

        public void setStringField(String stringField) {
            this.stringField = stringField;
        }
    }


    // Test model classes for non-nullable fields with null example/default

    public static class NonNullableStringModel {
        @Schema(nullable = false, example = "null", defaultValue = "null")
        private String stringField;

        public String getStringField() {
            return stringField;
        }

        public void setStringField(String stringField) {
            this.stringField = stringField;
        }
    }

    public static class NonNullableBooleanModel {
        @Schema(nullable = false, example = "null", defaultValue = "null")
        private Boolean booleanField;

        public Boolean getBooleanField() {
            return booleanField;
        }

        public void setBooleanField(Boolean booleanField) {
            this.booleanField = booleanField;
        }
    }

    public static class NonNullableIntegerModel {
        @Schema(nullable = false, example = "null", defaultValue = "null")
        private Integer integerField;

        public Integer getIntegerField() {
            return integerField;
        }

        public void setIntegerField(Integer integerField) {
            this.integerField = integerField;
        }
    }

    /**
     * Test model class for Issue #4229
     * Matches the exact example from the issue report
     */
    public static class Issue4229TestData {
        @Schema(nullable = true, example = "null")
        private Integer id;

        public Integer getId() {
            return id;
        }

        public void setId(Integer id) {
            this.id = id;
        }
    }

    /**
     * Test model with examples array containing "null" string in OAS 3.1
     */
    public static class NullableStringWithExamplesModel {
        @Schema(nullable = true, examples = {"validValue", "null", "anotherValue"})
        private String stringField;

        public String getStringField() {
            return stringField;
        }

        public void setStringField(String stringField) {
            this.stringField = stringField;
        }
    }

    /**
     * Test model with examples array containing multiple values including "null"
     */
    public static class NullableIntegerWithMultipleExamplesModel {
        @Schema(nullable = true, examples = {"1", "null", "100"})
        private Integer integerField;

        public Integer getIntegerField() {
            return integerField;
        }

        public void setIntegerField(Integer integerField) {
            this.integerField = integerField;
        }
    }


    /**
     * Test model with both example and examples set
     * Only examples should be used in the generated OpenAPI
     */
    public static class BothExampleAndExamplesModel {
        @Schema(nullable = true, example = "singleExampleValue", examples = {"arrayValue1", "null", "arrayValue3"})
        private String stringField;

        public String getStringField() {
            return stringField;
        }

        public void setStringField(String stringField) {
            this.stringField = stringField;
        }
    }
}