Switched to latest tenant commons mechanics and added a basic tests to check context loads and liquibase executes correctly

This commit is contained in:
Timo Bejan 2024-09-25 13:38:08 +03:00
parent 5296c2b042
commit 7ef3897067
18 changed files with 374 additions and 117 deletions

View File

@ -1,7 +1,6 @@
plugins {
`java-library`
`maven-publish`
`kotlin-dsl`
pmd
checkstyle
jacoco
@ -11,9 +10,6 @@ plugins {
repositories {
mavenLocal()
maven {
url = uri("https://pdftron.com/maven/release")
}
maven {
url = uri("https://nexus.knecon.com/repository/gindev/");
credentials {
@ -26,10 +22,12 @@ repositories {
val springBootVersion = "3.1.5"
val springCloudVersion = "4.0.4"
val springRabbitTest = "3.0.9"
val testContainersVersion = "1.20.1"
dependencies {
api("com.fasterxml.jackson.core:jackson-databind:2.15.2:")
api("com.knecon.fforesight:tenant-commons:0.30.0")
api("com.knecon.fforesight:tenant-commons:0.31.0")
api("com.zaxxer:HikariCP:5.0.1")
api("com.google.guava:guava:32.1.2-jre")
api("org.liquibase:liquibase-core:4.20.0")
@ -42,7 +40,9 @@ dependencies {
api("org.projectlombok:lombok:1.18.28")
runtimeOnly("org.springframework.boot:spring-boot-devtools:${springBootVersion}")
testImplementation("org.springframework.boot:spring-boot-starter-test:${springBootVersion}")
testImplementation("org.springframework.amqp:spring-rabbit-test:3.0.6")
testImplementation("org.testcontainers:postgresql:${testContainersVersion}")
testImplementation("org.springframework.amqp:spring-rabbit-test:${springRabbitTest}")
testImplementation("org.testcontainers:rabbitmq:${testContainersVersion}")
}
group = "com.knecon.fforesight"
@ -93,7 +93,7 @@ tasks.named<Test>("test") {
sonarqube {
properties {
property("sonar.login", providers.gradleProperty("sonarToken").getOrNull())
providers.gradleProperty("sonarToken").getOrNull()?.let { property("sonar.login", it) }
property("sonar.host.url", "https://sonarqube.knecon.com")
}
}

View File

@ -1,40 +0,0 @@
package com.knecon.fforesight.databasetenantcommons;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueBuilder;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.knecon.fforesight.tenantcommons.queue.TenantMessagingConfiguration;
@Configuration
public class TenantMessagingConfigurationImpl extends TenantMessagingConfiguration {
public String getTenantSyncQueueName() {
return this.getQueueNameWithSuffix("_tenant_sync");
}
@Bean({"tenantSyncQueue"})
public Queue tenantSyncQueue() {
return QueueBuilder.durable(this.getTenantSyncQueueName())
.withArgument("x-dead-letter-exchange", "")
.withArgument("x-dead-letter-routing-key", this.getTenantEventsDLQName())
.withArgument("x-expires", 300000)
.build();
}
@Bean
public Binding tenantSyncBinding(@Qualifier("tenantSyncQueue") Queue tenantCreatedQueue, @Qualifier("tenantExchange") TopicExchange tenantExchange) {
return BindingBuilder.bind(tenantCreatedQueue).to(tenantExchange).with("tenant.sync");
}
}

View File

@ -0,0 +1,31 @@
package com.knecon.fforesight.databasetenantcommons.providers;
import org.springframework.stereotype.Service;
import com.knecon.fforesight.tenantcommons.listener.ITenantEventHandler;
import com.knecon.fforesight.tenantcommons.model.TenantCreatedEvent;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
@RequiredArgsConstructor
public class TenantCreatedDatabaseEventHandler implements ITenantEventHandler<TenantCreatedEvent> {
private final TenantLiquibaseInitializer tenantLiquibaseInitializer;
@Override
public void handle(TenantCreatedEvent tenantCreatedEvent) {
tenantLiquibaseInitializer.initializeTenant(tenantCreatedEvent.getTenantId());
}
@Override
public Class<TenantCreatedEvent> getEventClass() {
return TenantCreatedEvent.class;
}
}

View File

@ -1,29 +0,0 @@
package com.knecon.fforesight.databasetenantcommons.providers;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import com.knecon.fforesight.databasetenantcommons.providers.events.TenantCreatedEvent;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
@RequiredArgsConstructor
public class TenantCreatedListener {
private final TenantLiquibaseInitializer tenantLiquibaseInitializer;
@SneakyThrows
@RabbitListener(queues = "#{tenantMessagingConfigurationImpl.getTenantCreatedQueueName()}")
public void createTenant(TenantCreatedEvent tenantRequest) {
tenantLiquibaseInitializer.initializeTenant(tenantRequest.getTenantId());
}
}

View File

@ -2,13 +2,10 @@ package com.knecon.fforesight.databasetenantcommons.providers;
import java.util.Optional;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import com.knecon.fforesight.databasetenantcommons.providers.events.TenantSyncEvent;
import jakarta.annotation.PostConstruct;
import com.knecon.fforesight.tenantcommons.listener.ITenantEventHandler;
import com.knecon.fforesight.tenantcommons.model.TenantSyncEvent;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
@ -16,16 +13,24 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
@RequiredArgsConstructor
public class TenantSyncListener {
public class TenantSyncDatabaseEventHandler implements ITenantEventHandler<TenantSyncEvent> {
private final Optional<TenantSyncService> tenantSyncServices;
@Override
@SneakyThrows
@RabbitListener(queues = "#{tenantMessagingConfigurationImpl.getTenantSyncQueueName()}")
public void syncTenant(TenantSyncEvent tenantSyncEvent) {
public void handle(TenantSyncEvent tenantSyncEvent) {
tenantSyncServices.ifPresent(t -> t.syncTenant(tenantSyncEvent));
}
@Override
public Class<TenantSyncEvent> getEventClass() {
return TenantSyncEvent.class;
}
}

View File

@ -1,9 +1,9 @@
package com.knecon.fforesight.databasetenantcommons.providers;
import com.knecon.fforesight.databasetenantcommons.providers.events.TenantSyncEvent;
import com.knecon.fforesight.tenantcommons.model.TenantSyncEvent;
public interface TenantSyncService {
void syncTenant(TenantSyncEvent tenantSyncEvent);
void syncTenant(TenantSyncEvent tenantSyncEvent);
}

View File

@ -1,14 +0,0 @@
package com.knecon.fforesight.databasetenantcommons.providers.events;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TenantCreatedEvent {
private String tenantId;
}

View File

@ -1,17 +0,0 @@
package com.knecon.fforesight.databasetenantcommons.providers.events;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class TenantSyncEvent {
private String tenantId;
private JsonNode payload;
}

View File

@ -0,0 +1,56 @@
package com.knecon.fforesight.databasetenantcommons;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import com.knecon.fforesight.databasetenantcommons.utils.SpringPostgreSQLTestContainer;
import com.knecon.fforesight.tenantcommons.MultiTenancyAutoConfiguration;
import lombok.extern.slf4j.Slf4j;
import org.testcontainers.containers.RabbitMQContainer;
@Slf4j
@ContextConfiguration(initializers = {BasicIntegrationTest.Initializer.class})
@ImportAutoConfiguration({MultiTenancyAutoConfiguration.class, DatabaseTenantCommonsAutoConfiguration.class})
@SuppressWarnings("PMD.TestClassWithoutTestCases")
@SpringBootTest
public abstract class BasicIntegrationTest {
static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@SuppressWarnings("PMD")
public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
var postgreSQLContainerMaster = SpringPostgreSQLTestContainer.getInstance().withDatabaseName("integration-tests-db-master").withUsername("sa").withPassword("sa");
postgreSQLContainerMaster.start();
var port = postgreSQLContainerMaster.getJdbcUrl().substring(0, postgreSQLContainerMaster.getJdbcUrl().lastIndexOf('/')).split(":")[3];
RabbitMQContainer rabbitContainer = new RabbitMQContainer("rabbitmq:3.12");
rabbitContainer.start();
log.info("Rabbit container started and available at {}", rabbitContainer.getHttpUrl());
TestPropertyValues.of("RABBITMQ_USERNAME=" + rabbitContainer.getAdminUsername(),
"RABBITMQ_PASSWORD=" + rabbitContainer.getAdminPassword(),
"RABBITMQ_HOST=" + rabbitContainer.getHost(),
"RABBITMQ_PORT=" + rabbitContainer.getAmqpPort(),
"DB_PORT=" + port,
"DB_HOST=" + postgreSQLContainerMaster.getHost(),
"POD_NAME=tenant-database-commons"
).applyTo(configurableApplicationContext.getEnvironment());
}
}
@SpringBootApplication(exclude = {LiquibaseAutoConfiguration.class, JpaRepositoriesAutoConfiguration.class})
static class TestConfiguration {
}
}

View File

@ -0,0 +1,93 @@
package com.knecon.fforesight.databasetenantcommons;
import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import com.knecon.fforesight.databasetenantcommons.providers.TenantLiquibaseInitializer;
import com.knecon.fforesight.databasetenantcommons.utils.TestTenantProvider;
import com.knecon.fforesight.databasetenantcommons.utils.entity.TestEntity;
import com.knecon.fforesight.databasetenantcommons.utils.repo.TestRepository;
import com.knecon.fforesight.tenantcommons.EncryptionDecryptionService;
import com.knecon.fforesight.tenantcommons.TenantContext;
import com.knecon.fforesight.tenantcommons.model.AuthDetails;
import com.knecon.fforesight.tenantcommons.model.DatabaseConnection;
import com.knecon.fforesight.tenantcommons.model.TenantResponse;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
@Slf4j
public class LiquibaseTenantInitIntegrationTest extends BasicIntegrationTest {
private final static String TENANT_1 = "tenant1";
@Autowired
private EncryptionDecryptionService encryptionDecryptionService;
@Autowired
private RabbitTemplate rabbitTemplate;
@Autowired
private TestTenantProvider tenantProvider;
@Autowired
private TestRepository testRepository;
@Value("${DB_PORT}")
private String port;
@Value("${DB_HOST}")
private String host;
@Value("${fforesight.tenant-exchange.name:tenants-exchange}")
String tenantExchangeName;
@Test
@SneakyThrows
public void testTenantInitialized() {
TenantContext.setTenantId(TENANT_1);
log.info("Creating tenant {}", TENANT_1);
var testTenant = new TenantResponse();
testTenant.setTenantId(TENANT_1);
testTenant.setGuid(TENANT_1);
testTenant.setDisplayName(TENANT_1);
testTenant.setAuthDetails(new AuthDetails());
testTenant.setDatabaseConnection(DatabaseConnection.builder()
.driver("postgresql")
.host(host)
.port(port)
.database("integration-tests-db-master")
.schema(TENANT_1)
.username("sa")
.password(encryptionDecryptionService.encrypt("sa"))
.build());
tenantProvider.addTenant(testTenant);
log.info("Sending tenant created event");
rabbitTemplate.convertAndSend(tenantExchangeName, "tenant.created", TENANT_1);
var iteration = 0;
do {
Thread.sleep(2000);
try {
testRepository.findAll();
// when find succeeds tenant was created
break;
} catch (Exception e) {
// continue
}
} while (iteration < 15);
testRepository.save(new TestEntity(1, "Hello"));
var all = testRepository.findAll();
assertThat(all).isNotEmpty();
assertThat(all.get(0).getLabel()).isEqualTo("Hello");
}
}

View File

@ -0,0 +1,25 @@
package com.knecon.fforesight.databasetenantcommons.utils;
import org.testcontainers.containers.PostgreSQLContainer;
public final class SpringPostgreSQLTestContainer extends PostgreSQLContainer<SpringPostgreSQLTestContainer> {
private static final String IMAGE_VERSION = "postgres:15.2";
private static SpringPostgreSQLTestContainer container;
private SpringPostgreSQLTestContainer() {
super(IMAGE_VERSION);
}
public static SpringPostgreSQLTestContainer getInstance() {
if (container == null) {
container = new SpringPostgreSQLTestContainer();
}
return container;
}
}

View File

@ -0,0 +1,46 @@
package com.knecon.fforesight.databasetenantcommons.utils;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Service;
import com.knecon.fforesight.tenantcommons.TenantProvider;
import com.knecon.fforesight.tenantcommons.listener.ITenantEventHandler;
import com.knecon.fforesight.tenantcommons.model.TenantCreatedEvent;
import com.knecon.fforesight.tenantcommons.model.TenantResponse;
import com.knecon.fforesight.tenantcommons.model.UpdateDetailsRequest;
@Service
public class TestTenantProvider implements TenantProvider {
private List<TenantResponse> tenants = new ArrayList<>();
@Override
public void updateDetails(String s, UpdateDetailsRequest updateDetailsRequest) {
//noop
}
@Override
public TenantResponse getTenant(String s) {
return tenants.stream().filter(t -> t.getTenantId().equals(s)).findFirst().orElse(null);
}
@Override
public List<TenantResponse> getTenants() {
return tenants;
}
public void addTenant(TenantResponse tenantResponse) {
this.tenants.add(tenantResponse);
}
}

View File

@ -0,0 +1,24 @@
package com.knecon.fforesight.databasetenantcommons.utils.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.testcontainers.shaded.org.checkerframework.checker.units.qual.A;
@Entity
@Data
@Table(name = "test")
@NoArgsConstructor
@AllArgsConstructor
public class TestEntity {
@Id
private int id;
@Column
private String label;
}

View File

@ -0,0 +1,9 @@
package com.knecon.fforesight.databasetenantcommons.utils.repo;
import org.springframework.data.jpa.repository.JpaRepository;
import com.knecon.fforesight.databasetenantcommons.utils.entity.TestEntity;
public interface TestRepository extends JpaRepository<TestEntity, Integer> {
}

View File

@ -0,0 +1,34 @@
spring:
application:
name: database-tenant-commons-tester
rabbitmq:
host: ${RABBITMQ_HOST:localhost}
port: ${RABBITMQ_PORT:5672}
username: ${RABBITMQ_USERNAME:user}
password: ${RABBITMQ_PASSWORD:rabbitmq}
listener:
simple:
acknowledge-mode: AUTO
concurrency: 5
retry:
enabled: true
max-attempts: 3
max-interval: 15000
prefetch: 1
multitenancy:
datasource-cache:
maximumSize: 100
expireAfterAccess: 1
packages:
repositories: 'com.knecon.fforesight.databasetenantcommons.utils.repo'
entities: 'com.knecon.fforesight.databasetenantcommons.utils.entity'
tenant:
datasource:
driverClassName: org.postgresql.Driver
liquibase:
changeLog: classpath:changelog/db.changelog-tenant.yaml
clear-checksums: true
logging.type: ${LOGGING_TYPE:CONSOLE}

View File

@ -0,0 +1,3 @@
databaseChangeLog:
- include:
file: changelog/tenant/test-table.changelog.yaml

View File

@ -0,0 +1,18 @@
databaseChangeLog:
- changeSet:
id: test-1
author: test-1
changes:
- createTable:
columns:
- column:
constraints:
nullable: false
primaryKey: true
primaryKeyName: test_pkey
name: id
type: BIGINT
- column:
name: label
type: VARCHAR(255)
tableName: test

View File

@ -0,0 +1,13 @@
<configuration>
<springProperty scope="configuration" name="logType" source="logging.type"/>
<springProperty scope="context" name="application.name" source="spring.application.name"/>
<springProperty scope="context" name="version" source="project.version"/>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<include resource="org/springframework/boot/logging/logback/console-appender.xml"/>
<root level="INFO">
<appender-ref ref="${logType}"/>
</root>
</configuration>