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>
