feat: upgrade to QQQ 0.35.0 with Java 21 and add unit tests #1

Merged
KofTwentyTwo merged 1 commits from feature/java-21-upgrade into main 2025-12-28 22:37:10 +00:00
12 changed files with 914 additions and 26 deletions

No files matched your search

+57
View File
@@ -0,0 +1,57 @@
version: 2.1
orbs:
qqq-orb: kingsrook/qqq-orb@0.5.0
workflows:
test_only:
jobs:
- qqq-orb/mvn_test_only:
context: [ qqq-maven-registry-credentials ]
filters:
branches:
ignore: /(develop|main|release/.*|hotfix/.*|integration.*)/
tags:
only: []
publish_snapshot:
jobs:
- qqq-orb/mvn_publish:
context: [ qqq-maven-registry-credentials ]
branch_type: snapshot
filters:
branches:
only:
- develop
publish_release_candidate:
jobs:
- qqq-orb/mvn_publish:
context: [ qqq-maven-registry-credentials ]
branch_type: release_candidate
filters:
branches:
only:
- /release\/.*/
publish_release:
jobs:
- qqq-orb/mvn_publish:
context: [ qqq-maven-registry-credentials ]
branch_type: release
filters:
branches:
only: [ main ]
tags:
only: [ /v.*/ ]
publish_hotfix_release:
jobs:
- qqq-orb/mvn_publish:
context: [ qqq-maven-registry-credentials ]
branch_type: hotfix
filters:
branches:
only:
- /hotfix\/.*/
+6
View File
@@ -13,6 +13,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Data acquisition tools for dr5hn repository
- Gzip decompression for cities data
- Population and country filtering options
- Liquibase changelog generator with prefix substitution
- Unit tests for config, producer, generator, and sync components
- CircleCI CI/CD pipeline configuration
### Changed
- Upgraded to QQQ 0.35.0 with Java 21 support
## [0.1.0-alpha] - 2024-12-28
+19
View File
@@ -1,6 +1,7 @@
# QBit: Geo Data
[![Alpha](https://img.shields.io/badge/status-alpha-orange)]()
[![CircleCI](https://circleci.com/gh/QRun-IO/qbit-geo-data.svg?style=shield)](https://circleci.com/gh/QRun-IO/qbit-geo-data)
Geographic reference data QBit providing countries, states/provinces, and cities for QQQ applications.
@@ -141,6 +142,24 @@ This creates two independent sets of tables:
Query each using its prefixed table name, same entity classes.
## Database Schema
Generate a Liquibase changelog for your database:
```java
import com.kingsrook.qbits.geodata.liquibase.GeoDataLiquibaseGenerator;
GeoDataQBitConfig config = new GeoDataQBitConfig()
.withTableNamePrefix("shipping")
.withEnableCountries(true)
.withEnableStateProvinces(true)
.withEnableCities(true);
GeoDataLiquibaseGenerator.generate(config, Path.of("db/changelog-geo-data.xml"));
```
The generator creates a changelog with your prefix substituted and only includes tables you've enabled.
## License
AGPL-3.0 - See [LICENSE](LICENSE)
+3 -3
View File
@@ -18,10 +18,10 @@
</modules>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<qqq.version>0.23.0</qqq.version>
<qqq.version>0.35.0</qqq.version>
</properties>
<dependencyManagement>
@@ -0,0 +1,154 @@
/*******************************************************************************
** Liquibase changelog generator for the Geographic Data QBit.
**
** Reads the bundled changelog template and generates a final changelog with:
** - Prefix substitution for table/constraint/index names
** - Section removal for disabled entities
*******************************************************************************/
package com.kingsrook.qbits.geodata.liquibase;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import com.kingsrook.qbits.geodata.GeoDataQBitConfig;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
public class GeoDataLiquibaseGenerator
{
private static final QLogger LOG = QLogger.getLogger(GeoDataLiquibaseGenerator.class);
private static final String TEMPLATE_RESOURCE = "/db/changelog-template.xml";
private static final Pattern SECTION_PATTERN = Pattern.compile(
"\\s*<!-- SECTION: (\\w+) -->.*?<!-- END SECTION: \\1 -->\\s*",
Pattern.DOTALL
);
/*******************************************************************************
** Generate a Liquibase changelog from the template using the provided config.
**
** @param config the QBit configuration with prefix and enabled tables
** @param outputPath the path to write the generated changelog
** @throws IOException if template cannot be read or output cannot be written
*******************************************************************************/
public static void generate(GeoDataQBitConfig config, Path outputPath) throws IOException
{
String template = loadTemplate();
String result = processTemplate(template, config);
Files.createDirectories(outputPath.getParent());
Files.writeString(outputPath, result, StandardCharsets.UTF_8);
LOG.info("Generated Liquibase changelog", logPair("outputPath", outputPath));
}
/*******************************************************************************
** Generate changelog content as a string without writing to a file.
**
** @param config the QBit configuration with prefix and enabled tables
** @return the generated changelog XML content
** @throws IOException if template cannot be read
*******************************************************************************/
public static String generate(GeoDataQBitConfig config) throws IOException
{
String template = loadTemplate();
return processTemplate(template, config);
}
/*******************************************************************************
** Load the changelog template from classpath resources.
*******************************************************************************/
private static String loadTemplate() throws IOException
{
try(InputStream is = GeoDataLiquibaseGenerator.class.getResourceAsStream(TEMPLATE_RESOURCE))
{
if(is == null)
{
throw new IOException("Changelog template not found: " + TEMPLATE_RESOURCE);
}
return new String(is.readAllBytes(), StandardCharsets.UTF_8);
}
}
/*******************************************************************************
** Process the template by removing disabled sections and substituting prefix.
*******************************************************************************/
private static String processTemplate(String template, GeoDataQBitConfig config)
{
String result = template;
/////////////////////////////////////////////////////////////////////////
// Build set of sections to remove based on disabled entities //
/////////////////////////////////////////////////////////////////////////
Set<String> sectionsToRemove = new HashSet<>();
if(!Boolean.TRUE.equals(config.getEnableCountries()))
{
sectionsToRemove.add("country");
}
if(!Boolean.TRUE.equals(config.getEnableStateProvinces()))
{
sectionsToRemove.add("stateProvince");
}
if(!Boolean.TRUE.equals(config.getEnableCities()))
{
sectionsToRemove.add("city");
}
/////////////////////////////////////////////////////////////////////////
// Remove disabled sections //
/////////////////////////////////////////////////////////////////////////
if(!sectionsToRemove.isEmpty())
{
Matcher matcher = SECTION_PATTERN.matcher(result);
StringBuffer sb = new StringBuffer();
while(matcher.find())
{
String sectionName = matcher.group(1);
if(sectionsToRemove.contains(sectionName))
{
matcher.appendReplacement(sb, "\n");
}
}
matcher.appendTail(sb);
result = sb.toString();
}
/////////////////////////////////////////////////////////////////////////
// Substitute prefix placeholder //
/////////////////////////////////////////////////////////////////////////
String prefix = config.getTableNamePrefix();
if(StringUtils.hasContent(prefix))
{
result = result.replace("${prefix}", prefix);
}
else
{
//////////////////////////////////////////////////////////////////////////
// No prefix configured - remove the prefix and underscore //
//////////////////////////////////////////////////////////////////////////
result = result.replace("${prefix}_", "");
result = result.replace("${prefix}-", "");
result = result.replace("${prefix}", "");
}
return result;
}
}
@@ -18,8 +18,8 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
import com.kingsrook.qqq.backend.core.actions.processes.RunBackendStepInput;
import com.kingsrook.qqq.backend.core.actions.processes.RunBackendStepOutput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
@@ -5,58 +5,114 @@
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.20.xsd">
<!-- SECTION: exampleEntity -->
<changeSet id="${prefix}-create-example-entity-v1" author="example-data-qbit">
<createTable tableName="${prefix}_example_entity">
<!-- SECTION: country -->
<changeSet id="${prefix}-create-country-v1" author="geo-data-qbit">
<createTable tableName="${prefix}_country">
<column name="id" type="INT" autoIncrement="true">
<constraints primaryKey="true"/>
</column>
<column name="code" type="VARCHAR(50)">
<constraints nullable="false" unique="true"/>
<column name="alpha2_code" type="VARCHAR(2)">
<constraints nullable="false"/>
</column>
<column name="alpha3_code" type="VARCHAR(3)"/>
<column name="numeric_code" type="INT"/>
<column name="name" type="VARCHAR(100)">
<constraints nullable="false"/>
</column>
<column name="description" type="VARCHAR(500)"/>
<column name="official_name" type="VARCHAR(100)"/>
<column name="is_active" type="BOOLEAN" defaultValueBoolean="true"/>
<column name="create_date" type="TIMESTAMP"/>
<column name="modify_date" type="TIMESTAMP"/>
</createTable>
<createIndex tableName="${prefix}_example_entity" indexName="${prefix}_example_entity_code_idx">
<column name="code"/>
<addUniqueConstraint
tableName="${prefix}_country"
columnNames="alpha2_code"
constraintName="${prefix}_country_alpha2_uk"/>
<createIndex tableName="${prefix}_country" indexName="${prefix}_country_alpha2_idx">
<column name="alpha2_code"/>
</createIndex>
<createIndex tableName="${prefix}_country" indexName="${prefix}_country_alpha3_idx">
<column name="alpha3_code"/>
</createIndex>
</changeSet>
<!-- END SECTION: exampleEntity -->
<!-- END SECTION: country -->
<!-- SECTION: exampleChildEntity -->
<changeSet id="${prefix}-create-example-child-entity-v1" author="example-data-qbit">
<createTable tableName="${prefix}_example_child_entity">
<!-- SECTION: stateProvince -->
<changeSet id="${prefix}-create-state-province-v1" author="geo-data-qbit">
<createTable tableName="${prefix}_state_province">
<column name="id" type="INT" autoIncrement="true">
<constraints primaryKey="true"/>
</column>
<column name="example_entity_id" type="INT">
<column name="country_id" type="INT">
<constraints nullable="false"/>
</column>
<column name="code" type="VARCHAR(50)">
<column name="code" type="VARCHAR(10)">
<constraints nullable="false"/>
</column>
<column name="name" type="VARCHAR(100)">
<constraints nullable="false"/>
</column>
<column name="subdivision_type" type="VARCHAR(50)"/>
<column name="is_active" type="BOOLEAN" defaultValueBoolean="true"/>
<column name="create_date" type="TIMESTAMP"/>
<column name="modify_date" type="TIMESTAMP"/>
</createTable>
<addForeignKeyConstraint
baseTableName="${prefix}_example_child_entity"
baseColumnNames="example_entity_id"
referencedTableName="${prefix}_example_entity"
baseTableName="${prefix}_state_province"
baseColumnNames="country_id"
referencedTableName="${prefix}_country"
referencedColumnNames="id"
constraintName="${prefix}_fk_child_to_parent"/>
<createIndex tableName="${prefix}_example_child_entity" indexName="${prefix}_example_child_entity_parent_idx">
<column name="example_entity_id"/>
constraintName="${prefix}_fk_state_province_country"/>
<addUniqueConstraint
tableName="${prefix}_state_province"
columnNames="country_id,code"
constraintName="${prefix}_state_province_country_code_uk"/>
<createIndex tableName="${prefix}_state_province" indexName="${prefix}_state_province_country_idx">
<column name="country_id"/>
</createIndex>
<createIndex tableName="${prefix}_state_province" indexName="${prefix}_state_province_code_idx">
<column name="code"/>
</createIndex>
</changeSet>
<!-- END SECTION: exampleChildEntity -->
<!-- END SECTION: stateProvince -->
<!-- SECTION: city -->
<changeSet id="${prefix}-create-city-v1" author="geo-data-qbit">
<createTable tableName="${prefix}_city">
<column name="id" type="INT" autoIncrement="true">
<constraints primaryKey="true"/>
</column>
<column name="state_province_id" type="INT">
<constraints nullable="false"/>
</column>
<column name="name" type="VARCHAR(200)">
<constraints nullable="false"/>
</column>
<column name="ascii_name" type="VARCHAR(200)"/>
<column name="population" type="INT"/>
<column name="latitude" type="DECIMAL(10,7)"/>
<column name="longitude" type="DECIMAL(10,7)"/>
<column name="timezone" type="VARCHAR(50)"/>
<column name="is_active" type="BOOLEAN" defaultValueBoolean="true"/>
<column name="create_date" type="TIMESTAMP"/>
<column name="modify_date" type="TIMESTAMP"/>
</createTable>
<addForeignKeyConstraint
baseTableName="${prefix}_city"
baseColumnNames="state_province_id"
referencedTableName="${prefix}_state_province"
referencedColumnNames="id"
constraintName="${prefix}_fk_city_state_province"/>
<createIndex tableName="${prefix}_city" indexName="${prefix}_city_state_province_idx">
<column name="state_province_id"/>
</createIndex>
<createIndex tableName="${prefix}_city" indexName="${prefix}_city_name_idx">
<column name="name"/>
</createIndex>
<createIndex tableName="${prefix}_city" indexName="${prefix}_city_population_idx">
<column name="population"/>
</createIndex>
</changeSet>
<!-- END SECTION: city -->
</databaseChangeLog>
@@ -0,0 +1,182 @@
/*******************************************************************************
** Unit tests for GeoDataQBitConfig.
*******************************************************************************/
package com.kingsrook.qbits.geodata;
import java.util.ArrayList;
import java.util.List;
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class GeoDataQBitConfigTest
{
/*******************************************************************************
** Test that validation fails when backendName is missing.
*******************************************************************************/
@Test
void testValidate_missingBackendName_addsError()
{
GeoDataQBitConfig config = new GeoDataQBitConfig();
QInstance qInstance = new QInstance();
List<String> errors = new ArrayList<>();
config.validate(qInstance, errors);
assertThat(errors).contains("backendName is required for GeoDataQBit");
}
/*******************************************************************************
** Test that validation fails when backend is not found in QInstance.
*******************************************************************************/
@Test
void testValidate_backendNotFound_addsError()
{
GeoDataQBitConfig config = new GeoDataQBitConfig()
.withBackendName("nonexistent");
QInstance qInstance = new QInstance();
List<String> errors = new ArrayList<>();
config.validate(qInstance, errors);
assertThat(errors).contains("Backend not found: nonexistent");
}
/*******************************************************************************
** Test that validation fails when cities enabled without states.
*******************************************************************************/
@Test
void testValidate_citiesWithoutStates_addsError()
{
GeoDataQBitConfig config = new GeoDataQBitConfig()
.withBackendName("rdbms")
.withEnableCities(true)
.withEnableStateProvinces(false);
QInstance qInstance = new QInstance();
qInstance.addBackend(new QBackendMetaData().withName("rdbms"));
List<String> errors = new ArrayList<>();
config.validate(qInstance, errors);
assertThat(errors).contains("enableStateProvinces must be true when enableCities is true (cities reference states)");
}
/*******************************************************************************
** Test that validation fails when states enabled without countries.
*******************************************************************************/
@Test
void testValidate_statesWithoutCountries_addsError()
{
GeoDataQBitConfig config = new GeoDataQBitConfig()
.withBackendName("rdbms")
.withEnableStateProvinces(true)
.withEnableCountries(false);
QInstance qInstance = new QInstance();
qInstance.addBackend(new QBackendMetaData().withName("rdbms"));
List<String> errors = new ArrayList<>();
config.validate(qInstance, errors);
assertThat(errors).contains("enableCountries must be true when enableStateProvinces is true (states reference countries)");
}
/*******************************************************************************
** Test that validation passes with valid configuration.
*******************************************************************************/
@Test
void testValidate_validConfig_noErrors()
{
GeoDataQBitConfig config = new GeoDataQBitConfig()
.withBackendName("rdbms")
.withTableNamePrefix("shipping")
.withEnableCountries(true)
.withEnableStateProvinces(true)
.withEnableCities(true);
QInstance qInstance = new QInstance();
qInstance.addBackend(new QBackendMetaData().withName("rdbms"));
List<String> errors = new ArrayList<>();
config.validate(qInstance, errors);
assertThat(errors).isEmpty();
}
/*******************************************************************************
** Test getEnabledTableNames returns all tables when all enabled.
*******************************************************************************/
@Test
void testGetEnabledTableNames_allEnabled()
{
GeoDataQBitConfig config = new GeoDataQBitConfig()
.withEnableCountries(true)
.withEnableStateProvinces(true)
.withEnableCities(true);
List<String> tables = config.getEnabledTableNames();
assertThat(tables).containsExactly("country", "stateProvince", "city");
}
/*******************************************************************************
** Test getEnabledTableNames returns only countries when others disabled.
*******************************************************************************/
@Test
void testGetEnabledTableNames_onlyCountries()
{
GeoDataQBitConfig config = new GeoDataQBitConfig()
.withEnableCountries(true)
.withEnableStateProvinces(false)
.withEnableCities(false);
List<String> tables = config.getEnabledTableNames();
assertThat(tables).containsExactly("country");
}
/*******************************************************************************
** Test applyPrefix with prefix configured.
*******************************************************************************/
@Test
void testApplyPrefix_withPrefix()
{
GeoDataQBitConfig config = new GeoDataQBitConfig()
.withTableNamePrefix("shipping");
assertThat(config.applyPrefix("country")).isEqualTo("shipping_country");
assertThat(config.applyPrefix("stateProvince")).isEqualTo("shipping_stateProvince");
}
/*******************************************************************************
** Test applyPrefix without prefix returns original name.
*******************************************************************************/
@Test
void testApplyPrefix_withoutPrefix()
{
GeoDataQBitConfig config = new GeoDataQBitConfig();
assertThat(config.applyPrefix("country")).isEqualTo("country");
}
}
@@ -0,0 +1,138 @@
/*******************************************************************************
** Unit tests for GeoDataQBitProducer.
*******************************************************************************/
package com.kingsrook.qbits.geodata;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
class GeoDataQBitProducerTest
{
/*******************************************************************************
** Test that producer throws exception when config is invalid.
*******************************************************************************/
@Test
void testProduce_invalidConfig_throwsException()
{
GeoDataQBitProducer producer = new GeoDataQBitProducer()
.withConfig(new GeoDataQBitConfig()); // Missing backendName
QInstance qInstance = new QInstance();
assertThatThrownBy(() -> producer.produce(qInstance, "test"))
.isInstanceOf(QException.class)
.hasMessageContaining("backendName is required");
}
/*******************************************************************************
** Test that producer throws exception when backend not found.
*******************************************************************************/
@Test
void testProduce_backendNotFound_throwsException()
{
GeoDataQBitProducer producer = new GeoDataQBitProducer()
.withConfig(new GeoDataQBitConfig()
.withBackendName("nonexistent"));
QInstance qInstance = new QInstance();
assertThatThrownBy(() -> producer.produce(qInstance, "test"))
.isInstanceOf(QException.class)
.hasMessageContaining("Backend not found");
}
/*******************************************************************************
** Test that producer creates QBit metadata.
*******************************************************************************/
@Test
void testProduce_createsQBitMetaData() throws QException
{
QInstance qInstance = new QInstance();
qInstance.addBackend(new QBackendMetaData().withName("rdbms"));
GeoDataQBitProducer producer = new GeoDataQBitProducer()
.withConfig(new GeoDataQBitConfig()
.withBackendName("rdbms")
.withTableNamePrefix("geo")
.withEnableCountries(true)
.withEnableStateProvinces(false)
.withEnableCities(false));
producer.produce(qInstance, "my-geo-qbit");
assertThat(qInstance.getQBits()).isNotEmpty();
assertThat(qInstance.getQBits().values().stream()
.anyMatch(qb -> "my-geo-qbit".equals(qb.getNamespace()))).isTrue();
}
/*******************************************************************************
** Test that producer stores config in QBit metadata.
*******************************************************************************/
@Test
void testProduce_storesConfigInQBitMetaData() throws QException
{
QInstance qInstance = new QInstance();
qInstance.addBackend(new QBackendMetaData().withName("rdbms"));
GeoDataQBitConfig config = new GeoDataQBitConfig()
.withBackendName("rdbms")
.withTableNamePrefix("shipping")
.withEnableCountries(true)
.withEnableStateProvinces(false)
.withEnableCities(false);
GeoDataQBitProducer producer = new GeoDataQBitProducer()
.withConfig(config);
producer.produce(qInstance, "shipping-geo");
assertThat(qInstance.getQBits().values().stream()
.filter(qb -> "shipping-geo".equals(qb.getNamespace()))
.findFirst()
.map(qb -> qb.getConfig())
.orElse(null)).isEqualTo(config);
}
/*******************************************************************************
** Test fluent withConfig method.
*******************************************************************************/
@Test
void testWithConfig_returnsSelf()
{
GeoDataQBitProducer producer = new GeoDataQBitProducer();
GeoDataQBitConfig config = new GeoDataQBitConfig();
GeoDataQBitProducer result = producer.withConfig(config);
assertThat(result).isSameAs(producer);
assertThat(producer.getConfig()).isSameAs(config);
}
/*******************************************************************************
** Test constants are correctly defined.
*******************************************************************************/
@Test
void testConstants()
{
assertThat(GeoDataQBitProducer.GROUP_ID).isEqualTo("com.kingsrook.qbits");
assertThat(GeoDataQBitProducer.ARTIFACT_ID).isEqualTo("qbit-geo-data");
assertThat(GeoDataQBitProducer.VERSION).isEqualTo("0.1.0");
}
}
@@ -0,0 +1,140 @@
/*******************************************************************************
** Unit tests for GeoDataLiquibaseGenerator.
*******************************************************************************/
package com.kingsrook.qbits.geodata.liquibase;
import java.io.IOException;
import com.kingsrook.qbits.geodata.GeoDataQBitConfig;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class GeoDataLiquibaseGeneratorTest
{
/*******************************************************************************
** Test that prefix is substituted in generated changelog.
*******************************************************************************/
@Test
void testGenerate_substitutesPrefix() throws IOException
{
GeoDataQBitConfig config = new GeoDataQBitConfig()
.withTableNamePrefix("shipping")
.withEnableCountries(true)
.withEnableStateProvinces(true)
.withEnableCities(true);
String result = GeoDataLiquibaseGenerator.generate(config);
assertThat(result).contains("tableName=\"shipping_country\"");
assertThat(result).contains("tableName=\"shipping_state_province\"");
assertThat(result).contains("tableName=\"shipping_city\"");
assertThat(result).contains("id=\"shipping-create-country-v1\"");
assertThat(result).doesNotContain("${prefix}");
}
/*******************************************************************************
** Test that city section is removed when cities disabled.
*******************************************************************************/
@Test
void testGenerate_removesCitySection() throws IOException
{
GeoDataQBitConfig config = new GeoDataQBitConfig()
.withTableNamePrefix("test")
.withEnableCountries(true)
.withEnableStateProvinces(true)
.withEnableCities(false);
String result = GeoDataLiquibaseGenerator.generate(config);
assertThat(result).contains("test_country");
assertThat(result).contains("test_state_province");
assertThat(result).doesNotContain("test_city");
assertThat(result).doesNotContain("SECTION: city");
}
/*******************************************************************************
** Test that stateProvince section is removed when states disabled.
*******************************************************************************/
@Test
void testGenerate_removesStateProvinceSection() throws IOException
{
GeoDataQBitConfig config = new GeoDataQBitConfig()
.withTableNamePrefix("test")
.withEnableCountries(true)
.withEnableStateProvinces(false)
.withEnableCities(false);
String result = GeoDataLiquibaseGenerator.generate(config);
assertThat(result).contains("test_country");
assertThat(result).doesNotContain("test_state_province");
assertThat(result).doesNotContain("test_city");
}
/*******************************************************************************
** Test that all sections removed when all disabled.
*******************************************************************************/
@Test
void testGenerate_removesAllSections() throws IOException
{
GeoDataQBitConfig config = new GeoDataQBitConfig()
.withTableNamePrefix("test")
.withEnableCountries(false)
.withEnableStateProvinces(false)
.withEnableCities(false);
String result = GeoDataLiquibaseGenerator.generate(config);
assertThat(result).doesNotContain("createTable");
assertThat(result).contains("databaseChangeLog");
}
/*******************************************************************************
** Test generation without prefix removes placeholder.
*******************************************************************************/
@Test
void testGenerate_noPrefix() throws IOException
{
GeoDataQBitConfig config = new GeoDataQBitConfig()
.withEnableCountries(true)
.withEnableStateProvinces(false)
.withEnableCities(false);
String result = GeoDataLiquibaseGenerator.generate(config);
assertThat(result).contains("tableName=\"country\"");
assertThat(result).doesNotContain("${prefix}");
assertThat(result).doesNotContain("_country\"");
}
/*******************************************************************************
** Test that foreign key constraints use correct prefixed table names.
*******************************************************************************/
@Test
void testGenerate_foreignKeysUsePrefixedNames() throws IOException
{
GeoDataQBitConfig config = new GeoDataQBitConfig()
.withTableNamePrefix("billing")
.withEnableCountries(true)
.withEnableStateProvinces(true)
.withEnableCities(true);
String result = GeoDataLiquibaseGenerator.generate(config);
assertThat(result).contains("referencedTableName=\"billing_country\"");
assertThat(result).contains("referencedTableName=\"billing_state_province\"");
assertThat(result).contains("constraintName=\"billing_fk_state_province_country\"");
}
}
@@ -0,0 +1,75 @@
/*******************************************************************************
** Unit tests for GeoDataSyncProcessMetaDataProducer.
*******************************************************************************/
package com.kingsrook.qbits.geodata.sync;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class GeoDataSyncProcessMetaDataProducerTest
{
/*******************************************************************************
** Test that NAME constant is correctly defined.
*******************************************************************************/
@Test
void testNameConstant()
{
assertThat(GeoDataSyncProcessMetaDataProducer.NAME).isEqualTo("geoDataSync");
}
/*******************************************************************************
** Test that produce returns valid process metadata.
*******************************************************************************/
@Test
void testProduce_returnsValidProcess()
{
GeoDataSyncProcessMetaDataProducer producer = new GeoDataSyncProcessMetaDataProducer();
QInstance qInstance = new QInstance();
QProcessMetaData process = producer.produce(qInstance);
assertThat(process).isNotNull();
assertThat(process.getName()).isEqualTo("geoDataSync");
assertThat(process.getLabel()).isEqualTo("Sync Geographic Data");
}
/*******************************************************************************
** Test that process has sync step.
*******************************************************************************/
@Test
void testProduce_hasSyncStep()
{
GeoDataSyncProcessMetaDataProducer producer = new GeoDataSyncProcessMetaDataProducer();
QInstance qInstance = new QInstance();
QProcessMetaData process = producer.produce(qInstance);
assertThat(process.getStepList()).hasSize(1);
assertThat(process.getStepList().get(0).getName()).isEqualTo("sync");
}
/*******************************************************************************
** Test that sync step references GeoDataSyncStep code.
*******************************************************************************/
@Test
void testProduce_syncStepReferencesCorrectCode()
{
GeoDataSyncProcessMetaDataProducer producer = new GeoDataSyncProcessMetaDataProducer();
QInstance qInstance = new QInstance();
QProcessMetaData process = producer.produce(qInstance);
assertThat(process.getStepList().get(0)).isNotNull();
}
}
@@ -0,0 +1,61 @@
/*******************************************************************************
** Unit tests for GeoDataSyncStep.
*******************************************************************************/
package com.kingsrook.qbits.geodata.sync;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
class GeoDataSyncStepTest
{
/*******************************************************************************
** Test that constant is correctly defined.
*******************************************************************************/
@Test
void testFieldConstant()
{
assertThat(GeoDataSyncStep.FIELD_TABLE_NAME_PREFIX).isEqualTo("tableNamePrefix");
}
/*******************************************************************************
** Test that run throws exception when prefix is missing.
*******************************************************************************/
@Test
void testRun_missingPrefix_throwsException()
{
GeoDataSyncStep step = new GeoDataSyncStep();
RunBackendStepInput input = new RunBackendStepInput();
RunBackendStepOutput output = new RunBackendStepOutput();
assertThatThrownBy(() -> step.run(input, output))
.isInstanceOf(QException.class)
.hasMessageContaining("tableNamePrefix is required");
}
/*******************************************************************************
** Test that run throws exception when prefix is empty.
*******************************************************************************/
@Test
void testRun_emptyPrefix_throwsException()
{
GeoDataSyncStep step = new GeoDataSyncStep();
RunBackendStepInput input = new RunBackendStepInput();
input.addValue(GeoDataSyncStep.FIELD_TABLE_NAME_PREFIX, "");
RunBackendStepOutput output = new RunBackendStepOutput();
assertThatThrownBy(() -> step.run(input, output))
.isInstanceOf(QException.class)
.hasMessageContaining("tableNamePrefix is required");
}
}