This is an automated email from the ASF dual-hosted git repository.

robertlazarski pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/axis-axis2-java-core.git

commit d9baa0c85eae622ba4c8c1c9b4477924ee939bba
Author: Robert Lazarski <[email protected]>
AuthorDate: Mon May 11 17:01:40 2026 -1000

    Add HbmBatchSchemaGenerator tests with StockExchangeBO fixture
    
    11 new tests covering the batch generator and a production-style
    HBM XML mapping (StockExchangeBO) that exercises:
    - id with native generator
    - version field (excluded from write schema)
    - not-null required property (name)
    - nullable properties (micCode, country, city, etc.)
    - many-to-one relationship (region → RegionBO)
    - set collection (closedDates → ExchangeClosedDateBO)
    - nested <column not-null="true"> element (deleted)
    - read schema: all fields, ID readOnly, $ref relationships
    - write schema: excludes id/version, $ref uses Write suffix
    - end-to-end batch: scans test resources dir, produces valid
      OpenAPI 3.0 JSON with read+write schemas for all entities
    
    StockExchangeBO.hbm.xml is a sanitized, OSS-clean test fixture
    derived from a real-world exchange entity mapping.
    
    40 tests total (29 existing + 11 new), all pass.
    
    Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
---
 .../jpa/schema/HbmBatchSchemaGeneratorTest.java    | 280 +++++++++++++++++++++
 .../src/test/resources/StockExchangeBO.hbm.xml     |  60 +++++
 2 files changed, 340 insertions(+)

diff --git 
a/modules/jpa-schema/src/test/java/org/apache/axis2/jpa/schema/HbmBatchSchemaGeneratorTest.java
 
