Added integration test

This commit is contained in:
Michael Schnell
2019-12-28 16:14:29 +01:00
parent 6cb56b2adb
commit 69dc18f424
10 changed files with 411 additions and 162 deletions

View File

@@ -46,7 +46,7 @@ public class CmdApplication {
* @return New event store instance.
*/
@Bean(destroyMethod = "shutdown")
public com.github.msemys.esjc.EventStore getESHttpEventStore(final Config config) {
public com.github.msemys.esjc.EventStore getESHttpEventStore(final CmdConfig config) {
return EventStoreBuilder.newBuilder().singleNodeAddress(config.getEventStoreHost(), config.getEventStoreTcpPort())
.executor(Executors.newFixedThreadPool(10)).userCredentials(config.getEventStoreUser(), config.getEventStorePassword())
.build();

View File

@@ -4,7 +4,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class Config {
public class CmdConfig {
private static final String EVENT_STORE_PROTOCOL = "http";
@@ -39,7 +39,7 @@ public class Config {
/**
* Constructor using default values internally.
*/
public Config() {
public CmdConfig() {
super();
this.eventStoreProtocol = EVENT_STORE_PROTOCOL;
this.eventStoreHost = EVENT_STORE_HOST;
@@ -59,7 +59,7 @@ public class Config {
* @param eventStoreUser User.
* @param eventStorePassword Password.
*/
public Config(final String eventStoreProtocol, final String eventStoreHost, final int eventStoreHttpPort,
public CmdConfig(final String eventStoreProtocol, final String eventStoreHost, final int eventStoreHttpPort,
final int eventStoreTcpPort, final String eventStoreUser, final String eventStorePassword) {
super();
this.eventStoreProtocol = eventStoreProtocol;

View File

@@ -11,7 +11,7 @@
<modules>
<module>shared</module>
<module>query</module>
<!-- <module>command</module> -->
<module>command</module>
</modules>
</project>

View File

@@ -22,6 +22,8 @@
<maven.compiler.target>1.8</maven.compiler.target>
<java.version>1.8</java.version>
<esc.version>0.3.1-SNAPSHOT</esc.version>
<surefire-plugin.version>2.22.1</surefire-plugin.version>
<failsafe-plugin.version>2.22.2</failsafe-plugin.version>
</properties>
<dependencies>
@@ -87,12 +89,6 @@
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!-- test -->
<dependency>
@@ -100,6 +96,10 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
@@ -107,15 +107,156 @@
</exclusions>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>spring-mock-mvc</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.awaitility</groupId>
<artifactId>awaitility</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>${failsafe-plugin.version}</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>io.fabric8</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>0.31.0</version>
<configuration>
<images>
<image>
<name>eventstore/eventstore:release-4.1.3</name>
<run>
<network>
<mode>bridge</mode>
</network>
<ports>
<port>1113:1113</port>
<port>2113:2113</port>
</ports>
<log>
<enabled>false</enabled>
</log>
<wait>
<http>
<url>http://localhost:2113/web/index.html#/</url>
<method>GET</method>
</http>
<time>20000</time>
</wait>
</run>
</image>
<image>
<name>mariadb:10.4</name>
<run>
<network>
<mode>bridge</mode>
</network>
<ports>
<port>3306:3306</port>
</ports>
<log>
<enabled>false</enabled>
</log>
<env>
<MYSQL_INITDB_SKIP_TZINFO>1</MYSQL_INITDB_SKIP_TZINFO>
<MYSQL_ROOT_PASSWORD>xyz</MYSQL_ROOT_PASSWORD>
<MYSQL_DATABASE>querydb</MYSQL_DATABASE>
<MYSQL_USER>mary</MYSQL_USER>
<MYSQL_PASSWORD>abc</MYSQL_PASSWORD>
</env>
<wait>
<time>10000</time>
</wait>
</run>
</image>
</images>
</configuration>
<executions>
<execution>
<id>start-images</id>
<phase>pre-integration-test</phase>
<goals>
<goal>start</goal>
</goals>
</execution>
<execution>
<id>stop-images</id>
<phase>post-integration-test</phase>
<goals>
<goal>stop</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>sonatype.oss.snapshots</id>
<name>Sonatype OSS Snapshot Repository</name>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<updatePolicy>always</updatePolicy>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
</project>

View File

@@ -1,128 +0,0 @@
package org.fuin.cqrs4j.example.spring.query.app;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class Config {
private static final String EVENT_STORE_PROTOCOL = "http";
private static final String EVENT_STORE_HOST = "localhost";
private static final int EVENT_STORE_HTTP_PORT = 2113;
private static final int EVENT_STORE_TCP_PORT = 1113;
private static final String EVENT_STORE_USER = "admin";
private static final String EVENT_STORE_PASSWORD = "changeit";
@Value("${EVENT_STORE_PROTOCOL:http}")
private String eventStoreProtocol;
@Value("${EVENT_STORE_HOST:localhost}")
private String eventStoreHost;
@Value("${EVENT_STORE_HTTP_PORT:2113}")
private int eventStoreHttpPort;
@Value("${EVENT_STORE_TCP_PORT:1113}")
private int eventStoreTcpPort;
@Value("${EVENT_STORE_USER:admin}")
private String eventStoreUser;
@Value("${EVENT_STORE_PASSWORD:changeit}")
private String eventStorePassword;
/**
* Constructor using default values internally.
*/
public Config() {
super();
this.eventStoreProtocol = EVENT_STORE_PROTOCOL;
this.eventStoreHost = EVENT_STORE_HOST;
this.eventStoreHttpPort = EVENT_STORE_HTTP_PORT;
this.eventStoreTcpPort = EVENT_STORE_TCP_PORT;
this.eventStoreUser = EVENT_STORE_USER;
this.eventStorePassword = EVENT_STORE_PASSWORD;
}
/**
* Constructor with all data.
*
* @param eventStoreProtocol Protocol.
* @param eventStoreHost Host.
* @param eventStoreHttpPort HTTP port
* @param eventStoreTcpPort TCP port.
* @param eventStoreUser User.
* @param eventStorePassword Password.
*/
public Config(final String eventStoreProtocol, final String eventStoreHost, final int eventStoreHttpPort, final int eventStoreTcpPort,
final String eventStoreUser, final String eventStorePassword) {
super();
this.eventStoreProtocol = eventStoreProtocol;
this.eventStoreHost = eventStoreHost;
this.eventStoreHttpPort = eventStoreHttpPort;
this.eventStoreTcpPort = eventStoreTcpPort;
this.eventStoreUser = eventStoreUser;
this.eventStorePassword = eventStorePassword;
}
/**
* Returns the protocol of the event store.
*
* @return Either http or https.
*/
public String getEventStoreProtocol() {
return eventStoreProtocol;
}
/**
* Returns the host name of the event store.
*
* @return Name.
*/
public String getEventStoreHost() {
return eventStoreHost;
}
/**
* Returns the HTTP port of the event store.
*
* @return Port.
*/
public int getEventStoreHttpPort() {
return eventStoreHttpPort;
}
/**
* Returns the TCP port of the event store.
*
* @return Port.
*/
public int getEventStoreTcpPort() {
return eventStoreTcpPort;
}
/**
* Returns the username of the event store.
*
* @return Username.
*/
public String getEventStoreUser() {
return eventStoreUser;
}
/**
* Returns the password of the event store.
*
* @return Password.
*/
public String getEventStorePassword() {
return eventStorePassword;
}
}

View File

@@ -32,7 +32,7 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
"org.fuin.cqrs4j.example.spring.query.controller", "org.fuin.cqrs4j.example.spring.query.domain",
"org.fuin.cqrs4j.example.spring.query.handler" })
@EnableJpaRepositories("org.fuin.cqrs4j.example.spring.query.domain")
@EntityScan({"org.fuin.cqrs4j.example.spring.query.domain", "org.fuin.cqrs4j.example.spring.query.handler"})
@EntityScan({ "org.fuin.cqrs4j.example.spring.query.domain", "org.fuin.cqrs4j.example.spring.query.handler" })
@EnableScheduling
@EnableAsync
public class QryApplication {
@@ -43,14 +43,13 @@ public class QryApplication {
* @return Fully configured instance.
*/
@Bean
public Jsonb createJsonb() {
final JsonbConfig config = new JsonbConfig()
.withAdapters(SharedUtils.JSONB_ADAPTERS)
.withPropertyVisibilityStrategy(new FieldAccessStrategy());
final Jsonb jsonb = JsonbBuilder.create(config);
return jsonb;
}
public Jsonb createJsonb() {
final JsonbConfig config = new JsonbConfig().withAdapters(SharedUtils.JSONB_ADAPTERS)
.withPropertyVisibilityStrategy(new FieldAccessStrategy());
final Jsonb jsonb = JsonbBuilder.create(config);
return jsonb;
}
/**
* Creates a HTTP based event store connection.
*
@@ -59,7 +58,7 @@ public class QryApplication {
* @return New event store instance.
*/
@Bean(destroyMethod = "close")
public IESHttpEventStore getESHttpEventStore(final Config config) {
public IESHttpEventStore getESHttpEventStore(final QryConfig config) {
final String url = config.getEventStoreProtocol() + "://" + config.getEventStoreHost() + ":"
+ config.getEventStoreHttpPort();
try {

View File

@@ -0,0 +1,134 @@
package org.fuin.cqrs4j.example.spring.query.app;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
public class QryConfig {
private static final String EVENT_STORE_PROTOCOL = "http";
private static final String EVENT_STORE_HOST = "localhost";
private static final int EVENT_STORE_HTTP_PORT = 2113;
private static final int EVENT_STORE_TCP_PORT = 1113;
private static final String EVENT_STORE_USER = "admin";
private static final String EVENT_STORE_PASSWORD = "changeit";
@Value("${EVENT_STORE_PROTOCOL:http}")
private String eventStoreProtocol;
@Value("${EVENT_STORE_HOST:localhost}")
private String eventStoreHost;
@Value("${EVENT_STORE_HTTP_PORT:2113}")
private int eventStoreHttpPort;
@Value("${EVENT_STORE_TCP_PORT:1113}")
private int eventStoreTcpPort;
@Value("${EVENT_STORE_USER:admin}")
private String eventStoreUser;
@Value("${EVENT_STORE_PASSWORD:changeit}")
private String eventStorePassword;
/**
* Constructor using default values internally.
*/
public QryConfig() {
super();
this.eventStoreProtocol = EVENT_STORE_PROTOCOL;
this.eventStoreHost = EVENT_STORE_HOST;
this.eventStoreHttpPort = EVENT_STORE_HTTP_PORT;
this.eventStoreTcpPort = EVENT_STORE_TCP_PORT;
this.eventStoreUser = EVENT_STORE_USER;
this.eventStorePassword = EVENT_STORE_PASSWORD;
}
/**
* Constructor with all data.
*
* @param eventStoreProtocol Protocol.
* @param eventStoreHost Host.
* @param eventStoreHttpPort HTTP port
* @param eventStoreTcpPort TCP port.
* @param eventStoreUser User.
* @param eventStorePassword Password.
*/
public QryConfig(final String eventStoreProtocol, final String eventStoreHost, final int eventStoreHttpPort,
final int eventStoreTcpPort, final String eventStoreUser, final String eventStorePassword) {
super();
this.eventStoreProtocol = eventStoreProtocol;
this.eventStoreHost = eventStoreHost;
this.eventStoreHttpPort = eventStoreHttpPort;
this.eventStoreTcpPort = eventStoreTcpPort;
this.eventStoreUser = eventStoreUser;
this.eventStorePassword = eventStorePassword;
}
/**
* Returns the protocol of the event store.
*
* @return Either http or https.
*/
public String getEventStoreProtocol() {
return eventStoreProtocol;
}
/**
* Returns the host name of the event store.
*
* @return Name.
*/
public String getEventStoreHost() {
return eventStoreHost;
}
/**
* Returns the HTTP port of the event store.
*
* @return Port.
*/
public int getEventStoreHttpPort() {
return eventStoreHttpPort;
}
/**
* Returns the TCP port of the event store.
*
* @return Port.
*/
public int getEventStoreTcpPort() {
return eventStoreTcpPort;
}
/**
* Returns the username of the event store.
*
* @return Username.
*/
public String getEventStoreUser() {
return eventStoreUser;
}
/**
* Returns the password of the event store.
*
* @return Password.
*/
public String getEventStorePassword() {
return eventStorePassword;
}
@Override
public String toString() {
return "QryConfig [eventStoreProtocol=" + eventStoreProtocol + ", eventStoreHost=" + eventStoreHost
+ ", eventStoreHttpPort=" + eventStoreHttpPort + ", eventStoreTcpPort=" + eventStoreTcpPort
+ ", eventStoreUser=" + eventStoreUser + "]";
}
}

View File

@@ -9,6 +9,7 @@ import java.util.Set;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.annotation.PreDestroy;
import javax.annotation.concurrent.ThreadSafe;
import org.fuin.ddd4j.ddd.EventType;
@@ -82,6 +83,12 @@ public class PersonProjector {
APP_STARTED.set(true);
}
@PreDestroy
public void destroy() {
APP_STARTED.set(false);
LOG.info("Application stopped");
}
private void readStreamEvents() {
// TODO Make sure a projection with the correct events exists

View File

@@ -1,13 +0,0 @@
package org.fuin.cqrs4j.example.spring.query;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class Cqrs4jSpringQueryExampleApplicationTests {
@Test
void contextLoads() {
}
}

View File

@@ -0,0 +1,109 @@
package org.fuin.cqrs4j.example.spring.query.api;
import static io.restassured.RestAssured.given;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.awaitility.Awaitility.await;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import java.util.Arrays;
import java.util.UUID;
import javax.persistence.EntityManager;
import org.fuin.cqrs4j.example.spring.query.app.QryApplication;
import org.fuin.cqrs4j.example.spring.query.app.QryConfig;
import org.fuin.cqrs4j.example.spring.query.controller.PersonController;
import org.fuin.cqrs4j.example.spring.query.domain.QryPerson;
import org.fuin.cqrs4j.example.spring.shared.PersonCreatedEvent;
import org.fuin.cqrs4j.example.spring.shared.PersonId;
import org.fuin.cqrs4j.example.spring.shared.PersonName;
import org.fuin.esc.api.CommonEvent;
import org.fuin.esc.api.EventId;
import org.fuin.esc.api.EventStore;
import org.fuin.esc.api.SimpleCommonEvent;
import org.fuin.esc.api.SimpleStreamId;
import org.fuin.esc.api.TypeName;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.web.context.WebApplicationContext;
import io.restassured.RestAssured;
import io.restassured.module.mockmvc.RestAssuredMockMvc;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ContextConfiguration(classes = QryApplication.class)
public class PersonControllerIT {
@LocalServerPort
int port;
@Autowired
WebApplicationContext wac;
@Autowired
EventStore eventStore;
@Autowired
EntityManager em;
@Autowired
PersonController testee;
@Autowired
QryConfig config;
@BeforeEach
public void initRestAssuredMockMvcStandalone() {
RestAssured.port = port;
RestAssuredMockMvc.webAppContextSetup(wac);
}
@Test
public void testGetByIdNotFound() {
given().pathParam("id", UUID.randomUUID()).when().get("/persons/{id}").then().statusCode(404);
}
public boolean findPerson(final PersonId personId) {
return em.find(QryPerson.class, personId.asString()) != null;
}
@Test
public void testGetByIdOK() {
// PREPARE
final PersonId personId = new PersonId(UUID.randomUUID());
final PersonName personName = new PersonName("Peter Parker " + personId);
final SimpleStreamId personStreamId = new SimpleStreamId(PersonId.TYPE + "-" + personId);
final PersonCreatedEvent event = new PersonCreatedEvent(personId, personName);
final CommonEvent ce = new SimpleCommonEvent(new EventId(event.getEventId().asBaseType()),
new TypeName(event.getEventType().asBaseType()), event);
eventStore.appendToStream(personStreamId, ce);
await().atMost(5, SECONDS).until(() -> findPerson(personId));
// TEST & VERIFY
final QryPerson person = given().pathParam("id", personId.asString()).when().get("/persons/{id}").then()
.statusCode(200).extract().as(QryPerson.class);
assertThat(person.getId(), is(equalTo(personId)));
assertThat(person.getName(), is(equalTo(personName)));
final QryPerson[] persons = given().when().get("/persons").then().statusCode(200).extract()
.as(QryPerson[].class);
assertThat(Arrays.asList(persons), is(not(empty())));
final QryPerson person0 = persons[0];
assertThat(person0.getId(), is(equalTo(personId)));
assertThat(person0.getName(), is(equalTo(personName)));
}
}