feat: upgrade to QQQ 0.35.0 with Java 21 and add unit tests #1
No files matched your search
@@ -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\/.*/
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# QBit: Geo Data
|
||||
|
||||
[]()
|
||||
[](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)
|
||||
@@ -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>
|
||||
|
||||
+154
@@ -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;
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -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>
|
||||
+182
@@ -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");
|
||||
}
|
||||
}
|
||||
+138
@@ -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");
|
||||
}
|
||||
}
|
||||
+140
@@ -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\"");
|
||||
}
|
||||
}
|
||||
+75
@@ -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();
|
||||
}
|
||||
}
|
||||
+61
@@ -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");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user