b/modules/jpa-schema/src/test/java/org/apache/axis2/jpa/schema/HbmBatchSchemaGeneratorTest.java
new file mode 100644
index 0000000000..a999f76e75
--- /dev/null
+++ 
b/modules/jpa-schema/src/test/java/org/apache/axis2/jpa/schema/HbmBatchSchemaGeneratorTest.java
@@ -0,0 +1,280 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.axis2.jpa.schema;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.nio.file.Files;
+
+import static org.junit.Assert.*;
+
+/**
+ * Tests for {@link HbmBatchSchemaGenerator} — the CLI batch tool that
+ * converts a directory of HBM XML files into an OpenAPI 3.0 schemas JSON.
+ *
+ * <p>Uses test HBM files on the classpath: CompanyBO.hbm.xml,
+ * DepartmentBO.hbm.xml, StockExchangeBO.hbm.xml.
+ */
+public class HbmBatchSchemaGeneratorTest {
+
+    private static final ObjectMapper mapper = new ObjectMapper();
+
+    // ═══════════════════════════════════════════════════════════════════════
+    // StockExchangeBO introspection — validates a production-style HBM
+    // mapping with id+generator, version, not-null, many-to-one, set,
+    // nested column element
+    // ═══════════════════════════════════════════════════════════════════════
+
+    @Test
+    public void testStockExchange_parsesEntity() throws IOException {
+        try (InputStream is = 
getClass().getResourceAsStream("/StockExchangeBO.hbm.xml")) {
+            assertNotNull("StockExchangeBO.hbm.xml must be on classpath", is);
+
+            HbmXmlIntrospector introspector = new HbmXmlIntrospector();
+            EntitySchemaModel model = introspector.introspect(is, 
"StockExchangeBO.hbm.xml");
+
+            assertNotNull(model);
+            assertEquals("StockExchangeBO", model.getEntityName());
+            assertEquals("STOCK_EXCHANGE", model.getTableName());
+        }
+    }
+
+    @Test
+    public void testStockExchange_idAndVersion() throws IOException {
+        try (InputStream is = 
getClass().getResourceAsStream("/StockExchangeBO.hbm.xml")) {
+            HbmXmlIntrospector introspector = new HbmXmlIntrospector();
+            EntitySchemaModel model = introspector.introspect(is, 
"StockExchangeBO.hbm.xml");
+
+            assertEquals(1, model.getIdFieldNames().size());
+            assertEquals("id", model.getIdFieldNames().get(0));
+
+            EntitySchemaModel.FieldModel idField = model.getFields().get("id");
+            assertTrue("ID should be generated", idField.isGeneratedValue());
+            assertFalse("ID should not be nullable", idField.isNullable());
+
+            assertEquals("version", model.getVersionFieldName());
+            assertTrue(model.getFields().get("version").isVersionField());
+            assertTrue(model.getFields().get("version").isExcludedFromWrite());
+        }
+    }
+
+    @Test
+    public void testStockExchange_requiredProperty() throws IOException {
+        try (InputStream is = 
getClass().getResourceAsStream("/StockExchangeBO.hbm.xml")) {
+            HbmXmlIntrospector introspector = new HbmXmlIntrospector();
+            EntitySchemaModel model = introspector.introspect(is, 
"StockExchangeBO.hbm.xml");
+
+            EntitySchemaModel.FieldModel name = model.getFields().get("name");
+            assertNotNull(name);
+            assertEquals("string", name.getJsonSchemaType());
+            assertFalse("name is not-null=true", name.isNullable());
+        }
+    }
+
+    @Test
+    public void testStockExchange_nullableProperties() throws IOException {
+        try (InputStream is = 
getClass().getResourceAsStream("/StockExchangeBO.hbm.xml")) {
+            HbmXmlIntrospector introspector = new HbmXmlIntrospector();
+            EntitySchemaModel model = introspector.introspect(is, 
"StockExchangeBO.hbm.xml");
+
+            for (String field : new String[]{"micCode", "country", "city", 
"website", "timeZoneName"}) {
+                EntitySchemaModel.FieldModel f = model.getFields().get(field);
+                assertNotNull(field + " should exist", f);
+                assertTrue(field + " should be nullable", f.isNullable());
+            }
+        }
+    }
+
+    @Test
+    public void testStockExchange_manyToOneRelationship() throws IOException {
+        try (InputStream is = 
getClass().getResourceAsStream("/StockExchangeBO.hbm.xml")) {
+            HbmXmlIntrospector introspector = new HbmXmlIntrospector();
+            EntitySchemaModel model = introspector.introspect(is, 
"StockExchangeBO.hbm.xml");
+
+            EntitySchemaModel.FieldModel region = 
model.getFields().get("region");
+            assertNotNull("region relationship should exist", region);
+            assertEquals("object", region.getJsonSchemaType());
+            assertEquals("RegionBO", region.getRefEntityName());
+            assertFalse(region.isCollection());
+        }
+    }
+
+    @Test
+    public void testStockExchange_setCollection() throws IOException {
+        try (InputStream is = 
getClass().getResourceAsStream("/StockExchangeBO.hbm.xml")) {
+            HbmXmlIntrospector introspector = new HbmXmlIntrospector();
+            EntitySchemaModel model = introspector.introspect(is, 
"StockExchangeBO.hbm.xml");
+
+            EntitySchemaModel.FieldModel closedDates = 
model.getFields().get("closedDates");
+            assertNotNull("closedDates set should exist", closedDates);
+            assertEquals("array", closedDates.getJsonSchemaType());
+            assertTrue(closedDates.isCollection());
+            assertEquals("ExchangeClosedDateBO", 
closedDates.getRefEntityName());
+        }
+    }
+
+    @Test
+    public void testStockExchange_nestedColumnNotNull() throws IOException {
+        try (InputStream is = 
getClass().getResourceAsStream("/StockExchangeBO.hbm.xml")) {
+            HbmXmlIntrospector introspector = new HbmXmlIntrospector();
+            EntitySchemaModel model = introspector.introspect(is, 
"StockExchangeBO.hbm.xml");
+
+            EntitySchemaModel.FieldModel deleted = 
model.getFields().get("deleted");
+            assertNotNull(deleted);
+            assertEquals("boolean", deleted.getJsonSchemaType());
+            assertFalse("deleted with nested column not-null=true should be 
required",
+                    deleted.isNullable());
+        }
+    }
+
+    @Test
+    public void testStockExchange_readSchemaIncludesAll() throws IOException {
+        try (InputStream is = 
getClass().getResourceAsStream("/StockExchangeBO.hbm.xml")) {
+            HbmXmlIntrospector introspector = new HbmXmlIntrospector();
+            EntitySchemaModel model = introspector.introspect(is, 
"StockExchangeBO.hbm.xml");
+
+            JsonNode readSchema = JpaSchemaGenerator.generateReadSchema(model);
+            JsonNode props = readSchema.get("properties");
+
+            assertTrue("read has id", props.has("id"));
+            assertTrue("read has version", props.has("version"));
+            assertTrue("read has name", props.has("name"));
+            assertTrue("read has region", props.has("region"));
+            assertTrue("read has closedDates", props.has("closedDates"));
+            assertTrue("read has deleted", props.has("deleted"));
+
+            // ID is readOnly in read schema
+            assertTrue(props.get("id").has("readOnly"));
+
+            // region is $ref
+            assertEquals("#/components/schemas/RegionBO",
+                    props.get("region").get("$ref").asText());
+
+            // closedDates is array of $ref
+            assertEquals("array", 
props.get("closedDates").get("type").asText());
+            assertEquals("#/components/schemas/ExchangeClosedDateBO",
+                    
props.get("closedDates").get("items").get("$ref").asText());
+        }
+    }
+
+    @Test
+    public void testStockExchange_writeSchemaExcludesServerManaged() throws 
IOException {
+        try (InputStream is = 
getClass().getResourceAsStream("/StockExchangeBO.hbm.xml")) {
+            HbmXmlIntrospector introspector = new HbmXmlIntrospector();
+            EntitySchemaModel model = introspector.introspect(is, 
"StockExchangeBO.hbm.xml");
+
+            JsonNode writeSchema = 
JpaSchemaGenerator.generateWriteSchema(model);
+            JsonNode props = writeSchema.get("properties");
+
+            assertFalse("write excludes generated ID", props.has("id"));
+            assertFalse("write excludes version", props.has("version"));
+            assertTrue("write includes name", props.has("name"));
+            assertTrue("write includes region", props.has("region"));
+            assertTrue("write includes closedDates", props.has("closedDates"));
+
+            // Write schema $refs use "Write" suffix
+            assertEquals("#/components/schemas/RegionBOWrite",
+                    props.get("region").get("$ref").asText());
+        }
+    }
+
+    // ═══════════════════════════════════════════════════════════════════════
+    // Batch generator — end-to-end test using test resources directory
+    // ═══════════════════════════════════════════════════════════════════════
+
+    @Test
+    public void testBatchGenerator_producesValidOpenApiJson() throws 
IOException {
+        // Find the test resources directory (contains CompanyBO, 
DepartmentBO, StockExchangeBO)
+        URL resourceUrl = getClass().getResource("/StockExchangeBO.hbm.xml");
+        assertNotNull("test resource must be on classpath", resourceUrl);
+        File resourceDir = new File(resourceUrl.getFile()).getParentFile();
+
+        File outputFile = File.createTempFile("openapi-schemas-", ".json");
+        outputFile.deleteOnExit();
+
+        // Run the batch generator
+        HbmBatchSchemaGenerator.main(new String[]{
+                resourceDir.getAbsolutePath(),
+                outputFile.getAbsolutePath()
+        });
+
+        // Verify the output is valid JSON
+        assertTrue("output file should exist", outputFile.exists());
+        assertTrue("output file should not be empty", outputFile.length() > 0);
+
+        JsonNode root = mapper.readTree(outputFile);
+
+        // OpenAPI structure
+        assertEquals("3.0.1", root.get("openapi").asText());
+        assertNotNull(root.get("info"));
+        assertNotNull(root.get("components"));
+        assertNotNull(root.get("components").get("schemas"));
+
+        JsonNode schemas = root.get("components").get("schemas");
+
+        // Should have read + write schemas for each entity
+        // At minimum: CompanyBO, DepartmentBO, StockExchangeBO (3 entities × 
2 = 6 schemas)
+        assertTrue("should have at least 6 schemas, got " + schemas.size(),
+                schemas.size() >= 6);
+
+        // Verify specific entities exist
+        assertTrue("CompanyBO read schema", schemas.has("CompanyBO"));
+        assertTrue("CompanyBO write schema", schemas.has("CompanyBOWrite"));
+        assertTrue("StockExchangeBO read schema", 
schemas.has("StockExchangeBO"));
+        assertTrue("StockExchangeBO write schema", 
schemas.has("StockExchangeBOWrite"));
+        assertTrue("DepartmentBO read schema", schemas.has("DepartmentBO"));
+        assertTrue("DepartmentBO write schema", 
schemas.has("DepartmentBOWrite"));
+
+        // Spot-check StockExchangeBO schema content
+        JsonNode exchangeRead = schemas.get("StockExchangeBO");
+        assertEquals("object", exchangeRead.get("type").asText());
+        assertTrue(exchangeRead.get("properties").has("name"));
+        assertTrue(exchangeRead.get("properties").has("region"));
+        assertTrue(exchangeRead.get("properties").has("closedDates"));
+
+        // Write schema should not have id/version
+        JsonNode exchangeWrite = schemas.get("StockExchangeBOWrite");
+        assertFalse(exchangeWrite.get("properties").has("id"));
+        assertFalse(exchangeWrite.get("properties").has("version"));
+    }
+
+    @Test
+    public void testBatchGenerator_fieldCount() throws IOException {
+        try (InputStream is = 
getClass().getResourceAsStream("/StockExchangeBO.hbm.xml")) {
+            HbmXmlIntrospector introspector = new HbmXmlIntrospector();
+            EntitySchemaModel model = introspector.introspect(is, 
"StockExchangeBO.hbm.xml");
+
+            // 11 properties + 1 id + 1 version + 1 many-to-one + 1 set = 15 
fields
+            assertEquals("exact field count", 15, model.getFields().size());
+
+            // Count relationships
+            long relCount = model.getFields().values().stream()
+                    .filter(f -> f.getRefEntityName() != null).count();
+            assertEquals("2 relationships (region + closedDates)", 2, 
relCount);
+        }
+    }
+}
diff --git a/modules/jpa-schema/src/test/resources/StockExchangeBO.hbm.xml 
b/modules/jpa-schema/src/test/resources/StockExchangeBO.hbm.xml
new file mode 100644
index 0000000000..2ae97d4434
--- /dev/null
+++ b/modules/jpa-schema/src/test/resources/StockExchangeBO.hbm.xml
@@ -0,0 +1,60 @@
+<?xml version="1.0"?>
+<!DOCTYPE hibernate-mapping PUBLIC
+       "-//Hibernate/Hibernate Mapping DTD//EN"
+       "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"; >
+
+<!--
+  Sanitized HBM mapping for batch generator testing.
+  Models a stock exchange with trading hours, location, and relationships.
+  Exercises: id+generator, version, not-null properties, nullable properties,
+  many-to-one, set collection, nested column element with default.
+-->
+<hibernate-mapping package="org.example.bo">
+       <class name="StockExchangeBO" table="STOCK_EXCHANGE" 
dynamic-update="false"
+               dynamic-insert="true" select-before-update="false" 
optimistic-lock="version"
+               lazy="true" batch-size="300">
+
+               <id name="id" type="java.lang.Long" column="exchangeID">
+                       <generator class="native"/>
+               </id>
+               <version name="version" type="java.lang.Long" access="field"
+                       column="OBJ_VERSION" />
+
+               <property name="name" column="name" type="java.lang.String"
+                       unique="true" not-null="true" />
+               <property name="micCode" column="micCode"
+                       type="java.lang.String" not-null="false" />
+               <property name="operatingMicCode" column="operatingMicCode"
+                       type="java.lang.String" not-null="false" />
+               <property name="country" column="country"
+                       type="java.lang.String" not-null="false" />
+               <property name="isoCountryCode" column="isoCountryCode"
+                       type="java.lang.String" not-null="false" />
+               <property name="city" column="city"
+                       type="java.lang.String" not-null="false" />
+               <property name="website" column="website"
+                       type="java.lang.String" not-null="false" />
+               <property name="openTimeLocal" column="openTimeLocal"
+                       type="java.lang.String" not-null="false" />
+               <property name="closeTimeLocal" column="closeTimeLocal"
+                       type="java.lang.String" not-null="false" />
+               <property name="timeZoneName" column="timeZoneName"
+                       type="java.lang.String" not-null="false" />
+
+               <many-to-one name="region"
+                       class="org.example.bo.RegionBO"
+                       column="regionID" cascade="save-update" />
+
+               <set name="closedDates" table="ExchangeClosed" inverse="true" 
lazy="true">
+                       <key>
+                               <column name="exchangeID" not-null="true" />
+                       </key>
+                       <one-to-many 
class="org.example.bo.ExchangeClosedDateBO" />
+               </set>
+
+               <property name="deleted" type="java.lang.Boolean">
+                       <column name="deleted" not-null="true" default="0" />
+               </property>
+
+       </class>
+</hibernate-mapping>

Reply via email to