|
| 1 | +/* |
| 2 | + * Licensed to the Apache Software Foundation (ASF) under one |
| 3 | + * or more contributor license agreements. See the NOTICE file |
| 4 | + * distributed with this work for additional information |
| 5 | + * regarding copyright ownership. The ASF licenses this file |
| 6 | + * to you under the Apache License, Version 2.0 (the |
| 7 | + * "License"); you may not use this file except in compliance |
| 8 | + * with the License. You may obtain a copy of the License at |
| 9 | + * |
| 10 | + * http://www.apache.org/licenses/LICENSE-2.0 |
| 11 | + * |
| 12 | + * Unless required by applicable law or agreed to in writing, |
| 13 | + * software distributed under the License is distributed on an |
| 14 | + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| 15 | + * KIND, either express or implied. See the License for the |
| 16 | + * specific language governing permissions and limitations |
| 17 | + * under the License. |
| 18 | + */ |
| 19 | + |
| 20 | +package org.apache.axis2.jpa.schema; |
| 21 | + |
| 22 | +import com.fasterxml.jackson.databind.JsonNode; |
| 23 | +import com.fasterxml.jackson.databind.ObjectMapper; |
| 24 | + |
| 25 | +import org.junit.Test; |
| 26 | + |
| 27 | +import java.io.File; |
| 28 | +import java.io.IOException; |
| 29 | +import java.io.InputStream; |
| 30 | +import java.net.URL; |
| 31 | +import java.nio.file.Files; |
| 32 | + |
| 33 | +import static org.junit.Assert.*; |
| 34 | + |
| 35 | +/** |
| 36 | + * Tests for {@link HbmBatchSchemaGenerator} — the CLI batch tool that |
| 37 | + * converts a directory of HBM XML files into an OpenAPI 3.0 schemas JSON. |
| 38 | + * |
| 39 | + * <p>Uses test HBM files on the classpath: CompanyBO.hbm.xml, |
| 40 | + * DepartmentBO.hbm.xml, StockExchangeBO.hbm.xml. |
| 41 | + */ |
| 42 | +public class HbmBatchSchemaGeneratorTest { |
| 43 | + |
| 44 | + private static final ObjectMapper mapper = new ObjectMapper(); |
| 45 | + |
| 46 | + // ═══════════════════════════════════════════════════════════════════════ |
| 47 | + // StockExchangeBO introspection — validates a production-style HBM |
| 48 | + // mapping with id+generator, version, not-null, many-to-one, set, |
| 49 | + // nested column element |
| 50 | + // ═══════════════════════════════════════════════════════════════════════ |
| 51 | + |
| 52 | + @Test |
| 53 | + public void testStockExchange_parsesEntity() throws IOException { |
| 54 | + try (InputStream is = getClass().getResourceAsStream("/StockExchangeBO.hbm.xml")) { |
| 55 | + assertNotNull("StockExchangeBO.hbm.xml must be on classpath", is); |
| 56 | + |
| 57 | + HbmXmlIntrospector introspector = new HbmXmlIntrospector(); |
| 58 | + EntitySchemaModel model = introspector.introspect(is, "StockExchangeBO.hbm.xml"); |
| 59 | + |
| 60 | + assertNotNull(model); |
| 61 | + assertEquals("StockExchangeBO", model.getEntityName()); |
| 62 | + assertEquals("STOCK_EXCHANGE", model.getTableName()); |
| 63 | + } |
| 64 | + } |
| 65 | + |
| 66 | + @Test |
| 67 | + public void testStockExchange_idAndVersion() throws IOException { |
| 68 | + try (InputStream is = getClass().getResourceAsStream("/StockExchangeBO.hbm.xml")) { |
| 69 | + HbmXmlIntrospector introspector = new HbmXmlIntrospector(); |
| 70 | + EntitySchemaModel model = introspector.introspect(is, "StockExchangeBO.hbm.xml"); |
| 71 | + |
| 72 | + assertEquals(1, model.getIdFieldNames().size()); |
| 73 | + assertEquals("id", model.getIdFieldNames().get(0)); |
| 74 | + |
| 75 | + EntitySchemaModel.FieldModel idField = model.getFields().get("id"); |
| 76 | + assertTrue("ID should be generated", idField.isGeneratedValue()); |
| 77 | + assertFalse("ID should not be nullable", idField.isNullable()); |
| 78 | + |
| 79 | + assertEquals("version", model.getVersionFieldName()); |
| 80 | + assertTrue(model.getFields().get("version").isVersionField()); |
| 81 | + assertTrue(model.getFields().get("version").isExcludedFromWrite()); |
| 82 | + } |
| 83 | + } |
| 84 | + |
| 85 | + @Test |
| 86 | + public void testStockExchange_requiredProperty() throws IOException { |
| 87 | + try (InputStream is = getClass().getResourceAsStream("/StockExchangeBO.hbm.xml")) { |
| 88 | + HbmXmlIntrospector introspector = new HbmXmlIntrospector(); |
| 89 | + EntitySchemaModel model = introspector.introspect(is, "StockExchangeBO.hbm.xml"); |
| 90 | + |
| 91 | + EntitySchemaModel.FieldModel name = model.getFields().get("name"); |
| 92 | + assertNotNull(name); |
| 93 | + assertEquals("string", name.getJsonSchemaType()); |
| 94 | + assertFalse("name is not-null=true", name.isNullable()); |
| 95 | + } |
| 96 | + } |
| 97 | + |
| 98 | + @Test |
| 99 | + public void testStockExchange_nullableProperties() throws IOException { |
| 100 | + try (InputStream is = getClass().getResourceAsStream("/StockExchangeBO.hbm.xml")) { |
| 101 | + HbmXmlIntrospector introspector = new HbmXmlIntrospector(); |
| 102 | + EntitySchemaModel model = introspector.introspect(is, "StockExchangeBO.hbm.xml"); |
| 103 | + |
| 104 | + for (String field : new String[]{"micCode", "country", "city", "website", "timeZoneName"}) { |
| 105 | + EntitySchemaModel.FieldModel f = model.getFields().get(field); |
| 106 | + assertNotNull(field + " should exist", f); |
| 107 | + assertTrue(field + " should be nullable", f.isNullable()); |
| 108 | + } |
| 109 | + } |
| 110 | + } |
| 111 | + |
| 112 | + @Test |
| 113 | + public void testStockExchange_manyToOneRelationship() throws IOException { |
| 114 | + try (InputStream is = getClass().getResourceAsStream("/StockExchangeBO.hbm.xml")) { |
| 115 | + HbmXmlIntrospector introspector = new HbmXmlIntrospector(); |
| 116 | + EntitySchemaModel model = introspector.introspect(is, "StockExchangeBO.hbm.xml"); |
| 117 | + |
| 118 | + EntitySchemaModel.FieldModel region = model.getFields().get("region"); |
| 119 | + assertNotNull("region relationship should exist", region); |
| 120 | + assertEquals("object", region.getJsonSchemaType()); |
| 121 | + assertEquals("RegionBO", region.getRefEntityName()); |
| 122 | + assertFalse(region.isCollection()); |
| 123 | + } |
| 124 | + } |
| 125 | + |
| 126 | + @Test |
| 127 | + public void testStockExchange_setCollection() throws IOException { |
| 128 | + try (InputStream is = getClass().getResourceAsStream("/StockExchangeBO.hbm.xml")) { |
| 129 | + HbmXmlIntrospector introspector = new HbmXmlIntrospector(); |
| 130 | + EntitySchemaModel model = introspector.introspect(is, "StockExchangeBO.hbm.xml"); |
| 131 | + |
| 132 | + EntitySchemaModel.FieldModel closedDates = model.getFields().get("closedDates"); |
| 133 | + assertNotNull("closedDates set should exist", closedDates); |
| 134 | + assertEquals("array", closedDates.getJsonSchemaType()); |
| 135 | + assertTrue(closedDates.isCollection()); |
| 136 | + assertEquals("ExchangeClosedDateBO", closedDates.getRefEntityName()); |
| 137 | + } |
| 138 | + } |
| 139 | + |
| 140 | + @Test |
| 141 | + public void testStockExchange_nestedColumnNotNull() throws IOException { |
| 142 | + try (InputStream is = getClass().getResourceAsStream("/StockExchangeBO.hbm.xml")) { |
| 143 | + HbmXmlIntrospector introspector = new HbmXmlIntrospector(); |
| 144 | + EntitySchemaModel model = introspector.introspect(is, "StockExchangeBO.hbm.xml"); |
| 145 | + |
| 146 | + EntitySchemaModel.FieldModel deleted = model.getFields().get("deleted"); |
| 147 | + assertNotNull(deleted); |
| 148 | + assertEquals("boolean", deleted.getJsonSchemaType()); |
| 149 | + assertFalse("deleted with nested column not-null=true should be required", |
| 150 | + deleted.isNullable()); |
| 151 | + } |
| 152 | + } |
| 153 | + |
| 154 | + @Test |
| 155 | + public void testStockExchange_readSchemaIncludesAll() throws IOException { |
| 156 | + try (InputStream is = getClass().getResourceAsStream("/StockExchangeBO.hbm.xml")) { |
| 157 | + HbmXmlIntrospector introspector = new HbmXmlIntrospector(); |
| 158 | + EntitySchemaModel model = introspector.introspect(is, "StockExchangeBO.hbm.xml"); |
| 159 | + |
| 160 | + JsonNode readSchema = JpaSchemaGenerator.generateReadSchema(model); |
| 161 | + JsonNode props = readSchema.get("properties"); |
| 162 | + |
| 163 | + assertTrue("read has id", props.has("id")); |
| 164 | + assertTrue("read has version", props.has("version")); |
| 165 | + assertTrue("read has name", props.has("name")); |
| 166 | + assertTrue("read has region", props.has("region")); |
| 167 | + assertTrue("read has closedDates", props.has("closedDates")); |
| 168 | + assertTrue("read has deleted", props.has("deleted")); |
| 169 | + |
| 170 | + // ID is readOnly in read schema |
| 171 | + assertTrue(props.get("id").has("readOnly")); |
| 172 | + |
| 173 | + // region is $ref |
| 174 | + assertEquals("#/components/schemas/RegionBO", |
| 175 | + props.get("region").get("$ref").asText()); |
| 176 | + |
| 177 | + // closedDates is array of $ref |
| 178 | + assertEquals("array", props.get("closedDates").get("type").asText()); |
| 179 | + assertEquals("#/components/schemas/ExchangeClosedDateBO", |
| 180 | + props.get("closedDates").get("items").get("$ref").asText()); |
| 181 | + } |
| 182 | + } |
| 183 | + |
| 184 | + @Test |
| 185 | + public void testStockExchange_writeSchemaExcludesServerManaged() throws IOException { |
| 186 | + try (InputStream is = getClass().getResourceAsStream("/StockExchangeBO.hbm.xml")) { |
| 187 | + HbmXmlIntrospector introspector = new HbmXmlIntrospector(); |
| 188 | + EntitySchemaModel model = introspector.introspect(is, "StockExchangeBO.hbm.xml"); |
| 189 | + |
| 190 | + JsonNode writeSchema = JpaSchemaGenerator.generateWriteSchema(model); |
| 191 | + JsonNode props = writeSchema.get("properties"); |
| 192 | + |
| 193 | + assertFalse("write excludes generated ID", props.has("id")); |
| 194 | + assertFalse("write excludes version", props.has("version")); |
| 195 | + assertTrue("write includes name", props.has("name")); |
| 196 | + assertTrue("write includes region", props.has("region")); |
| 197 | + assertTrue("write includes closedDates", props.has("closedDates")); |
| 198 | + |
| 199 | + // Write schema $refs use "Write" suffix |
| 200 | + assertEquals("#/components/schemas/RegionBOWrite", |
| 201 | + props.get("region").get("$ref").asText()); |
| 202 | + } |
| 203 | + } |
| 204 | + |
| 205 | + // ═══════════════════════════════════════════════════════════════════════ |
| 206 | + // Batch generator — end-to-end test using test resources directory |
| 207 | + // ═══════════════════════════════════════════════════════════════════════ |
| 208 | + |
| 209 | + @Test |
| 210 | + public void testBatchGenerator_producesValidOpenApiJson() throws IOException { |
| 211 | + // Find the test resources directory (contains CompanyBO, DepartmentBO, StockExchangeBO) |
| 212 | + URL resourceUrl = getClass().getResource("/StockExchangeBO.hbm.xml"); |
| 213 | + assertNotNull("test resource must be on classpath", resourceUrl); |
| 214 | + File resourceDir = new File(resourceUrl.getFile()).getParentFile(); |
| 215 | + |
| 216 | + File outputFile = File.createTempFile("openapi-schemas-", ".json"); |
| 217 | + outputFile.deleteOnExit(); |
| 218 | + |
| 219 | + // Run the batch generator |
| 220 | + HbmBatchSchemaGenerator.main(new String[]{ |
| 221 | + resourceDir.getAbsolutePath(), |
| 222 | + outputFile.getAbsolutePath() |
| 223 | + }); |
| 224 | + |
| 225 | + // Verify the output is valid JSON |
| 226 | + assertTrue("output file should exist", outputFile.exists()); |
| 227 | + assertTrue("output file should not be empty", outputFile.length() > 0); |
| 228 | + |
| 229 | + JsonNode root = mapper.readTree(outputFile); |
| 230 | + |
| 231 | + // OpenAPI structure |
| 232 | + assertEquals("3.0.1", root.get("openapi").asText()); |
| 233 | + assertNotNull(root.get("info")); |
| 234 | + assertNotNull(root.get("components")); |
| 235 | + assertNotNull(root.get("components").get("schemas")); |
| 236 | + |
| 237 | + JsonNode schemas = root.get("components").get("schemas"); |
| 238 | + |
| 239 | + // Should have read + write schemas for each entity |
| 240 | + // At minimum: CompanyBO, DepartmentBO, StockExchangeBO (3 entities × 2 = 6 schemas) |
| 241 | + assertTrue("should have at least 6 schemas, got " + schemas.size(), |
| 242 | + schemas.size() >= 6); |
| 243 | + |
| 244 | + // Verify specific entities exist |
| 245 | + assertTrue("CompanyBO read schema", schemas.has("CompanyBO")); |
| 246 | + assertTrue("CompanyBO write schema", schemas.has("CompanyBOWrite")); |
| 247 | + assertTrue("StockExchangeBO read schema", schemas.has("StockExchangeBO")); |
| 248 | + assertTrue("StockExchangeBO write schema", schemas.has("StockExchangeBOWrite")); |
| 249 | + assertTrue("DepartmentBO read schema", schemas.has("DepartmentBO")); |
| 250 | + assertTrue("DepartmentBO write schema", schemas.has("DepartmentBOWrite")); |
| 251 | + |
| 252 | + // Spot-check StockExchangeBO schema content |
| 253 | + JsonNode exchangeRead = schemas.get("StockExchangeBO"); |
| 254 | + assertEquals("object", exchangeRead.get("type").asText()); |
| 255 | + assertTrue(exchangeRead.get("properties").has("name")); |
| 256 | + assertTrue(exchangeRead.get("properties").has("region")); |
| 257 | + assertTrue(exchangeRead.get("properties").has("closedDates")); |
| 258 | + |
| 259 | + // Write schema should not have id/version |
| 260 | + JsonNode exchangeWrite = schemas.get("StockExchangeBOWrite"); |
| 261 | + assertFalse(exchangeWrite.get("properties").has("id")); |
| 262 | + assertFalse(exchangeWrite.get("properties").has("version")); |
| 263 | + } |
| 264 | + |
| 265 | + @Test |
| 266 | + public void testBatchGenerator_fieldCount() throws IOException { |
| 267 | + try (InputStream is = getClass().getResourceAsStream("/StockExchangeBO.hbm.xml")) { |
| 268 | + HbmXmlIntrospector introspector = new HbmXmlIntrospector(); |
| 269 | + EntitySchemaModel model = introspector.introspect(is, "StockExchangeBO.hbm.xml"); |
| 270 | + |
| 271 | + // 11 properties + 1 id + 1 version + 1 many-to-one + 1 set = 15 fields |
| 272 | + assertEquals("exact field count", 15, model.getFields().size()); |
| 273 | + |
| 274 | + // Count relationships |
| 275 | + long relCount = model.getFields().values().stream() |
| 276 | + .filter(f -> f.getRefEntityName() != null).count(); |
| 277 | + assertEquals("2 relationships (region + closedDates)", 2, relCount); |
| 278 | + } |
| 279 | + } |
| 280 | +} |
0 commit comments