Compare commits
No commits in common. "main" and "draft" have entirely different histories.
File diff suppressed because it is too large
Load Diff
55
messaging-connection-demo/pom.xml
Normal file
55
messaging-connection-demo/pom.xml
Normal file
@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>cz.moneta.demo</groupId>
|
||||
<artifactId>messaging-connection-demo</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
|
||||
<kafka.version>3.7.1</kafka.version>
|
||||
<confluent.version>7.6.1</confluent.version>
|
||||
<ibm.mq.version>9.4.2.0</ibm.mq.version>
|
||||
<jakarta.jms.version>3.1.0</jakarta.jms.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.kafka</groupId>
|
||||
<artifactId>kafka-clients</artifactId>
|
||||
<version>${kafka.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.confluent</groupId>
|
||||
<artifactId>kafka-avro-serializer</artifactId>
|
||||
<version>${confluent.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.ibm.mq</groupId>
|
||||
<artifactId>com.ibm.mq.allclient</artifactId>
|
||||
<version>${ibm.mq.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>jakarta.jms</groupId>
|
||||
<artifactId>jakarta.jms-api</artifactId>
|
||||
<version>${jakarta.jms.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>confluent</id>
|
||||
<name>Confluent Maven Repository</name>
|
||||
<url>https://packages.confluent.io/maven/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
</project>
|
||||
@ -0,0 +1,69 @@
|
||||
package cz.moneta.demo;
|
||||
|
||||
import com.ibm.msg.client.jms.JmsConnectionFactory;
|
||||
import com.ibm.msg.client.jms.JmsFactoryFactory;
|
||||
import com.ibm.msg.client.wmq.WMQConstants;
|
||||
import org.apache.kafka.clients.producer.KafkaProducer;
|
||||
import org.apache.kafka.clients.producer.ProducerConfig;
|
||||
import org.apache.kafka.common.serialization.StringSerializer;
|
||||
|
||||
import javax.jms.JMSContext;
|
||||
import java.util.Properties;
|
||||
|
||||
public class MessagingConnectionApp {
|
||||
|
||||
public static KafkaProducer<String, String> createKafkaConnection(String bootstrapServers,
|
||||
String apiKey,
|
||||
String apiSecret) {
|
||||
Properties kafkaProps = new Properties();
|
||||
kafkaProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
|
||||
kafkaProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
|
||||
kafkaProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
|
||||
kafkaProps.put("security.protocol", "SASL_SSL");
|
||||
kafkaProps.put("sasl.mechanism", "PLAIN");
|
||||
kafkaProps.put("sasl.jaas.config",
|
||||
String.format("org.apache.kafka.common.security.plain.PlainLoginModule required username=\"%s\" password=\"%s\";",
|
||||
apiKey, apiSecret));
|
||||
|
||||
return new KafkaProducer<>(kafkaProps);
|
||||
}
|
||||
|
||||
public static JMSContext createMqConnection(String host,
|
||||
int port,
|
||||
String channel,
|
||||
String queueManager,
|
||||
String user,
|
||||
String password) throws Exception {
|
||||
JmsFactoryFactory factoryFactory = JmsFactoryFactory.getInstance(WMQConstants.WMQ_PROVIDER);
|
||||
JmsConnectionFactory connectionFactory = factoryFactory.createConnectionFactory();
|
||||
|
||||
connectionFactory.setStringProperty(WMQConstants.WMQ_HOST_NAME, host);
|
||||
connectionFactory.setIntProperty(WMQConstants.WMQ_PORT, port);
|
||||
connectionFactory.setStringProperty(WMQConstants.WMQ_CHANNEL, channel);
|
||||
connectionFactory.setStringProperty(WMQConstants.WMQ_QUEUE_MANAGER, queueManager);
|
||||
connectionFactory.setIntProperty(WMQConstants.WMQ_CONNECTION_MODE, WMQConstants.WMQ_CM_CLIENT);
|
||||
connectionFactory.setStringProperty(WMQConstants.USERID, user);
|
||||
connectionFactory.setStringProperty(WMQConstants.PASSWORD, password);
|
||||
|
||||
return connectionFactory.createContext(user, password, JMSContext.AUTO_ACKNOWLEDGE);
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
String kafkaBootstrap = System.getProperty("kafka.bootstrap", "localhost:9092");
|
||||
String kafkaApiKey = System.getProperty("kafka.apiKey", "api-key");
|
||||
String kafkaApiSecret = System.getProperty("kafka.apiSecret", "api-secret");
|
||||
|
||||
String mqHost = System.getProperty("mq.host", "localhost");
|
||||
int mqPort = Integer.parseInt(System.getProperty("mq.port", "1414"));
|
||||
String mqChannel = System.getProperty("mq.channel", "DEV.APP.SVRCONN");
|
||||
String mqQueueManager = System.getProperty("mq.queueManager", "QM1");
|
||||
String mqUser = System.getProperty("mq.user", "app");
|
||||
String mqPassword = System.getProperty("mq.password", "pass");
|
||||
|
||||
try (KafkaProducer<String, String> kafkaProducer = createKafkaConnection(kafkaBootstrap, kafkaApiKey, kafkaApiSecret);
|
||||
JMSContext mqContext = createMqConnection(mqHost, mqPort, mqChannel, mqQueueManager, mqUser, mqPassword)) {
|
||||
System.out.println("Kafka connection created: " + (kafkaProducer != null));
|
||||
System.out.println("IBM MQ connection created: " + (mqContext != null));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -29,11 +29,10 @@
|
||||
<commons-beanutils.version>1.9.3</commons-beanutils.version>
|
||||
<commons-configuration.version>1.6</commons-configuration.version>
|
||||
<cxf.version>4.0.3</cxf.version>
|
||||
<kafka.version>3.7.1</kafka.version>
|
||||
<confluent.version>7.6.1</confluent.version>
|
||||
<ibm.mq.version>9.4.5.0</ibm.mq.version>
|
||||
<javax.jms.version>2.0.1</javax.jms.version>
|
||||
<kafka.clients.version>3.7.0</kafka.clients.version>
|
||||
<confluent.version>7.6.0</confluent.version>
|
||||
<assertj.version>3.24.2</assertj.version>
|
||||
<jakarta.jms.version>3.1.0</jakarta.jms.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
@ -271,6 +270,48 @@
|
||||
<version>${appium-java-client.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.kafka</groupId>
|
||||
<artifactId>kafka-clients</artifactId>
|
||||
<version>${kafka.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.confluent</groupId>
|
||||
<artifactId>kafka-avro-serializer</artifactId>
|
||||
<version>${confluent.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.confluent</groupId>
|
||||
<artifactId>kafka-schema-registry-client</artifactId>
|
||||
<version>${confluent.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.avro</groupId>
|
||||
<artifactId>avro</artifactId>
|
||||
<version>1.11.3</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.ibm.mq</groupId>
|
||||
<artifactId>com.ibm.mq.allclient</artifactId>
|
||||
<version>${ibm.mq.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>javax.jms</groupId>
|
||||
<artifactId>javax.jms-api</artifactId>
|
||||
<version>2.0.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>jakarta.jms</groupId>
|
||||
<artifactId>jakarta.jms-api</artifactId>
|
||||
<version>${jakarta.jms.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Used for Web Services connector -->
|
||||
<dependency>
|
||||
<groupId>org.apache.cxf</groupId>
|
||||
@ -290,44 +331,6 @@
|
||||
<version>${cxf.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Used in IbmMqConnector -->
|
||||
<dependency>
|
||||
<groupId>com.ibm.mq</groupId>
|
||||
<artifactId>com.ibm.mq.allclient</artifactId>
|
||||
<version>${ibm.mq.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.jms</groupId>
|
||||
<artifactId>javax.jms-api</artifactId>
|
||||
<version>${javax.jms.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Kafka dependencies -->
|
||||
<dependency>
|
||||
<groupId>org.apache.kafka</groupId>
|
||||
<artifactId>kafka-clients</artifactId>
|
||||
<version>${kafka.clients.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.confluent</groupId>
|
||||
<artifactId>kafka-avro-serializer</artifactId>
|
||||
<version>${confluent.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.avro</groupId>
|
||||
<artifactId>avro</artifactId>
|
||||
<version>1.11.3</version>
|
||||
</dependency>
|
||||
|
||||
<!-- AssertJ for advanced assertions -->
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<version>${assertj.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Used in Wso2ConnectorServlet -->
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
@ -401,6 +404,23 @@
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-dependency-plugin</artifactId>
|
||||
<version>3.9.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>install</phase>
|
||||
<goals>
|
||||
<goal>copy-dependencies</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<outputDirectory>${project.build.directory}/lib</outputDirectory>
|
||||
<includeScope>runtime</includeScope>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
|
||||
@ -1,502 +1,264 @@
|
||||
package cz.moneta.test.harness.connectors.messaging;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.KeyStore;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import javax.jms.BytesMessage;
|
||||
import javax.jms.JMSConsumer;
|
||||
import javax.jms.JMSContext;
|
||||
import javax.jms.JMSException;
|
||||
import javax.jms.JMSRuntimeException;
|
||||
import javax.jms.JMSProducer;
|
||||
import javax.jms.Message;
|
||||
import javax.jms.Queue;
|
||||
import javax.jms.QueueBrowser;
|
||||
import javax.jms.TextMessage;
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import com.ibm.mq.jms.MQConnectionFactory;
|
||||
import com.ibm.msg.client.wmq.WMQConstants;
|
||||
|
||||
import cz.moneta.test.harness.connectors.Connector;
|
||||
import cz.moneta.test.harness.messaging.MessageContentType;
|
||||
import cz.moneta.test.harness.messaging.MqMessageFormat;
|
||||
import cz.moneta.test.harness.messaging.ReceivedMessage;
|
||||
import cz.moneta.test.harness.messaging.exception.MessagingConnectionException;
|
||||
import cz.moneta.test.harness.messaging.exception.MessagingDestinationException;
|
||||
import cz.moneta.test.harness.messaging.exception.MessagingTimeoutException;
|
||||
import cz.moneta.test.harness.support.messaging.ImqRequest;
|
||||
import cz.moneta.test.harness.exception.MessagingTimeoutException;
|
||||
import cz.moneta.test.harness.messaging.model.MessageContentType;
|
||||
import cz.moneta.test.harness.messaging.model.MqMessageFormat;
|
||||
import cz.moneta.test.harness.messaging.model.ReceivedMessage;
|
||||
|
||||
/**
|
||||
* IBM MQ connector using JMS client with Jakarta JMS API. Supports
|
||||
* multi-instance Queue Manager, SSL/TLS, and multiple message formats.
|
||||
* <p>
|
||||
* Supported formats: - JSON: JMS TextMessage with plain JSON string (default) -
|
||||
* XML: JMS TextMessage with XML string - UTF-8 (CCSID 1208): JMS BytesMessage
|
||||
* with UTF-8 encoding - EBCDIC (CCSID 870): JMS BytesMessage with EBCDIC
|
||||
* IBM-870 encoding
|
||||
*/
|
||||
public class IbmMqConnector implements Connector {
|
||||
|
||||
private static final Logger LOG = LogManager.getLogger(IbmMqConnector.class);
|
||||
private static final Charset EBCDIC_870 = Charset.forName("IBM870");
|
||||
private static final Charset UTF_8 = StandardCharsets.UTF_8;
|
||||
private final MQConnectionFactory connectionFactory;
|
||||
private final String user;
|
||||
private final String password;
|
||||
private final Object contextLock = new Object();
|
||||
private volatile JMSContext jmsContext;
|
||||
|
||||
private static final Charset EBCDIC_870 = Charset.forName("IBM870");
|
||||
private static final Charset UTF_8 = StandardCharsets.UTF_8;
|
||||
public IbmMqConnector(String host,
|
||||
int port,
|
||||
String channel,
|
||||
String queueManager,
|
||||
String user,
|
||||
String password,
|
||||
String keystorePath,
|
||||
String keystorePassword,
|
||||
String cipherSuite) {
|
||||
this.user = user;
|
||||
this.password = password;
|
||||
try {
|
||||
if (keystorePath != null && !keystorePath.isBlank()) {
|
||||
System.setProperty("javax.net.ssl.keyStore", keystorePath);
|
||||
System.setProperty("javax.net.ssl.trustStore", keystorePath);
|
||||
if (keystorePassword != null) {
|
||||
System.setProperty("javax.net.ssl.keyStorePassword", keystorePassword);
|
||||
System.setProperty("javax.net.ssl.trustStorePassword", keystorePassword);
|
||||
}
|
||||
}
|
||||
|
||||
private static final long DEFAULT_POLL_INTERVAL_MS = 100;
|
||||
private static final long DEFAULT_MAX_POLL_INTERVAL_MS = 1000;
|
||||
connectionFactory = new MQConnectionFactory();
|
||||
connectionFactory.setHostName(host);
|
||||
connectionFactory.setPort(port);
|
||||
connectionFactory.setQueueManager(queueManager);
|
||||
connectionFactory.setChannel(channel);
|
||||
connectionFactory.setTransportType(WMQConstants.WMQ_CM_CLIENT);
|
||||
if (user != null && !user.isBlank()) {
|
||||
connectionFactory.setStringProperty(WMQConstants.USERID, user);
|
||||
}
|
||||
if (password != null && !password.isBlank()) {
|
||||
connectionFactory.setStringProperty(WMQConstants.PASSWORD, password);
|
||||
}
|
||||
|
||||
private static final String TLS_VERSION = "TLSv1.2";
|
||||
if (cipherSuite != null && !cipherSuite.isBlank()) {
|
||||
connectionFactory.setSSLCipherSuite(cipherSuite);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Failed to initialize IBM MQ connection factory", e);
|
||||
}
|
||||
}
|
||||
|
||||
private final MQConnectionFactory connectionFactory;
|
||||
private JMSContext jmsContext;
|
||||
private final String queueManager;
|
||||
private final String user;
|
||||
private final String password;
|
||||
public void send(String queueName,
|
||||
String payload,
|
||||
MqMessageFormat format,
|
||||
Map<String, String> properties) {
|
||||
switch (Objects.requireNonNull(format, "format")) {
|
||||
case JSON, XML -> sendTextMessage(queueName, payload, properties);
|
||||
case EBCDIC_870 -> sendBytesMessage(queueName, payload, EBCDIC_870, 870, properties);
|
||||
case UTF8_1208 -> sendBytesMessage(queueName, payload, UTF_8, 1208, properties);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor with multi-instance Queue Manager support.
|
||||
*
|
||||
* @param connectionNameList Connection name list in format
|
||||
* "host1(port1),host2(port2)"
|
||||
* @param channel MQ channel name
|
||||
* @param queueManager Queue Manager name
|
||||
* @param user Username for authentication
|
||||
* @param password Password for authentication
|
||||
* @param keystorePath Path to SSL keystore (can be null for non-SSL)
|
||||
* @param keystorePassword Password for SSL keystore
|
||||
* @param sslCipherSuite SSL cipher suite to use (e.g.,
|
||||
* "TLS_RSA_WITH_AES_256_CBC_SHA256")
|
||||
*/
|
||||
public IbmMqConnector(String connectionNameList, String channel, String queueManager, String user, String password,
|
||||
String keystorePath, String keystorePassword, String sslCipherSuite) {
|
||||
this.queueManager = queueManager;
|
||||
this.user = user;
|
||||
this.password = password;
|
||||
public ReceivedMessage receive(String queueName,
|
||||
String messageSelector,
|
||||
MqMessageFormat expectedFormat,
|
||||
Duration timeout) {
|
||||
JMSContext context = getContext();
|
||||
Queue queue = context.createQueue("queue:///" + queueName);
|
||||
try (JMSConsumer consumer = messageSelector == null || messageSelector.isBlank()
|
||||
? context.createConsumer(queue)
|
||||
: context.createConsumer(queue, messageSelector)) {
|
||||
Message message = consumer.receive(Optional.ofNullable(timeout).orElse(Duration.ofSeconds(30)).toMillis());
|
||||
if (message == null) {
|
||||
throw new MessagingTimeoutException("Timeout waiting for IBM MQ message from queue: " + queueName);
|
||||
}
|
||||
return toReceivedMessage(message, queueName, expectedFormat);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
connectionFactory = new MQConnectionFactory();
|
||||
connectionFactory.setConnectionNameList(connectionNameList);
|
||||
connectionFactory.setQueueManager(queueManager);
|
||||
connectionFactory.setChannel(channel);
|
||||
connectionFactory.setTransportType(WMQConstants.WMQ_CM_CLIENT);
|
||||
if (user != null && !user.isBlank()) {
|
||||
connectionFactory.setStringProperty(WMQConstants.USERID, user);
|
||||
}
|
||||
if (password != null && !password.isBlank()) {
|
||||
connectionFactory.setStringProperty(WMQConstants.PASSWORD, password);
|
||||
}
|
||||
public List<ReceivedMessage> browse(String queueName,
|
||||
Predicate<ReceivedMessage> filter,
|
||||
MqMessageFormat expectedFormat,
|
||||
Duration timeout) {
|
||||
long timeoutMillis = Optional.ofNullable(timeout).orElse(Duration.ofSeconds(30)).toMillis();
|
||||
long deadline = System.currentTimeMillis() + timeoutMillis;
|
||||
long backoff = 100;
|
||||
JMSContext context = getContext();
|
||||
Queue queue = context.createQueue("queue:///" + queueName);
|
||||
|
||||
if (keystorePath != null && !keystorePath.isBlank() && keystorePassword != null
|
||||
&& !keystorePassword.isBlank()) {
|
||||
connectionFactory.setSSLSocketFactory(getSslSocketFactory(keystorePath, keystorePassword));
|
||||
}
|
||||
while (System.currentTimeMillis() < deadline) {
|
||||
List<ReceivedMessage> matched = new ArrayList<>();
|
||||
try (QueueBrowser browser = context.createBrowser(queue)) {
|
||||
Enumeration<?> messages = browser.getEnumeration();
|
||||
while (messages.hasMoreElements()) {
|
||||
Message message = (Message) messages.nextElement();
|
||||
ReceivedMessage receivedMessage = toReceivedMessage(message, queueName, expectedFormat);
|
||||
if (filter == null || filter.test(receivedMessage)) {
|
||||
matched.add(receivedMessage);
|
||||
}
|
||||
}
|
||||
} catch (JMSException e) {
|
||||
throw new IllegalStateException("Failed to browse IBM MQ queue: " + queueName, e);
|
||||
}
|
||||
|
||||
if (sslCipherSuite != null && !sslCipherSuite.isBlank()) {
|
||||
connectionFactory.setSSLCipherSuite(sslCipherSuite);
|
||||
}
|
||||
if (!matched.isEmpty()) {
|
||||
return matched;
|
||||
}
|
||||
|
||||
// Initialize JMS context
|
||||
connect();
|
||||
try {
|
||||
TimeUnit.MILLISECONDS.sleep(backoff);
|
||||
} catch (InterruptedException ignored) {
|
||||
Thread.currentThread().interrupt();
|
||||
break;
|
||||
}
|
||||
backoff = Math.min(backoff * 2, 1000);
|
||||
}
|
||||
throw new MessagingTimeoutException("Timeout waiting for IBM MQ message from queue: " + queueName);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new MessagingConnectionException("Failed to create IBM MQ connection to " + queueManager, e);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void close() {
|
||||
JMSContext context = jmsContext;
|
||||
if (context != null) {
|
||||
context.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to IBM MQ.
|
||||
*/
|
||||
private void connect() {
|
||||
try {
|
||||
this.jmsContext = connectionFactory.createContext(user, password, JMSContext.AUTO_ACKNOWLEDGE);
|
||||
this.jmsContext.start();
|
||||
LOG.info("Connected to IBM MQ: {}", queueManager);
|
||||
} catch (Exception e) {
|
||||
throw new MessagingConnectionException(
|
||||
"Failed to connect to IBM MQ: " + queueManager + " - " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
private void sendTextMessage(String queueName, String payload, Map<String, String> properties) {
|
||||
JMSContext context = getContext();
|
||||
JMSProducer producer = context.createProducer();
|
||||
TextMessage message = context.createTextMessage(payload);
|
||||
applyProperties(message, properties);
|
||||
producer.send(context.createQueue("queue:///" + queueName), message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a JSON or XML message as TextMessage.
|
||||
*/
|
||||
private void sendTextMessage(String queueName, String payload, Map<String, String> properties) {
|
||||
javax.jms.Queue queue = getQueue(queueName);
|
||||
private void sendBytesMessage(String queueName,
|
||||
String payload,
|
||||
Charset charset,
|
||||
int ccsid,
|
||||
Map<String, String> properties) {
|
||||
try {
|
||||
JMSContext context = getContext();
|
||||
JMSProducer producer = context.createProducer();
|
||||
BytesMessage message = context.createBytesMessage();
|
||||
message.writeBytes(Optional.ofNullable(payload).orElse("").getBytes(charset));
|
||||
message.setIntProperty(WMQConstants.JMS_IBM_CHARACTER_SET, ccsid);
|
||||
applyProperties(message, properties);
|
||||
producer.send(context.createQueue("queue:///" + queueName), message);
|
||||
} catch (JMSException e) {
|
||||
throw new IllegalStateException("Failed to send bytes message to IBM MQ queue: " + queueName, e);
|
||||
}
|
||||
}
|
||||
|
||||
TextMessage message = jmsContext.createTextMessage(payload);
|
||||
private void applyProperties(Message message, Map<String, String> properties) {
|
||||
Optional.ofNullable(properties).orElseGet(Collections::emptyMap)
|
||||
.forEach((key, value) -> {
|
||||
try {
|
||||
message.setStringProperty(key, String.valueOf(value));
|
||||
} catch (JMSException e) {
|
||||
throw new IllegalStateException("Failed to set JMS property: " + key, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Set JMS properties
|
||||
if (properties != null) {
|
||||
for (Map.Entry<String, String> entry : properties.entrySet()) {
|
||||
try {
|
||||
if (entry.getKey().equals(ImqRequest.PROP_JMS_CORRELATION_ID)) {
|
||||
message.setJMSCorrelationID(entry.getValue());
|
||||
continue;
|
||||
} else if (entry.getKey().equals(ImqRequest.PROP_JMS_TYPE)) {
|
||||
message.setJMSType(entry.getValue());
|
||||
continue;
|
||||
} else if (entry.getKey().equals(ImqRequest.PROP_JMS_MESSAGE_ID)) {
|
||||
message.setJMSMessageID(entry.getValue());
|
||||
continue;
|
||||
}
|
||||
message.setStringProperty(entry.getKey(), entry.getValue());
|
||||
} catch (JMSException e) {
|
||||
LOG.warn("Failed to set property: {}", entry.getKey(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
private ReceivedMessage toReceivedMessage(Message message, String queueName, MqMessageFormat format) {
|
||||
try {
|
||||
Map<String, String> headers = new LinkedHashMap<>();
|
||||
Enumeration<?> names = message.getPropertyNames();
|
||||
while (names.hasMoreElements()) {
|
||||
String name = String.valueOf(names.nextElement());
|
||||
headers.put(name, String.valueOf(message.getObjectProperty(name)));
|
||||
}
|
||||
|
||||
try {
|
||||
jmsContext.createProducer().send(queue, message);
|
||||
} catch (RuntimeException e) {
|
||||
throw new MessagingDestinationException("Failed to send message to queue: " + queueName, e);
|
||||
}
|
||||
LOG.debug("Sent JSON/XML message to queue: {}", queueName);
|
||||
}
|
||||
String body = decodeMessage(message, format);
|
||||
MessageContentType contentType = resolveContentType(message, format);
|
||||
return new ReceivedMessage(body, contentType, headers, message.getJMSTimestamp(), queueName);
|
||||
} catch (JMSException e) {
|
||||
throw new IllegalStateException("Failed to decode IBM MQ message", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message as BytesMessage with specific encoding and CCSID.
|
||||
*/
|
||||
private void sendBytesMessage(String queueName, String payload, Charset charset, int ccsid,
|
||||
Map<String, String> properties) {
|
||||
javax.jms.Queue queue = getQueue(queueName);
|
||||
private MessageContentType resolveContentType(Message message, MqMessageFormat expectedFormat) {
|
||||
if (message instanceof TextMessage) {
|
||||
return expectedFormat == MqMessageFormat.XML ? MessageContentType.XML : MessageContentType.JSON;
|
||||
}
|
||||
if (expectedFormat == MqMessageFormat.XML) {
|
||||
return MessageContentType.XML;
|
||||
}
|
||||
if (expectedFormat == MqMessageFormat.JSON) {
|
||||
return MessageContentType.JSON;
|
||||
}
|
||||
return MessageContentType.RAW_TEXT;
|
||||
}
|
||||
|
||||
BytesMessage message = jmsContext.createBytesMessage();
|
||||
private String decodeMessage(Message jmsMessage, MqMessageFormat format) {
|
||||
try {
|
||||
if (jmsMessage instanceof TextMessage textMessage) {
|
||||
return textMessage.getText();
|
||||
}
|
||||
|
||||
// Convert payload to bytes using specified charset
|
||||
byte[] bytes = payload.getBytes(charset);
|
||||
try {
|
||||
message.writeBytes(bytes);
|
||||
message.setIntProperty("CCSID", ccsid);
|
||||
} catch (JMSException e) {
|
||||
throw new MessagingDestinationException("Failed to create bytes message", e);
|
||||
}
|
||||
if (jmsMessage instanceof BytesMessage bytesMessage) {
|
||||
byte[] data = new byte[(int) bytesMessage.getBodyLength()];
|
||||
bytesMessage.readBytes(data);
|
||||
Charset charset = switch (format) {
|
||||
case EBCDIC_870 -> EBCDIC_870;
|
||||
case UTF8_1208, JSON, XML -> UTF_8;
|
||||
};
|
||||
return new String(data, charset);
|
||||
}
|
||||
return "";
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Failed to decode JMS message", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Set JMS properties
|
||||
if (properties != null) {
|
||||
for (Map.Entry<String, String> entry : properties.entrySet()) {
|
||||
try {
|
||||
if (entry.getKey().equals(ImqRequest.PROP_JMS_CORRELATION_ID)) {
|
||||
message.setJMSCorrelationID(entry.getValue());
|
||||
continue;
|
||||
} else if (entry.getKey().equals(ImqRequest.PROP_JMS_TYPE)) {
|
||||
message.setJMSType(entry.getValue());
|
||||
continue;
|
||||
} else if (entry.getKey().equals(ImqRequest.PROP_JMS_MESSAGE_ID)) {
|
||||
message.setJMSMessageID(entry.getValue());
|
||||
continue;
|
||||
}
|
||||
message.setStringProperty(entry.getKey(), entry.getValue());
|
||||
} catch (JMSException e) {
|
||||
LOG.warn("Failed to set property: {}", entry.getKey(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
jmsContext.createProducer().send(queue, message);
|
||||
} catch (RuntimeException e) {
|
||||
throw new MessagingDestinationException("Failed to send message to queue: " + queueName, e);
|
||||
}
|
||||
LOG.debug("Sent {} message to queue: {}", charset, queueName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message to a queue with specified format.
|
||||
*
|
||||
* @param queueName Queue name
|
||||
* @param payload Message payload
|
||||
* @param format Message format (JSON, XML, EBCDIC_870, UTF8_1208)
|
||||
* @param properties JMS properties to set
|
||||
*/
|
||||
public void send(String queueName, String payload, MqMessageFormat format, Map<String, String> properties) {
|
||||
switch (format) {
|
||||
case JSON, XML -> sendTextMessage(queueName, payload, properties);
|
||||
case EBCDIC_870 -> sendBytesMessage(queueName, payload, EBCDIC_870, 870, properties);
|
||||
case UTF8_1208 -> sendBytesMessage(queueName, payload, UTF_8, 1208, properties);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive a message from a queue with timeout.
|
||||
*
|
||||
* @param queueName Queue name
|
||||
* @param messageSelector JMS message selector (optional)
|
||||
* @param format Expected message format
|
||||
* @param timeout Timeout duration
|
||||
* @return Received message
|
||||
*/
|
||||
public ReceivedMessage receive(String queueName, String messageSelector, MqMessageFormat format,
|
||||
java.time.Duration timeout) {
|
||||
long timeoutMs = timeout.toMillis();
|
||||
|
||||
javax.jms.Queue queue = getQueue(queueName);
|
||||
JMSConsumer consumer = (messageSelector == null || messageSelector.isBlank() ? jmsContext.createConsumer(queue)
|
||||
: jmsContext.createConsumer(queue, messageSelector));
|
||||
|
||||
AtomicBoolean messageFound = new AtomicBoolean(false);
|
||||
ReceivedMessage received = null;
|
||||
|
||||
long pollInterval = DEFAULT_POLL_INTERVAL_MS;
|
||||
long remainingTime = timeoutMs;
|
||||
|
||||
try {
|
||||
while (remainingTime > 0 && !messageFound.get()) {
|
||||
Message message = consumer.receive(remainingTime);
|
||||
|
||||
if (message != null) {
|
||||
received = decodeMessage(message, queueName, format);
|
||||
messageFound.set(true);
|
||||
} else {
|
||||
// Exponential backoff
|
||||
pollInterval = Math.min(pollInterval * 2, DEFAULT_MAX_POLL_INTERVAL_MS);
|
||||
remainingTime -= pollInterval;
|
||||
}
|
||||
}
|
||||
|
||||
if (received == null) {
|
||||
throw new MessagingTimeoutException("No message matching filter found on queue '" + queueName
|
||||
+ "' within " + timeout.toMillis() + "ms");
|
||||
}
|
||||
|
||||
return received;
|
||||
|
||||
} catch (MessagingTimeoutException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new MessagingDestinationException("Failed to receive message from queue: " + queueName, e);
|
||||
} finally {
|
||||
try {
|
||||
consumer.close();
|
||||
} catch (JMSRuntimeException e) {
|
||||
LOG.warn("Failed to close consumer", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Browse a queue (non-destructive read).
|
||||
*
|
||||
* @param queueName Queue name
|
||||
* @param messageSelector JMS message selector (optional)
|
||||
* @param format Expected message format
|
||||
* @param maxMessages Maximum number of messages to browse
|
||||
* @return List of received messages
|
||||
*/
|
||||
public List<ReceivedMessage> browse(String queueName, String messageSelector, MqMessageFormat format,
|
||||
int maxMessages) {
|
||||
List<ReceivedMessage> messages = new ArrayList<>();
|
||||
javax.jms.Queue queue = getQueue(queueName);
|
||||
|
||||
try (javax.jms.QueueBrowser browser = (messageSelector == null || messageSelector.isBlank())
|
||||
? jmsContext.createBrowser(queue)
|
||||
: jmsContext.createBrowser(queue, messageSelector)) {
|
||||
|
||||
Enumeration<?> enumeration = browser.getEnumeration();
|
||||
int count = 0;
|
||||
|
||||
while (enumeration.hasMoreElements() && count < maxMessages) {
|
||||
Message message = (Message) enumeration.nextElement();
|
||||
if (message != null) {
|
||||
ReceivedMessage received = decodeMessage(message, queueName, format);
|
||||
messages.add(received);
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return messages;
|
||||
} catch (JMSException e) {
|
||||
throw new MessagingDestinationException("Failed to browse queue: " + queueName, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a JMS message to ReceivedMessage.
|
||||
*/
|
||||
private ReceivedMessage decodeMessage(Message jmsMessage, String queueName, MqMessageFormat format) {
|
||||
long timestamp;
|
||||
try {
|
||||
timestamp = jmsMessage.getJMSTimestamp();
|
||||
} catch (JMSException e) {
|
||||
timestamp = System.currentTimeMillis();
|
||||
}
|
||||
if (timestamp == 0) {
|
||||
timestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
String body;
|
||||
MessageContentType contentType;
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
|
||||
// Extract JMS properties as headers
|
||||
extractJmsProperties(jmsMessage, headers);
|
||||
|
||||
if (jmsMessage instanceof TextMessage textMessage) {
|
||||
try {
|
||||
body = textMessage.getText();
|
||||
} catch (JMSException e) {
|
||||
throw new RuntimeException("Failed to read text message body", e);
|
||||
}
|
||||
contentType = switch (format) {
|
||||
case XML -> MessageContentType.XML;
|
||||
default -> MessageContentType.JSON;
|
||||
};
|
||||
} else if (jmsMessage instanceof BytesMessage bytesMessage) {
|
||||
int ccsid;
|
||||
try {
|
||||
ccsid = bytesMessage.getIntProperty("CCSID");
|
||||
} catch (JMSException e) {
|
||||
ccsid = 1208; // default UTF-8
|
||||
}
|
||||
body = decodeBytesMessage(bytesMessage, ccsid);
|
||||
contentType = MessageContentType.RAW_TEXT;
|
||||
} else {
|
||||
try {
|
||||
throw new IllegalArgumentException("Unsupported message type: " + jmsMessage.getJMSType());
|
||||
} catch (JMSException e) {
|
||||
throw new IllegalArgumentException("Unsupported message type", e);
|
||||
}
|
||||
}
|
||||
|
||||
return new ReceivedMessage(body, contentType, headers, timestamp, queueName, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode BytesMessage body based on CCSID.
|
||||
*/
|
||||
private String decodeBytesMessage(BytesMessage bytesMessage, int ccsid) {
|
||||
try {
|
||||
long bodyLength;
|
||||
try {
|
||||
bodyLength = bytesMessage.getBodyLength();
|
||||
} catch (JMSException e) {
|
||||
throw new RuntimeException("Failed to get message body length", e);
|
||||
}
|
||||
byte[] data = new byte[(int) bodyLength];
|
||||
bytesMessage.readBytes(data);
|
||||
|
||||
Charset charset = switch (ccsid) {
|
||||
case 870 -> EBCDIC_870;
|
||||
case 1208 -> UTF_8;
|
||||
default -> UTF_8;
|
||||
};
|
||||
|
||||
return new String(data, charset);
|
||||
} catch (JMSException e) {
|
||||
throw new RuntimeException("Failed to read BytesMessage body", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract JMS properties as headers.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private void extractJmsProperties(Message message, Map<String, String> headers) {
|
||||
try {
|
||||
// Common JMS headers
|
||||
headers.put("JMSMessageID", message.getJMSMessageID());
|
||||
try {
|
||||
headers.put("JMSType", message.getJMSType() != null ? message.getJMSType() : "");
|
||||
} catch (JMSException e) {
|
||||
headers.put("JMSType", "");
|
||||
}
|
||||
try {
|
||||
headers.put("JMSDestination",
|
||||
message.getJMSDestination() != null ? message.getJMSDestination().toString() : "");
|
||||
} catch (JMSException e) {
|
||||
headers.put("JMSDestination", "");
|
||||
}
|
||||
try {
|
||||
headers.put("JMSDeliveryMode", String.valueOf(message.getJMSDeliveryMode()));
|
||||
} catch (JMSException e) {
|
||||
headers.put("JMSDeliveryMode", "");
|
||||
}
|
||||
try {
|
||||
headers.put("JMSPriority", String.valueOf(message.getJMSPriority()));
|
||||
} catch (JMSException e) {
|
||||
headers.put("JMSPriority", "");
|
||||
}
|
||||
try {
|
||||
headers.put("JMSTimestamp", String.valueOf(message.getJMSTimestamp()));
|
||||
} catch (JMSException e) {
|
||||
headers.put("JMSTimestamp", "");
|
||||
}
|
||||
|
||||
// Extract custom properties
|
||||
Enumeration<String> propertyNames = (Enumeration<String>) message.getPropertyNames();
|
||||
while (propertyNames.hasMoreElements()) {
|
||||
String propName = propertyNames.nextElement();
|
||||
Object propValue = message.getObjectProperty(propName);
|
||||
if (propValue != null) {
|
||||
headers.put(propName, propValue.toString());
|
||||
}
|
||||
}
|
||||
} catch (JMSException e) {
|
||||
LOG.warn("Failed to extract JMS properties", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Queue object from queue name.
|
||||
*/
|
||||
private javax.jms.Queue getQueue(String queueName) {
|
||||
return jmsContext.createQueue(queueName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (jmsContext != null) {
|
||||
try {
|
||||
jmsContext.close();
|
||||
LOG.info("Closed connection to IBM MQ: {}", queueManager);
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to close IBM MQ connection", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private SSLSocketFactory getSslSocketFactory(String keystorePath, String keystorePassword) throws Exception {
|
||||
// --- keystore ---
|
||||
KeyStore keyStore = KeyStore.getInstance("JKS");
|
||||
InputStream ksStream = IbmMqConnector.class.getClassLoader().getResourceAsStream(keystorePath);
|
||||
|
||||
if (ksStream == null) {
|
||||
throw new IllegalStateException("Keystore not found: " + keystorePath);
|
||||
}
|
||||
keyStore.load(ksStream, keystorePassword.toCharArray());
|
||||
|
||||
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||
kmf.init(keyStore, keystorePassword.toCharArray());
|
||||
|
||||
// --- truststore ---
|
||||
KeyStore trustStore = KeyStore.getInstance("JKS");
|
||||
InputStream tsStream = IbmMqConnector.class.getClassLoader().getResourceAsStream(keystorePath);
|
||||
|
||||
if (tsStream == null) {
|
||||
throw new IllegalStateException("Truststore not found: " + keystorePath);
|
||||
}
|
||||
trustStore.load(tsStream, keystorePassword.toCharArray());
|
||||
|
||||
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
||||
tmf.init(trustStore);
|
||||
|
||||
// --- SSL context ---
|
||||
SSLContext sslContext = SSLContext.getInstance(TLS_VERSION);
|
||||
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
|
||||
|
||||
return sslContext.getSocketFactory();
|
||||
}
|
||||
private JMSContext getContext() {
|
||||
JMSContext current = jmsContext;
|
||||
if (current == null) {
|
||||
synchronized (contextLock) {
|
||||
current = jmsContext;
|
||||
if (current == null) {
|
||||
jmsContext = current = connectionFactory.createContext(user, password, JMSContext.AUTO_ACKNOWLEDGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
return current;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,115 +0,0 @@
|
||||
package cz.moneta.test.harness.connectors.messaging;
|
||||
|
||||
import com.google.gson.*;
|
||||
import org.apache.avro.Schema;
|
||||
import org.apache.avro.generic.GenericData;
|
||||
import org.apache.avro.generic.GenericDatumReader;
|
||||
import org.apache.avro.generic.GenericDatumWriter;
|
||||
import org.apache.avro.generic.GenericRecord;
|
||||
import org.apache.avro.io.DatumReader;
|
||||
import org.apache.avro.io.DatumWriter;
|
||||
import org.apache.avro.io.Decoder;
|
||||
import org.apache.avro.io.JsonDecoder;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class JsonToAvroConverter {
|
||||
|
||||
protected static GenericRecord processJson(String json, Schema schema) throws IllegalArgumentException, JsonSchemaException {
|
||||
GenericRecord result = (GenericRecord) jsonElementToAvro(JsonParser.parseString(json), schema);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Object jsonElementToAvro(JsonElement element, Schema schema) throws JsonSchemaException {
|
||||
boolean schemaIsNullable = isNullable(schema);
|
||||
if (schemaIsNullable) {
|
||||
schema = typeFromNullable(schema);
|
||||
}
|
||||
if (element == null || element.isJsonNull()) {
|
||||
if (!schemaIsNullable) {
|
||||
throw new JsonSchemaException("The element is not nullable in Avro schema.");
|
||||
}
|
||||
return null;
|
||||
} else if (element.isJsonObject()) {
|
||||
if (schema.getType() != Schema.Type.RECORD) {
|
||||
throw new JsonSchemaException(
|
||||
String.format("The element `%s` doesn't match Avro type RECORD", element));
|
||||
}
|
||||
return jsonObjectToAvro(element.getAsJsonObject(), schema);
|
||||
} else if (element.isJsonArray()) {
|
||||
if (schema.getType() != Schema.Type.ARRAY) {
|
||||
throw new JsonSchemaException(
|
||||
String.format("The element `%s` doesn't match Avro type ARRAY", element));
|
||||
}
|
||||
JsonArray jsonArray = element.getAsJsonArray();
|
||||
List<Object> avroArray = new ArrayList<>(jsonArray.size());
|
||||
for (JsonElement e : element.getAsJsonArray()) {
|
||||
avroArray.add(jsonElementToAvro(e, schema.getElementType()));
|
||||
}
|
||||
return avroArray;
|
||||
} else if (element.isJsonPrimitive()) {
|
||||
return jsonPrimitiveToAvro(element.getAsJsonPrimitive(), schema);
|
||||
} else {
|
||||
throw new JsonSchemaException(
|
||||
String.format(
|
||||
"The Json element `%s` is of an unknown class %s", element, element.getClass()));
|
||||
}
|
||||
}
|
||||
|
||||
private static GenericRecord jsonObjectToAvro(JsonObject jsonObject, Schema schema) throws JsonSchemaException {
|
||||
GenericRecord avroRecord = new GenericData.Record(schema);
|
||||
|
||||
for (Schema.Field field : schema.getFields()) {
|
||||
avroRecord.put(field.name(), jsonElementToAvro(jsonObject.get(field.name()), field.schema()));
|
||||
}
|
||||
return avroRecord;
|
||||
}
|
||||
|
||||
private static boolean isNullable(Schema type) {
|
||||
return type.getType() == Schema.Type.NULL
|
||||
|| type.getType() == Schema.Type.UNION
|
||||
&& type.getTypes().stream().anyMatch(JsonToAvroConverter::isNullable);
|
||||
}
|
||||
|
||||
private static Schema typeFromNullable(Schema type) {
|
||||
if (type.getType() == Schema.Type.UNION) {
|
||||
return typeFromNullable(
|
||||
type.getTypes().stream()
|
||||
.filter(t -> t.getType() != Schema.Type.NULL)
|
||||
.findFirst()
|
||||
.orElseThrow(
|
||||
() ->
|
||||
new IllegalStateException(
|
||||
String.format("Type `%s` should have a non null subtype", type))));
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
private static Object jsonPrimitiveToAvro(JsonPrimitive primitive, Schema schema){
|
||||
switch (schema.getType()) {
|
||||
case NULL:
|
||||
return null;
|
||||
case STRING:
|
||||
return primitive.getAsString();
|
||||
case BOOLEAN:
|
||||
return primitive.getAsBoolean();
|
||||
case INT:
|
||||
return primitive.getAsInt();
|
||||
case LONG:
|
||||
return primitive.getAsLong();
|
||||
case FLOAT:
|
||||
return primitive.getAsFloat();
|
||||
case DOUBLE:
|
||||
return primitive.getAsDouble();
|
||||
default:
|
||||
return primitive.getAsString();
|
||||
}
|
||||
}
|
||||
|
||||
private static class JsonSchemaException extends Exception {
|
||||
JsonSchemaException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,16 +1,18 @@
|
||||
package cz.moneta.test.harness.connectors.messaging;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import cz.moneta.test.harness.connectors.Connector;
|
||||
import cz.moneta.test.harness.exception.MessagingTimeoutException;
|
||||
import cz.moneta.test.harness.messaging.model.MessageContentType;
|
||||
import cz.moneta.test.harness.messaging.model.ReceivedMessage;
|
||||
import io.confluent.kafka.schemaregistry.client.CachedSchemaRegistryClient;
|
||||
import io.confluent.kafka.serializers.AbstractKafkaSchemaSerDeConfig;
|
||||
import io.confluent.kafka.serializers.KafkaAvroDeserializer;
|
||||
import io.confluent.kafka.serializers.KafkaAvroDeserializerConfig;
|
||||
import io.confluent.kafka.serializers.KafkaAvroSerializer;
|
||||
import org.apache.avro.Schema;
|
||||
import org.apache.avro.generic.GenericData;
|
||||
import org.apache.avro.generic.GenericRecord;
|
||||
import org.apache.kafka.clients.consumer.ConsumerConfig;
|
||||
import org.apache.kafka.clients.consumer.ConsumerRecord;
|
||||
@ -20,329 +22,332 @@ import org.apache.kafka.clients.producer.KafkaProducer;
|
||||
import org.apache.kafka.clients.producer.ProducerConfig;
|
||||
import org.apache.kafka.clients.producer.ProducerRecord;
|
||||
import org.apache.kafka.common.TopicPartition;
|
||||
import org.apache.kafka.common.header.Headers;
|
||||
import org.apache.kafka.common.header.Header;
|
||||
import org.apache.kafka.common.serialization.StringDeserializer;
|
||||
import org.apache.kafka.common.serialization.StringSerializer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import cz.moneta.test.harness.messaging.exception.MessagingConnectionException;
|
||||
import cz.moneta.test.harness.messaging.exception.MessagingDestinationException;
|
||||
import cz.moneta.test.harness.messaging.exception.MessagingSchemaException;
|
||||
import cz.moneta.test.harness.messaging.exception.MessagingTimeoutException;
|
||||
import cz.moneta.test.harness.support.messaging.kafka.MessageContentType;
|
||||
import cz.moneta.test.harness.support.messaging.kafka.ReceivedMessage;
|
||||
import io.confluent.kafka.schemaregistry.client.CachedSchemaRegistryClient;
|
||||
import io.confluent.kafka.serializers.KafkaAvroDeserializer;
|
||||
import io.confluent.kafka.serializers.KafkaAvroSerializer;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Properties;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Kafka connector for sending and receiving messages.
|
||||
* Supports Avro serialization with Confluent Schema Registry.
|
||||
* <p>
|
||||
* Uses manual partition assignment (no consumer group) for test isolation.
|
||||
* Each receive operation creates a new consumer to avoid offset sharing.
|
||||
*/
|
||||
public class KafkaConnector implements cz.moneta.test.harness.connectors.Connector {
|
||||
public class KafkaConnector implements Connector {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(KafkaConnector.class);
|
||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||
|
||||
private final Properties producerConfig;
|
||||
private final Properties consumerConfig;
|
||||
private final String schemaRegistryUrl;
|
||||
private final Properties producerProps = new Properties();
|
||||
private final Properties consumerProps = new Properties();
|
||||
private final CachedSchemaRegistryClient schemaRegistryClient;
|
||||
private KafkaProducer<String, GenericRecord> producer;
|
||||
private volatile KafkaProducer<String, GenericRecord> producer;
|
||||
private final Object producerLock = new Object();
|
||||
|
||||
/**
|
||||
* Creates a new KafkaConnector.
|
||||
*
|
||||
* @param bootstrapServers Kafka bootstrap servers
|
||||
* @param apiKey Kafka API key
|
||||
* @param apiSecret Kafka API secret
|
||||
* @param schemaRegistryUrl Schema Registry URL
|
||||
* @param schemaRegistryApiKey Schema Registry API key
|
||||
* @param schemaRegistryApiSecret Schema Registry API secret
|
||||
*/
|
||||
public KafkaConnector(String bootstrapServers,
|
||||
String apiKey,
|
||||
String apiSecret,
|
||||
String schemaRegistryUrl,
|
||||
String schemaRegistryApiKey,
|
||||
String schemaRegistryApiSecret) {
|
||||
this.schemaRegistryUrl = schemaRegistryUrl;
|
||||
this.schemaRegistryClient = new CachedSchemaRegistryClient(
|
||||
Collections.singletonList(schemaRegistryUrl), 100, new HashMap<>());
|
||||
String jaasConfig = String.format("org.apache.kafka.common.security.plain.PlainLoginModule required username=\"%s\" password=\"%s\";",
|
||||
apiKey,
|
||||
apiSecret);
|
||||
String schemaRegistryAuth = schemaRegistryApiKey + ":" + schemaRegistryApiSecret;
|
||||
|
||||
this.producerConfig = createProducerConfig(bootstrapServers, apiKey, apiSecret);
|
||||
this.consumerConfig = createConsumerConfig(bootstrapServers, schemaRegistryApiKey, schemaRegistryApiSecret);
|
||||
producerProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
|
||||
producerProps.put("security.protocol", "SASL_SSL");
|
||||
producerProps.put("sasl.mechanism", "PLAIN");
|
||||
producerProps.put("sasl.jaas.config", jaasConfig);
|
||||
producerProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
|
||||
producerProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, KafkaAvroSerializer.class.getName());
|
||||
producerProps.put(AbstractKafkaSchemaSerDeConfig.SCHEMA_REGISTRY_URL_CONFIG, schemaRegistryUrl);
|
||||
producerProps.put(AbstractKafkaSchemaSerDeConfig.BASIC_AUTH_CREDENTIALS_SOURCE, "USER_INFO");
|
||||
producerProps.put(AbstractKafkaSchemaSerDeConfig.USER_INFO_CONFIG, schemaRegistryAuth);
|
||||
producerProps.put("auto.register.schemas", "false");
|
||||
|
||||
consumerProps.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
|
||||
consumerProps.put("security.protocol", "SASL_SSL");
|
||||
consumerProps.put("sasl.mechanism", "PLAIN");
|
||||
consumerProps.put("sasl.jaas.config", jaasConfig);
|
||||
consumerProps.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
|
||||
consumerProps.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, KafkaAvroDeserializer.class.getName());
|
||||
consumerProps.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "latest");
|
||||
consumerProps.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false");
|
||||
consumerProps.put(KafkaAvroDeserializerConfig.SPECIFIC_AVRO_READER_CONFIG, "false");
|
||||
consumerProps.put(AbstractKafkaSchemaSerDeConfig.SCHEMA_REGISTRY_URL_CONFIG, schemaRegistryUrl);
|
||||
consumerProps.put(AbstractKafkaSchemaSerDeConfig.BASIC_AUTH_CREDENTIALS_SOURCE, "USER_INFO");
|
||||
consumerProps.put(AbstractKafkaSchemaSerDeConfig.USER_INFO_CONFIG, schemaRegistryAuth);
|
||||
|
||||
this.schemaRegistryClient = new CachedSchemaRegistryClient(schemaRegistryUrl, 128);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates producer configuration.
|
||||
*/
|
||||
private Properties createProducerConfig(String bootstrapServers, String apiKey, String apiSecret) {
|
||||
Properties config = new Properties();
|
||||
config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
|
||||
config.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
|
||||
config.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, KafkaAvroSerializer.class);
|
||||
config.put("schema.registry.url", schemaRegistryUrl);
|
||||
config.put(ProducerConfig.ACKS_CONFIG, "all");
|
||||
config.put(ProducerConfig.LINGER_MS_CONFIG, 1);
|
||||
config.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384);
|
||||
|
||||
// SASL/PLAIN authentication
|
||||
// config.put("security.protocol", "SASL_SSL");
|
||||
// config.put("sasl.mechanism", "PLAIN");
|
||||
// config.put("sasl.jaas.config",
|
||||
// "org.apache.kafka.common.security.plain.PlainLoginModule required " +
|
||||
// "username=\"" + apiKey + "\" password=\"" + apiSecret + "\";");
|
||||
|
||||
// SSL configuration
|
||||
// config.put("ssl.endpoint.identification.algorithm", "https");
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates consumer configuration.
|
||||
*/
|
||||
private Properties createConsumerConfig(String bootstrapServers, String apiKey, String apiSecret) {
|
||||
Properties config = new Properties();
|
||||
config.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
|
||||
config.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
|
||||
config.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, KafkaAvroDeserializer.class);
|
||||
config.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "none");
|
||||
config.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
|
||||
|
||||
// SASL/PLAIN authentication
|
||||
config.put("security.protocol", "SASL_SSL");
|
||||
config.put("sasl.mechanism", "PLAIN");
|
||||
config.put("sasl.jaas.config",
|
||||
"org.apache.kafka.common.security.plain.PlainLoginModule required " +
|
||||
"username=\"" + apiKey + "\" password=\"" + apiSecret + "\";");
|
||||
|
||||
// Schema Registry for deserialization
|
||||
config.put("schema.registry.url", schemaRegistryUrl);
|
||||
config.put("specific.avro.reader", false);
|
||||
|
||||
// SSL configuration
|
||||
config.put("ssl.endpoint.identification.algorithm", "https");
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message to a Kafka topic.
|
||||
*/
|
||||
public void send(String topic, String key, String jsonPayload, Map<String, String> headers) {
|
||||
Objects.requireNonNull(topic, "topic");
|
||||
Schema schema = getSchemaForTopic(topic);
|
||||
GenericRecord record = jsonToAvro(jsonPayload, schema);
|
||||
ProducerRecord<String, GenericRecord> producerRecord = new ProducerRecord<>(topic, key, record);
|
||||
Optional.ofNullable(headers).orElseGet(HashMap::new)
|
||||
.forEach((headerKey, headerValue) -> producerRecord.headers()
|
||||
.add(headerKey, String.valueOf(headerValue).getBytes(StandardCharsets.UTF_8)));
|
||||
|
||||
try {
|
||||
org.apache.avro.Schema schema = getSchemaForTopic(topic);
|
||||
GenericRecord record = jsonToAvro(jsonPayload, schema);
|
||||
|
||||
ProducerRecord<String, GenericRecord> producerRecord =
|
||||
new ProducerRecord<>(topic, key, record);
|
||||
|
||||
// Add headers
|
||||
if (headers != null) {
|
||||
Headers kafkaHeaders = producerRecord.headers();
|
||||
headers.forEach((k, v) ->
|
||||
kafkaHeaders.add(k, v.getBytes(StandardCharsets.UTF_8)));
|
||||
}
|
||||
|
||||
// Send and wait for confirmation
|
||||
getProducer().send(producerRecord, (metadata, exception) -> {
|
||||
if (exception != null) {
|
||||
LOG.error("Failed to send message", exception);
|
||||
} else {
|
||||
LOG.debug("Message sent to topic {} partition {} offset {}",
|
||||
metadata.topic(), metadata.partition(), metadata.offset());
|
||||
}
|
||||
}).get(10, java.util.concurrent.TimeUnit.SECONDS);
|
||||
|
||||
} catch (ExecutionException e) {
|
||||
throw new MessagingConnectionException(
|
||||
"Failed to send message to topic " + topic, e.getCause());
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new MessagingConnectionException(
|
||||
"Interrupted while sending message to topic " + topic, e);
|
||||
} catch (MessagingSchemaException e) {
|
||||
throw e;
|
||||
getProducer().send(producerRecord).get(30, TimeUnit.SECONDS);
|
||||
} catch (Exception e) {
|
||||
throw new MessagingSchemaException(
|
||||
"Failed to convert JSON to Avro for topic " + topic, e);
|
||||
throw new IllegalStateException("Failed to send Kafka message to topic: " + topic, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives a message from a Kafka topic matching the filter.
|
||||
*/
|
||||
public List<ReceivedMessage> receive(String topic,
|
||||
Predicate<ReceivedMessage> filter,
|
||||
Duration timeout) {
|
||||
KafkaConsumer<String, GenericRecord> consumer = null;
|
||||
try {
|
||||
consumer = new KafkaConsumer<>(consumerConfig);
|
||||
long timeoutMillis = Optional.ofNullable(timeout).orElse(Duration.ofSeconds(30)).toMillis();
|
||||
long deadline = System.currentTimeMillis() + timeoutMillis;
|
||||
long backoff = 100;
|
||||
|
||||
// Get partitions for the topic
|
||||
List<TopicPartition> partitions = getPartitionsForTopic(consumer, topic);
|
||||
if (partitions.isEmpty()) {
|
||||
throw new MessagingDestinationException(
|
||||
"Topic '" + topic + "' does not exist or has no partitions");
|
||||
}
|
||||
|
||||
// Assign partitions and seek to end
|
||||
try (KafkaConsumer<String, Object> consumer = createConsumer()) {
|
||||
List<TopicPartition> partitions = consumer.partitionsFor(topic).stream()
|
||||
.map(info -> new TopicPartition(topic, info.partition()))
|
||||
.toList();
|
||||
consumer.assign(partitions);
|
||||
// consumer.seekToBeginning(partitions);
|
||||
consumer.seekToBeginning(partitions);
|
||||
consumer.seekToEnd(partitions);
|
||||
|
||||
// Poll loop with exponential backoff
|
||||
long startTime = System.currentTimeMillis();
|
||||
Duration pollInterval = Duration.ofMillis(100);
|
||||
Duration maxPollInterval = Duration.ofSeconds(1);
|
||||
|
||||
while (Duration.ofMillis(System.currentTimeMillis() - startTime).compareTo(timeout) < 0) {
|
||||
ConsumerRecords<String, GenericRecord> records = consumer.poll(pollInterval);
|
||||
|
||||
for (ConsumerRecord<String, GenericRecord> record : records) {
|
||||
ReceivedMessage message = convertToReceivedMessage(record, topic);
|
||||
if (filter.test(message)) {
|
||||
LOG.debug("Found matching message on topic {} partition {} offset {}",
|
||||
record.topic(), record.partition(), record.offset());
|
||||
return Collections.singletonList(message);
|
||||
while (System.currentTimeMillis() < deadline) {
|
||||
ConsumerRecords<String, Object> records = consumer.poll(Duration.ofMillis(100));
|
||||
if (!records.isEmpty()) {
|
||||
for (ConsumerRecord<String, Object> record : records) {
|
||||
ReceivedMessage message = toReceivedMessage(record);
|
||||
if (filter == null || filter.test(message)) {
|
||||
return List.of(message);
|
||||
}
|
||||
}
|
||||
backoff = 100;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Exponential backoff
|
||||
pollInterval = Duration.ofMillis(
|
||||
Math.min(pollInterval.toMillis() * 2, maxPollInterval.toMillis()));
|
||||
}
|
||||
|
||||
throw new MessagingTimeoutException(
|
||||
"No message matching filter found on topic '" + topic + "' within " + timeout);
|
||||
|
||||
} finally {
|
||||
if (consumer != null) {
|
||||
consumer.close();
|
||||
TimeUnit.MILLISECONDS.sleep(backoff);
|
||||
backoff = Math.min(backoff * 2, 1000);
|
||||
}
|
||||
} catch (MessagingTimeoutException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Failed to receive Kafka message from topic: " + topic, e);
|
||||
}
|
||||
throw new MessagingTimeoutException("Timeout waiting for Kafka message from topic: " + topic);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets partitions for a topic.
|
||||
*/
|
||||
private List<TopicPartition> getPartitionsForTopic(KafkaConsumer<?, ?> consumer, String topic) {
|
||||
List<TopicPartition> partitions = new ArrayList<>();
|
||||
List<org.apache.kafka.common.PartitionInfo> partitionInfos = consumer.partitionsFor(topic);
|
||||
if (partitionInfos != null) {
|
||||
for (org.apache.kafka.common.PartitionInfo partitionInfo : partitionInfos) {
|
||||
partitions.add(new TopicPartition(topic, partitionInfo.partition()));
|
||||
}
|
||||
}
|
||||
return partitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves current offsets for a topic.
|
||||
*/
|
||||
public Map<TopicPartition, Long> saveOffsets(String topic) {
|
||||
return new HashMap<>();
|
||||
try (KafkaConsumer<String, Object> consumer = createConsumer()) {
|
||||
List<TopicPartition> partitions = consumer.partitionsFor(topic).stream()
|
||||
.map(info -> new TopicPartition(topic, info.partition()))
|
||||
.toList();
|
||||
consumer.assign(partitions);
|
||||
consumer.seekToEnd(partitions);
|
||||
Map<TopicPartition, Long> offsets = new HashMap<>();
|
||||
partitions.forEach(partition -> offsets.put(partition, consumer.position(partition)));
|
||||
return offsets;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the connector and releases resources.
|
||||
*/
|
||||
@Override
|
||||
public void close() {
|
||||
if (producer != null) {
|
||||
producer.close();
|
||||
KafkaProducer<String, GenericRecord> current = producer;
|
||||
if (current != null) {
|
||||
current.close(Duration.ofSeconds(5));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or creates the producer (singleton, thread-safe).
|
||||
*/
|
||||
private KafkaProducer<String, GenericRecord> getProducer() {
|
||||
if (producer == null) {
|
||||
synchronized (this) {
|
||||
if (producer == null) {
|
||||
producer = new KafkaProducer<>(producerConfig);
|
||||
KafkaProducer<String, GenericRecord> current = producer;
|
||||
if (current == null) {
|
||||
synchronized (producerLock) {
|
||||
current = producer;
|
||||
if (current == null) {
|
||||
producer = current = new KafkaProducer<>(producerProps);
|
||||
}
|
||||
}
|
||||
}
|
||||
return producer;
|
||||
return current;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves schema from Schema Registry based on topic name.
|
||||
*/
|
||||
private org.apache.avro.Schema getSchemaForTopic(String topic) {
|
||||
private KafkaConsumer<String, Object> createConsumer() {
|
||||
Properties properties = new Properties();
|
||||
properties.putAll(consumerProps);
|
||||
properties.put(ConsumerConfig.GROUP_ID_CONFIG, "harness-" + UUID.randomUUID());
|
||||
return new KafkaConsumer<>(properties);
|
||||
}
|
||||
|
||||
private ReceivedMessage toReceivedMessage(ConsumerRecord<String, Object> record) {
|
||||
String body = convertValueToJson(record.value());
|
||||
Map<String, String> headers = new LinkedHashMap<>();
|
||||
for (Header header : record.headers()) {
|
||||
headers.put(header.key(), new String(header.value(), StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
return new ReceivedMessage(body,
|
||||
MessageContentType.JSON,
|
||||
headers,
|
||||
record.timestamp(),
|
||||
record.topic());
|
||||
}
|
||||
|
||||
private String convertValueToJson(Object value) {
|
||||
try {
|
||||
if (value instanceof GenericRecord genericRecord) {
|
||||
return avroToJson(genericRecord);
|
||||
}
|
||||
if (value instanceof CharSequence) {
|
||||
return value.toString();
|
||||
}
|
||||
return OBJECT_MAPPER.writeValueAsString(value);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Failed to convert Kafka payload to JSON", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Schema getSchemaForTopic(String topic) {
|
||||
String subject = topic + "-value";
|
||||
try {
|
||||
io.confluent.kafka.schemaregistry.client.SchemaMetadata metadata =
|
||||
schemaRegistryClient.getLatestSchemaMetadata(subject);
|
||||
return new org.apache.avro.Schema.Parser().parse(metadata.getSchema());
|
||||
// Get all versions and use the latest one
|
||||
java.util.List<Integer> versions = schemaRegistryClient.getAllVersions(subject);
|
||||
int latestVersion = versions.get(versions.size() - 1);
|
||||
io.confluent.kafka.schemaregistry.client.rest.entities.Schema confluentSchema =
|
||||
schemaRegistryClient.getByVersion(subject, latestVersion, false);
|
||||
String schemaString = confluentSchema.getSchema();
|
||||
return new Schema.Parser().parse(schemaString);
|
||||
} catch (Exception e) {
|
||||
if (e.getMessage() != null && e.getMessage().contains("404")) {
|
||||
throw new MessagingSchemaException(
|
||||
"Schema not found for subject '" + subject + "' in Schema Registry. " +
|
||||
"Make sure the topic exists and schema is registered.");
|
||||
}
|
||||
throw new MessagingSchemaException(
|
||||
"Failed to retrieve schema for topic '" + topic + "'", e);
|
||||
throw new IllegalStateException("Failed to get schema for subject: " + subject, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts JSON string to Avro GenericRecord.
|
||||
*/
|
||||
private GenericRecord jsonToAvro(String json, org.apache.avro.Schema schema) {
|
||||
try {
|
||||
GenericRecord genericRecord = JsonToAvroConverter.processJson(json, schema);
|
||||
return genericRecord;
|
||||
} catch (Exception e) {
|
||||
throw new MessagingSchemaException("Failed to convert JSON to Avro: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts Kafka ConsumerRecord to ReceivedMessage.
|
||||
*/
|
||||
private ReceivedMessage convertToReceivedMessage(ConsumerRecord<String, GenericRecord> record, String topic) {
|
||||
try {
|
||||
String jsonBody = avroToJson(record.value());
|
||||
|
||||
Map<String, String> headers = new HashMap<>();
|
||||
Headers kafkaHeaders = record.headers();
|
||||
for (org.apache.kafka.common.header.Header header : kafkaHeaders) {
|
||||
if (header.value() != null) {
|
||||
headers.put(header.key(), new String(header.value(), StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
|
||||
return ReceivedMessage.builder()
|
||||
.body(jsonBody)
|
||||
.contentType(MessageContentType.JSON)
|
||||
.headers(headers)
|
||||
.timestamp(record.timestamp())
|
||||
.source(topic)
|
||||
.key(record.key())
|
||||
.build();
|
||||
|
||||
} catch (Exception e) {
|
||||
LOG.error("Failed to convert Avro record to ReceivedMessage", e);
|
||||
throw new RuntimeException("Failed to convert message", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts Avro GenericRecord to JSON string.
|
||||
*/
|
||||
private String avroToJson(GenericRecord record) {
|
||||
try {
|
||||
return record.toString();
|
||||
return OBJECT_MAPPER.writeValueAsString(convertAvroObject(record));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to convert Avro to JSON: " + e.getMessage(), e);
|
||||
throw new IllegalStateException("Failed to convert Avro record to JSON", e);
|
||||
}
|
||||
}
|
||||
|
||||
private GenericRecord jsonToAvro(String jsonPayload, Schema schema) {
|
||||
try {
|
||||
JsonNode root = OBJECT_MAPPER.readTree(jsonPayload);
|
||||
Object converted = convertJsonNode(root, schema);
|
||||
return (GenericRecord) converted;
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Failed to convert JSON payload to Avro", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Object convertJsonNode(JsonNode node, Schema schema) {
|
||||
return switch (schema.getType()) {
|
||||
case RECORD -> {
|
||||
GenericData.Record record = new GenericData.Record(schema);
|
||||
schema.getFields().forEach(field -> record.put(field.name(),
|
||||
convertJsonNode(node.path(field.name()), field.schema())));
|
||||
yield record;
|
||||
}
|
||||
case ARRAY -> {
|
||||
List<Object> values = new ArrayList<>();
|
||||
node.forEach(item -> values.add(convertJsonNode(item, schema.getElementType())));
|
||||
yield values;
|
||||
}
|
||||
case MAP -> {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
node.fields().forEachRemaining(entry -> map.put(entry.getKey(),
|
||||
convertJsonNode(entry.getValue(), schema.getValueType())));
|
||||
yield map;
|
||||
}
|
||||
case UNION -> resolveUnion(node, schema);
|
||||
case ENUM -> new GenericData.EnumSymbol(schema, node.asText());
|
||||
case FIXED -> {
|
||||
byte[] fixedBytes = toBytes(node);
|
||||
yield new GenericData.Fixed(schema, fixedBytes);
|
||||
}
|
||||
case STRING -> node.isNull() ? null : node.asText();
|
||||
case INT -> node.isNull() ? null : node.asInt();
|
||||
case LONG -> node.isNull() ? null : node.asLong();
|
||||
case FLOAT -> node.isNull() ? null : (float) node.asDouble();
|
||||
case DOUBLE -> node.isNull() ? null : node.asDouble();
|
||||
case BOOLEAN -> node.isNull() ? null : node.asBoolean();
|
||||
case BYTES -> ByteBuffer.wrap(toBytes(node));
|
||||
case NULL -> null;
|
||||
};
|
||||
}
|
||||
|
||||
private Object resolveUnion(JsonNode node, Schema unionSchema) {
|
||||
if (node == null || node.isNull()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
IllegalStateException lastException = null;
|
||||
for (Schema candidate : unionSchema.getTypes()) {
|
||||
if (candidate.getType() == Schema.Type.NULL) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
Object value = convertJsonNode(node, candidate);
|
||||
if (GenericData.get().validate(candidate, value)) {
|
||||
return value;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
lastException = new IllegalStateException("Failed to resolve union type", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (lastException != null) {
|
||||
throw lastException;
|
||||
}
|
||||
throw new IllegalStateException("Cannot resolve union for node: " + node);
|
||||
}
|
||||
|
||||
private byte[] toBytes(JsonNode node) {
|
||||
if (node.isBinary()) {
|
||||
try {
|
||||
return node.binaryValue();
|
||||
} catch (Exception ignored) {
|
||||
// fallback to textual representation
|
||||
}
|
||||
}
|
||||
String text = node.asText();
|
||||
try {
|
||||
return Base64.getDecoder().decode(text);
|
||||
} catch (Exception ignored) {
|
||||
return text.getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
|
||||
private Object convertAvroObject(Object value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
if (value instanceof GenericRecord record) {
|
||||
Map<String, Object> jsonObject = new LinkedHashMap<>();
|
||||
record.getSchema().getFields().forEach(field -> jsonObject.put(field.name(), convertAvroObject(record.get(field.name()))));
|
||||
return jsonObject;
|
||||
}
|
||||
if (value instanceof GenericData.Array<?> array) {
|
||||
List<Object> values = new ArrayList<>(array.size());
|
||||
array.forEach(item -> values.add(convertAvroObject(item)));
|
||||
return values;
|
||||
}
|
||||
if (value instanceof Map<?, ?> map) {
|
||||
Map<String, Object> values = new LinkedHashMap<>();
|
||||
map.forEach((key, item) -> values.put(String.valueOf(key), convertAvroObject(item)));
|
||||
return values;
|
||||
}
|
||||
if (value instanceof ByteBuffer byteBuffer) {
|
||||
ByteBuffer duplicate = byteBuffer.duplicate();
|
||||
byte[] bytes = new byte[duplicate.remaining()];
|
||||
duplicate.get(bytes);
|
||||
return Base64.getEncoder().encodeToString(bytes);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,5 +55,4 @@ public class CaGwEndpoint implements RestEndpoint {
|
||||
public StoreAccessor getStore() {
|
||||
return store;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,199 +0,0 @@
|
||||
package cz.moneta.test.harness.endpoints.imq;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import cz.moneta.test.harness.connectors.VaultConnector;
|
||||
import cz.moneta.test.harness.connectors.messaging.IbmMqConnector;
|
||||
import cz.moneta.test.harness.constants.HarnessConfigConstants;
|
||||
import cz.moneta.test.harness.context.StoreAccessor;
|
||||
import cz.moneta.test.harness.endpoints.Endpoint;
|
||||
import cz.moneta.test.harness.messaging.MqMessageFormat;
|
||||
import cz.moneta.test.harness.messaging.ReceivedMessage;
|
||||
import cz.moneta.test.harness.support.auth.Credentials;
|
||||
|
||||
/**
|
||||
* IBM MQ First Vision endpoint. Provides high-level access to IBM MQ queues
|
||||
* with configuration from StoreAccessor.
|
||||
* <p>
|
||||
* Credentials are loaded from HashiCorp Vault.
|
||||
*/
|
||||
public class ImqFirstVisionEndpoint implements Endpoint {
|
||||
|
||||
private static final Logger LOG = LogManager.getLogger(ImqFirstVisionEndpoint.class);
|
||||
|
||||
private final IbmMqConnector connector;
|
||||
private final StoreAccessor store;
|
||||
|
||||
private String username, password, keystorePassword;
|
||||
|
||||
// Configuration keys
|
||||
private static final String CONNECTION_NAME_LIST_KEY = "endpoints.imq-first-vision.connection-name-list";
|
||||
private static final String CHANNEL_KEY = "endpoints.imq-first-vision.channel";
|
||||
private static final String QUEUE_MANAGER_KEY = "endpoints.imq-first-vision.queue-manager";
|
||||
private static final String SSL_CIPHER_SUITE_KEY = "endpoints.imq-first-vision.ssl-cipher-suite";
|
||||
private static final String VAULT_PATH_KEY = "vault.imq-first-vision.secrets.path";
|
||||
private static final String VAULT_KEYSTORE_PASSWORD_KEY = "keystorePassword";
|
||||
|
||||
private static final String KEYSTORE_PATH = "keystores/imq-keystore.jks";
|
||||
|
||||
/**
|
||||
* Constructor that reads configuration from StoreAccessor.
|
||||
*/
|
||||
public ImqFirstVisionEndpoint(StoreAccessor store) {
|
||||
this.store = store;
|
||||
|
||||
// Read configuration
|
||||
String connectionNameList = getConfig(CONNECTION_NAME_LIST_KEY);
|
||||
String channel = getConfig(CHANNEL_KEY);
|
||||
String queueManager = getConfig(QUEUE_MANAGER_KEY);
|
||||
String sslCipherSuite = getConfig(SSL_CIPHER_SUITE_KEY);
|
||||
|
||||
loadCredentialsFromVault();
|
||||
|
||||
try {
|
||||
this.connector = new IbmMqConnector(connectionNameList, channel, queueManager, username, password,
|
||||
KEYSTORE_PATH, keystorePassword, sslCipherSuite);
|
||||
|
||||
LOG.info("Initialized IBM MQ First Vision endpoint for queue manager: {}", queueManager);
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Failed to initialize IBM MQ endpoint", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a configuration value from StoreAccessor.
|
||||
*/
|
||||
private String getConfig(String key) {
|
||||
return Optional.ofNullable(store.getConfig(key))
|
||||
.orElseThrow(() -> new IllegalStateException("You need to configure " + key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Load credentials from HashiCorp Vault.
|
||||
*/
|
||||
private void loadCredentialsFromVault() {
|
||||
try {
|
||||
// Get vault URL from configuration
|
||||
String vaultPath = getConfig(VAULT_PATH_KEY);
|
||||
String vaultUrl = getConfig(HarnessConfigConstants.VAULT_URL_CONFIG);
|
||||
String vaultUser = getConfig(HarnessConfigConstants.VAULT_USERNAME_CONFIG);
|
||||
String vaultPassword = getConfig(HarnessConfigConstants.VAULT_PASSWORD_CONFIG);
|
||||
|
||||
VaultConnector vaultConnector = new VaultConnector(vaultUrl, vaultUser, vaultPassword);
|
||||
|
||||
Optional<Credentials> credentials = vaultConnector.getUsernameAndPassword(vaultPath);
|
||||
|
||||
if (credentials.isPresent()) {
|
||||
this.username = credentials.get().getUsername();
|
||||
this.password = credentials.get().getPassword();
|
||||
this.keystorePassword = vaultConnector.getValue(vaultPath, VAULT_KEYSTORE_PASSWORD_KEY)
|
||||
.map(Object::toString).orElse(null);
|
||||
LOG.info("Successfully loaded credentials from Vault for path: {}", vaultPath);
|
||||
} else {
|
||||
throw new IllegalStateException("Credentials not found in Vault at path: " + vaultPath);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Failed to load credentials from Vault", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message to a queue.
|
||||
*
|
||||
* @param queueName Physical queue name or logical name (from
|
||||
* ImqFirstVisionQueue)
|
||||
* @param payload Message payload
|
||||
* @param format Message format
|
||||
* @param properties JMS properties
|
||||
*/
|
||||
public void send(String queueName, String payload, MqMessageFormat format,
|
||||
java.util.Map<String, String> properties) {
|
||||
connector.send(queueName, payload, format, properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message to a queue using logical queue name.
|
||||
*/
|
||||
public void send(ImqFirstVisionQueue queue, String payload, MqMessageFormat format,
|
||||
java.util.Map<String, String> properties) {
|
||||
String physicalQueueName = resolveQueue(queue);
|
||||
connector.send(physicalQueueName, payload, format, properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive a message from a queue.
|
||||
*
|
||||
* @param queueName Physical queue name or logical name
|
||||
* @param messageSelector JMS message selector (optional)
|
||||
* @param format Expected message format
|
||||
* @param timeout Timeout duration
|
||||
* @return Received message
|
||||
*/
|
||||
public ReceivedMessage receive(String queueName, String messageSelector, MqMessageFormat format, Duration timeout) {
|
||||
return connector.receive(queueName, messageSelector, format, timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive a message from a queue using logical queue name.
|
||||
*/
|
||||
public ReceivedMessage receive(ImqFirstVisionQueue queue, String messageSelector, MqMessageFormat format,
|
||||
Duration timeout) {
|
||||
String physicalQueueName = resolveQueue(queue);
|
||||
return connector.receive(physicalQueueName, messageSelector, format, timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Browse a queue (non-destructive read).
|
||||
*
|
||||
* @param queueName Physical queue name or logical name
|
||||
* @param messageSelector JMS message selector (optional)
|
||||
* @param format Expected message format
|
||||
* @param maxMessages Maximum number of messages
|
||||
* @return List of received messages
|
||||
*/
|
||||
public List<ReceivedMessage> browse(String queueName, String messageSelector, MqMessageFormat format,
|
||||
int maxMessages) {
|
||||
return connector.browse(queueName, messageSelector, format, maxMessages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Browse a queue using logical queue name.
|
||||
*/
|
||||
public List<ReceivedMessage> browse(ImqFirstVisionQueue queue, String messageSelector, MqMessageFormat format,
|
||||
int maxMessages) {
|
||||
String physicalQueueName = resolveQueue(queue);
|
||||
return connector.browse(physicalQueueName, messageSelector, format, maxMessages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve logical queue name to physical queue name.
|
||||
*
|
||||
* @param logicalName Logical queue name or ImqFirstVisionQueue enum
|
||||
* @return Physical queue name
|
||||
*/
|
||||
public String resolveQueue(String logicalName) {
|
||||
String configKey = "endpoints.imq-first-vision." + logicalName + ".queue";
|
||||
return Optional.ofNullable(store.getConfig(configKey)).orElseThrow(
|
||||
() -> new IllegalStateException("Queue '" + logicalName + "' is not configured in " + configKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve ImqFirstVisionQueue enum to physical queue name.
|
||||
*/
|
||||
public String resolveQueue(ImqFirstVisionQueue queue) {
|
||||
return resolveQueue(queue.getConfigKey());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (connector != null) {
|
||||
connector.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,51 +0,0 @@
|
||||
package cz.moneta.test.harness.endpoints.imq;
|
||||
|
||||
/**
|
||||
* Logical queue names for IBM MQ First Vision.
|
||||
* Physical queue names are resolved from configuration.
|
||||
*/
|
||||
public enum ImqFirstVisionQueue {
|
||||
/**
|
||||
* Payment notifications queue.
|
||||
*/
|
||||
PAYMENT_NOTIFICATIONS("payment-notifications"),
|
||||
|
||||
/**
|
||||
* Payment request queue.
|
||||
*/
|
||||
PAYMENT_REQUEST("payment-request"),
|
||||
|
||||
/**
|
||||
* MF (Money Flow) requests queue.
|
||||
*/
|
||||
MF_REQUESTS("mf-requests"),
|
||||
|
||||
/**
|
||||
* MF (Money Flow) responses queue.
|
||||
*/
|
||||
MF_RESPONSES("mf-responses"),
|
||||
|
||||
/**
|
||||
* MF (Money Flow) EBCDIC queue.
|
||||
*/
|
||||
MF_EBCDIC("mf-ebcdic"),
|
||||
|
||||
/**
|
||||
* MF (Money Flow) UTF-8 queue.
|
||||
*/
|
||||
MF_UTF8("mf-utf8");
|
||||
|
||||
private final String configKey;
|
||||
|
||||
ImqFirstVisionQueue(String configKey) {
|
||||
this.configKey = configKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the configuration key for this queue.
|
||||
* Used to resolve physical queue name from configuration.
|
||||
*/
|
||||
public String getConfigKey() {
|
||||
return configKey;
|
||||
}
|
||||
}
|
||||
@ -1,199 +0,0 @@
|
||||
package cz.moneta.test.harness.endpoints.kafka;
|
||||
|
||||
import cz.moneta.test.harness.connectors.messaging.KafkaConnector;
|
||||
import cz.moneta.test.harness.context.BaseStoreAccessor;
|
||||
import cz.moneta.test.harness.context.StoreAccessor;
|
||||
import cz.moneta.test.harness.endpoints.Endpoint;
|
||||
import cz.moneta.test.harness.messaging.exception.MessagingConnectionException;
|
||||
import cz.moneta.test.harness.support.messaging.kafka.ReceivedMessage;
|
||||
import com.bettercloud.vault.Vault;
|
||||
import com.bettercloud.vault.VaultConfig;
|
||||
import com.bettercloud.vault.VaultException;
|
||||
import com.bettercloud.vault.response.LogicalResponse;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Kafka endpoint that provides high-level API for Kafka messaging.
|
||||
* <p>
|
||||
* Reads configuration from StoreAccessor and credentials from HashiCorp Vault.
|
||||
* Implements singleton pattern per test class (managed by Harness).
|
||||
* </p>
|
||||
*/
|
||||
public class KafkaEndpoint implements Endpoint {
|
||||
|
||||
private final KafkaConnector connector;
|
||||
private final StoreAccessor store;
|
||||
|
||||
/**
|
||||
* Creates a new KafkaEndpoint.
|
||||
* <p>
|
||||
* Configuration is read from StoreAccessor:
|
||||
* - endpoints.kafka.bootstrap-servers
|
||||
* - endpoints.kafka.schema-registry-url
|
||||
* <p>
|
||||
* Credentials are retrieved from Vault:
|
||||
* - vault.kafka.secrets.path -> apiKey, apiSecret
|
||||
* </p>
|
||||
*/
|
||||
public KafkaEndpoint(StoreAccessor store) {
|
||||
this.store = store;
|
||||
|
||||
// Read configuration
|
||||
String bootstrapServers = store.getConfig("endpoints.kafka.bootstrap-servers");
|
||||
if (bootstrapServers == null) {
|
||||
throw new IllegalStateException("You need to configure " + bootstrapServers + " to work with Kafka");
|
||||
}
|
||||
|
||||
String schemaRegistryUrl = store.getConfig("endpoints.kafka.schema-registry-url");
|
||||
if (schemaRegistryUrl == null) {
|
||||
throw new IllegalStateException("You need to configure " + schemaRegistryUrl + " url to work with Kafka");
|
||||
}
|
||||
|
||||
// Retrieve credentials from Vault
|
||||
String vaultPath = store.getConfig("vault.kafka.secrets.path");
|
||||
if (vaultPath == null) {
|
||||
throw new IllegalStateException(
|
||||
"You need to configure vault.kafka.secrets.path");
|
||||
}
|
||||
//
|
||||
String apiKey = getVaultValue(vaultPath, "apiKey");
|
||||
String apiSecret = getVaultValue(vaultPath, "apiSecret");
|
||||
String schemaRegistryApiKey = getVaultValue(vaultPath, "schemaRegistryApiKey");
|
||||
String schemaRegistryApiSecret = getVaultValue(vaultPath, "schemaRegistryApiSecret");
|
||||
|
||||
// Create connector
|
||||
this.connector = new KafkaConnector(
|
||||
bootstrapServers,
|
||||
apiKey,
|
||||
apiSecret,
|
||||
schemaRegistryUrl,
|
||||
schemaRegistryApiKey,
|
||||
schemaRegistryApiSecret
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message to a Kafka topic.
|
||||
*
|
||||
* @param topic Kafka topic name
|
||||
* @param key Message key
|
||||
* @param jsonPayload Message body as JSON string
|
||||
* @param headers Kafka headers (traceparent, requestID, activityID, etc.)
|
||||
*/
|
||||
public void send(String topic, String key, String jsonPayload, Map<String, String> headers) {
|
||||
connector.send(topic, key, jsonPayload, headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives a message from a Kafka topic matching the filter.
|
||||
*
|
||||
* @param topic Kafka topic name
|
||||
* @param filter Predicate to filter messages
|
||||
* @param timeout Maximum time to wait for a message
|
||||
* @return First matching ReceivedMessage
|
||||
*/
|
||||
public List<ReceivedMessage> receive(String topic,
|
||||
java.util.function.Predicate<ReceivedMessage> filter,
|
||||
Duration timeout) {
|
||||
return connector.receive(topic, filter, timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives a message with default timeout (30 seconds).
|
||||
*/
|
||||
public List<ReceivedMessage> receive(String topic,
|
||||
java.util.function.Predicate<ReceivedMessage> filter) {
|
||||
return receive(topic, filter, Duration.ofSeconds(30));
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives the first available message from a topic.
|
||||
*
|
||||
* @param topic Kafka topic name
|
||||
* @param timeout Maximum time to wait
|
||||
* @return First message
|
||||
*/
|
||||
public List<ReceivedMessage> receive(String topic, Duration timeout) {
|
||||
return receive(topic, msg -> true, timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the endpoint and releases resources.
|
||||
*/
|
||||
@Override
|
||||
public void close() {
|
||||
if (connector != null) {
|
||||
connector.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if Kafka is accessible.
|
||||
*/
|
||||
@Override
|
||||
public boolean canAccess() {
|
||||
try {
|
||||
// Try to get topic metadata - if this succeeds, Kafka is accessible
|
||||
// Note: We don't actually make a network call here, just verify config
|
||||
String bootstrapServers = store.getConfig("endpoints.kafka.bootstrap-servers");
|
||||
return bootstrapServers != null && !bootstrapServers.isEmpty();
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the underlying connector (for advanced use cases).
|
||||
*/
|
||||
public KafkaConnector getConnector() {
|
||||
return connector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the store accessor.
|
||||
*/
|
||||
public StoreAccessor getStore() {
|
||||
return store;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a value from Vault.
|
||||
*/
|
||||
private String getVaultValue(String path, String key) {
|
||||
try {
|
||||
VaultConfig vaultConfig = new VaultConfig()
|
||||
.address(store.getConfig("vault.address", "http://localhost:8200"))
|
||||
.token(store.getConfig("vault.token"))
|
||||
.build();
|
||||
Vault vault = new Vault(vaultConfig, 2);
|
||||
|
||||
LogicalResponse response = vault.logical().read(path);
|
||||
if (response == null) {
|
||||
throw new MessagingConnectionException(
|
||||
"Failed to read from Vault path: " + path);
|
||||
}
|
||||
Map<String, String> data = response.getData();
|
||||
if (data == null) {
|
||||
throw new MessagingConnectionException(
|
||||
"No data found in Vault path: " + path);
|
||||
}
|
||||
|
||||
String value = data.get(key);
|
||||
if (value == null) {
|
||||
throw new MessagingConnectionException(
|
||||
"Credential '" + key + "' not found in Vault path: " + path);
|
||||
}
|
||||
|
||||
return value;
|
||||
|
||||
} catch (VaultException e) {
|
||||
throw new MessagingConnectionException(
|
||||
"Failed to retrieve credential '" + key + "' from Vault at " + path, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,125 @@
|
||||
package cz.moneta.test.harness.endpoints.messaging;
|
||||
|
||||
import cz.moneta.test.harness.connectors.VaultConnector;
|
||||
import cz.moneta.test.harness.connectors.messaging.IbmMqConnector;
|
||||
import cz.moneta.test.harness.constants.HarnessConfigConstants;
|
||||
import cz.moneta.test.harness.context.StoreAccessor;
|
||||
import cz.moneta.test.harness.endpoints.Endpoint;
|
||||
import cz.moneta.test.harness.messaging.model.MqMessageFormat;
|
||||
import cz.moneta.test.harness.messaging.model.ReceivedMessage;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class IbmMqEndpoint implements Endpoint {
|
||||
|
||||
private static final String CONFIG_HOST = "endpoints.imq-first-vision.host";
|
||||
private static final String CONFIG_PORT = "endpoints.imq-first-vision.port";
|
||||
private static final String CONFIG_CHANNEL = "endpoints.imq-first-vision.channel";
|
||||
private static final String CONFIG_QUEUE_MANAGER = "endpoints.imq-first-vision.queue-manager";
|
||||
private static final String CONFIG_KEYSTORE_PATH = "endpoints.imq-first-vision.keystore.path";
|
||||
private static final String CONFIG_KEYSTORE_PASSWORD = "endpoints.imq-first-vision.keystore.password";
|
||||
private static final String CONFIG_VAULT_PATH = "vault.path.messaging.ibmmq";
|
||||
private static final String CONFIG_USERNAME = "endpoints.imq-first-vision.username";
|
||||
private static final String CONFIG_PASSWORD = "endpoints.imq-first-vision.password";
|
||||
private static final String CONFIG_SSL_CYPHER_SUITES = "endpoints.imq-first-vision.ssl-cipher-suite";
|
||||
|
||||
private final StoreAccessor store;
|
||||
private volatile IbmMqConnector connector;
|
||||
private final Object connectorLock = new Object();
|
||||
|
||||
public IbmMqEndpoint(StoreAccessor store) {
|
||||
this.store = store;
|
||||
}
|
||||
|
||||
public void send(String queueName,
|
||||
String payload,
|
||||
MqMessageFormat format,
|
||||
Map<String, String> properties) {
|
||||
getConnector().send(queueName, payload, format, properties);
|
||||
}
|
||||
|
||||
public ReceivedMessage receive(String queueName,
|
||||
String messageSelector,
|
||||
MqMessageFormat expectedFormat,
|
||||
Duration timeout) {
|
||||
return getConnector().receive(queueName, messageSelector, expectedFormat, timeout);
|
||||
}
|
||||
|
||||
public List<ReceivedMessage> browse(String queueName,
|
||||
Predicate<ReceivedMessage> filter,
|
||||
MqMessageFormat expectedFormat,
|
||||
Duration timeout) {
|
||||
return getConnector().browse(queueName, filter, expectedFormat, timeout);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
IbmMqConnector current = connector;
|
||||
if (current != null) {
|
||||
current.close();
|
||||
}
|
||||
}
|
||||
|
||||
private IbmMqConnector getConnector() {
|
||||
IbmMqConnector current = connector;
|
||||
if (current == null) {
|
||||
synchronized (connectorLock) {
|
||||
current = connector;
|
||||
if (current == null) {
|
||||
current = createConnector();
|
||||
connector = current;
|
||||
}
|
||||
}
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
private IbmMqConnector createConnector() {
|
||||
String host = requireConfig(CONFIG_HOST);
|
||||
int port = Integer.parseInt(requireConfig(CONFIG_PORT));
|
||||
String channel = requireConfig(CONFIG_CHANNEL);
|
||||
String queueManager = requireConfig(CONFIG_QUEUE_MANAGER);
|
||||
String username = resolveSecret(CONFIG_USERNAME, "username");
|
||||
String password = resolveSecret(CONFIG_PASSWORD, "password");
|
||||
String keystorePath = store.getConfig(CONFIG_KEYSTORE_PATH);
|
||||
String keystorePassword = store.getConfig(CONFIG_KEYSTORE_PASSWORD);
|
||||
String sslCipherSuites = store.getConfig(CONFIG_SSL_CYPHER_SUITES);
|
||||
|
||||
return new IbmMqConnector(host, port, channel, queueManager, username, password, keystorePath, keystorePassword, sslCipherSuites);
|
||||
}
|
||||
|
||||
private String resolveSecret(String localConfigKey, String vaultKey) {
|
||||
return Optional.ofNullable(store.getConfig(localConfigKey))
|
||||
.or(() -> readFromVault(vaultKey))
|
||||
.orElseThrow(() -> new IllegalStateException("Missing messaging secret: " + localConfigKey));
|
||||
}
|
||||
|
||||
private Optional<String> readFromVault(String key) {
|
||||
String path = store.getConfig(CONFIG_VAULT_PATH);
|
||||
if (path == null || path.isBlank()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
String basePath = store.getConfig("vault.path.base");
|
||||
String resolvedPath = path.startsWith("/") || basePath == null || basePath.isBlank()
|
||||
? path
|
||||
: basePath + "/" + path;
|
||||
|
||||
return createVaultConnector().flatMap(vault -> vault.getValue(resolvedPath, key));
|
||||
}
|
||||
|
||||
private Optional<VaultConnector> createVaultConnector() {
|
||||
return Optional.ofNullable(store.getConfig(HarnessConfigConstants.VAULT_URL_CONFIG))
|
||||
.flatMap(url -> Optional.ofNullable(store.getConfig(HarnessConfigConstants.VAULT_USERNAME_CONFIG))
|
||||
.flatMap(username -> Optional.ofNullable(store.getConfig(HarnessConfigConstants.VAULT_PASSWORD_CONFIG))
|
||||
.map(password -> new VaultConnector(url, username, password))));
|
||||
}
|
||||
|
||||
private String requireConfig(String key) {
|
||||
return Optional.ofNullable(store.getConfig(key))
|
||||
.orElseThrow(() -> new IllegalStateException("Missing required config: " + key));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,119 @@
|
||||
package cz.moneta.test.harness.endpoints.messaging;
|
||||
|
||||
import cz.moneta.test.harness.connectors.VaultConnector;
|
||||
import cz.moneta.test.harness.connectors.messaging.KafkaConnector;
|
||||
import cz.moneta.test.harness.constants.HarnessConfigConstants;
|
||||
import cz.moneta.test.harness.context.StoreAccessor;
|
||||
import cz.moneta.test.harness.endpoints.Endpoint;
|
||||
import cz.moneta.test.harness.messaging.model.ReceivedMessage;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class KafkaEndpoint implements Endpoint {
|
||||
|
||||
private static final String CONFIG_BOOTSTRAP_SERVERS = "messaging.kafka.bootstrap-servers";
|
||||
private static final String CONFIG_SCHEMA_REGISTRY_URL = "messaging.kafka.schema-registry-url";
|
||||
private static final String CONFIG_VAULT_PATH = "vault.path.messaging.kafka";
|
||||
|
||||
private static final String CONFIG_API_KEY = "messaging.kafka.api-key";
|
||||
private static final String CONFIG_API_SECRET = "messaging.kafka.api-secret";
|
||||
private static final String CONFIG_SCHEMA_REGISTRY_API_KEY = "messaging.kafka.schema-registry-api-key";
|
||||
private static final String CONFIG_SCHEMA_REGISTRY_API_SECRET = "messaging.kafka.schema-registry-api-secret";
|
||||
|
||||
private static final String VAULT_API_KEY = "apiKey";
|
||||
private static final String VAULT_API_SECRET = "apiSecret";
|
||||
private static final String VAULT_SCHEMA_REGISTRY_API_KEY = "schemaRegistryApiKey";
|
||||
private static final String VAULT_SCHEMA_REGISTRY_API_SECRET = "schemaRegistryApiSecret";
|
||||
|
||||
private final StoreAccessor store;
|
||||
private volatile KafkaConnector connector;
|
||||
private final Object connectorLock = new Object();
|
||||
|
||||
public KafkaEndpoint(StoreAccessor store) {
|
||||
this.store = store;
|
||||
}
|
||||
|
||||
public void send(String topic, String key, String payload, Map<String, String> headers) {
|
||||
getConnector().send(topic, key, payload, headers);
|
||||
}
|
||||
|
||||
public ReceivedMessage receive(String topic, Predicate<ReceivedMessage> filter, Duration timeout) {
|
||||
List<ReceivedMessage> messages = getConnector().receive(topic, filter, timeout);
|
||||
return messages.isEmpty() ? null : messages.get(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
KafkaConnector current = connector;
|
||||
if (current != null) {
|
||||
current.close();
|
||||
}
|
||||
}
|
||||
|
||||
private KafkaConnector getConnector() {
|
||||
KafkaConnector current = connector;
|
||||
if (current == null) {
|
||||
synchronized (connectorLock) {
|
||||
current = connector;
|
||||
if (current == null) {
|
||||
current = createConnector();
|
||||
connector = current;
|
||||
}
|
||||
}
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
private KafkaConnector createConnector() {
|
||||
String bootstrapServers = requireConfig(CONFIG_BOOTSTRAP_SERVERS);
|
||||
String schemaRegistryUrl = requireConfig(CONFIG_SCHEMA_REGISTRY_URL);
|
||||
String apiKey = resolveSecret(CONFIG_API_KEY, VAULT_API_KEY);
|
||||
String apiSecret = resolveSecret(CONFIG_API_SECRET, VAULT_API_SECRET);
|
||||
String schemaRegistryApiKey = resolveSecret(CONFIG_SCHEMA_REGISTRY_API_KEY, VAULT_SCHEMA_REGISTRY_API_KEY);
|
||||
String schemaRegistryApiSecret = resolveSecret(CONFIG_SCHEMA_REGISTRY_API_SECRET, VAULT_SCHEMA_REGISTRY_API_SECRET);
|
||||
|
||||
return new KafkaConnector(
|
||||
bootstrapServers,
|
||||
apiKey,
|
||||
apiSecret,
|
||||
schemaRegistryUrl,
|
||||
schemaRegistryApiKey,
|
||||
schemaRegistryApiSecret
|
||||
);
|
||||
}
|
||||
|
||||
private String resolveSecret(String localConfigKey, String vaultKey) {
|
||||
return Optional.ofNullable(store.getConfig(localConfigKey))
|
||||
.or(() -> readFromVault(vaultKey))
|
||||
.orElseThrow(() -> new IllegalStateException("Missing messaging secret: " + localConfigKey));
|
||||
}
|
||||
|
||||
private Optional<String> readFromVault(String key) {
|
||||
String path = store.getConfig(CONFIG_VAULT_PATH);
|
||||
if (path == null || path.isBlank()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
String basePath = store.getConfig("vault.path.base");
|
||||
String resolvedPath = path.startsWith("/") || basePath == null || basePath.isBlank()
|
||||
? path
|
||||
: basePath + "/" + path;
|
||||
|
||||
return createVaultConnector().flatMap(vault -> vault.getValue(resolvedPath, key));
|
||||
}
|
||||
|
||||
private Optional<VaultConnector> createVaultConnector() {
|
||||
return Optional.ofNullable(store.getConfig(HarnessConfigConstants.VAULT_URL_CONFIG))
|
||||
.flatMap(url -> Optional.ofNullable(store.getConfig(HarnessConfigConstants.VAULT_USERNAME_CONFIG))
|
||||
.flatMap(username -> Optional.ofNullable(store.getConfig(HarnessConfigConstants.VAULT_PASSWORD_CONFIG))
|
||||
.map(password -> new VaultConnector(url, username, password))));
|
||||
}
|
||||
|
||||
private String requireConfig(String key) {
|
||||
return Optional.ofNullable(store.getConfig(key))
|
||||
.orElseThrow(() -> new IllegalStateException("Missing required config: " + key));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,130 @@
|
||||
package cz.moneta.test.harness.endpoints.messaging;
|
||||
|
||||
import cz.moneta.test.harness.context.StoreAccessor;
|
||||
import cz.moneta.test.harness.endpoints.Endpoint;
|
||||
import cz.moneta.test.harness.exception.MessagingTimeoutException;
|
||||
import cz.moneta.test.harness.messaging.model.Destination;
|
||||
import cz.moneta.test.harness.messaging.model.MqMessageFormat;
|
||||
import cz.moneta.test.harness.messaging.model.Queue;
|
||||
import cz.moneta.test.harness.messaging.model.ReceivedMessage;
|
||||
import cz.moneta.test.harness.messaging.model.Topic;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class MessagingEndpoint implements Endpoint {
|
||||
|
||||
private final StoreAccessor store;
|
||||
private volatile KafkaEndpoint kafkaEndpoint;
|
||||
private volatile IbmMqEndpoint ibmMqEndpoint;
|
||||
private final Object endpointLock = new Object();
|
||||
|
||||
public MessagingEndpoint(StoreAccessor store) {
|
||||
this.store = store;
|
||||
}
|
||||
|
||||
public void send(String destinationName,
|
||||
String key,
|
||||
String payload,
|
||||
MqMessageFormat formatOverride,
|
||||
Map<String, String> headers) {
|
||||
Destination destination = resolveDestination(destinationName);
|
||||
if (destination instanceof Topic topic) {
|
||||
getKafkaEndpoint().send(topic.getTopicName(), key, payload, headers);
|
||||
return;
|
||||
}
|
||||
|
||||
Queue queue = (Queue) destination;
|
||||
MqMessageFormat format = Optional.ofNullable(formatOverride).orElse(queue.getFormat());
|
||||
getIbmMqEndpoint().send(queue.getQueueName(), payload, format, headers);
|
||||
}
|
||||
|
||||
public ReceivedMessage receive(String destinationName,
|
||||
Predicate<ReceivedMessage> filter,
|
||||
Duration timeout,
|
||||
MqMessageFormat formatOverride) {
|
||||
Destination destination = resolveDestination(destinationName);
|
||||
if (destination instanceof Topic topic) {
|
||||
return getKafkaEndpoint().receive(topic.getTopicName(), filter, timeout);
|
||||
}
|
||||
|
||||
Queue queue = (Queue) destination;
|
||||
MqMessageFormat format = Optional.ofNullable(formatOverride).orElse(queue.getFormat());
|
||||
List<ReceivedMessage> messages = getIbmMqEndpoint().browse(queue.getQueueName(), filter, format, timeout);
|
||||
if (messages.isEmpty()) {
|
||||
throw new MessagingTimeoutException("No IBM MQ message found for destination: " + destinationName);
|
||||
}
|
||||
return messages.get(0);
|
||||
}
|
||||
|
||||
public Destination resolveDestination(String destinationName) {
|
||||
String prefix = "messaging.destination." + destinationName + ".";
|
||||
String type = Optional.ofNullable(store.getConfig(prefix + "type"))
|
||||
.map(v -> v.toLowerCase(Locale.ROOT))
|
||||
.orElseThrow(() -> new IllegalStateException("Missing destination config: " + prefix + "type"));
|
||||
|
||||
return switch (type) {
|
||||
case "kafka" -> {
|
||||
String topic = requireConfig(prefix + "topic");
|
||||
yield new Topic(destinationName, topic);
|
||||
}
|
||||
case "ibmmq" -> {
|
||||
String queue = requireConfig(prefix + "queue");
|
||||
String format = store.getConfig(prefix + "format", MqMessageFormat.JSON.name().toLowerCase(Locale.ROOT));
|
||||
yield new Queue(destinationName, queue, MqMessageFormat.fromConfig(format, MqMessageFormat.JSON));
|
||||
}
|
||||
default -> throw new IllegalStateException("Unsupported destination type '" + type + "' for destination: " + destinationName);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
KafkaEndpoint kafka = kafkaEndpoint;
|
||||
if (kafka != null) {
|
||||
kafka.close();
|
||||
}
|
||||
IbmMqEndpoint mq = ibmMqEndpoint;
|
||||
if (mq != null) {
|
||||
mq.close();
|
||||
}
|
||||
}
|
||||
|
||||
private KafkaEndpoint getKafkaEndpoint() {
|
||||
KafkaEndpoint current = kafkaEndpoint;
|
||||
if (current == null) {
|
||||
synchronized (endpointLock) {
|
||||
current = kafkaEndpoint;
|
||||
if (current == null) {
|
||||
current = new KafkaEndpoint(store);
|
||||
kafkaEndpoint = current;
|
||||
}
|
||||
}
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
private IbmMqEndpoint getIbmMqEndpoint() {
|
||||
IbmMqEndpoint current = ibmMqEndpoint;
|
||||
if (current == null) {
|
||||
synchronized (endpointLock) {
|
||||
current = ibmMqEndpoint;
|
||||
if (current == null) {
|
||||
current = new IbmMqEndpoint(store);
|
||||
ibmMqEndpoint = current;
|
||||
}
|
||||
}
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
private String requireConfig(String key) {
|
||||
return Optional.ofNullable(store.getConfig(key))
|
||||
.filter(v -> !Objects.equals(v.trim(), ""))
|
||||
.orElseThrow(() -> new IllegalStateException("Missing required config: " + key));
|
||||
}
|
||||
}
|
||||
@ -1,51 +0,0 @@
|
||||
package cz.moneta.test.harness.endpoints.payment_engine;
|
||||
|
||||
import cz.moneta.test.harness.connectors.rest.RestConnector;
|
||||
import cz.moneta.test.harness.connectors.rest.SimpleRestConnector;
|
||||
import cz.moneta.test.harness.context.StoreAccessor;
|
||||
import cz.moneta.test.harness.endpoints.RestEndpoint;
|
||||
import jakarta.ws.rs.client.Entity;
|
||||
import jakarta.ws.rs.core.GenericType;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
public class PaeMqEndpoint implements RestEndpoint {
|
||||
|
||||
private RestConnector restConnector;
|
||||
private StoreAccessor store;
|
||||
|
||||
public PaeMqEndpoint(StoreAccessor store) {
|
||||
this.store = store;
|
||||
String endpointName = "endpoints.pae-mq.url";
|
||||
this.restConnector = Optional.ofNullable(store.getConfig(endpointName))
|
||||
.map(url -> new SimpleRestConnector(url, "PaeMqServerLogger"))
|
||||
.orElseThrow(() -> new IllegalStateException("You need to configure " + endpointName + " to work with PAE IBM MQ server"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Pair<Integer, T> get(String path, Map<String, Object> properties, Class<T> responseType, Map<String, Object> headers) {
|
||||
return restConnector.get(path, properties, new GenericType<>(responseType), headers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Pair<Integer, T> post(String path, Entity<?> request, Class<T> responseType, Map<String, Object> headers, boolean rawResponse) {
|
||||
return restConnector.post(path, request, new GenericType<>(responseType), headers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Pair<Integer, T> delete(String path, Map<String, Object> properties, Class<T> responseType, Map<String, Object> headers) {
|
||||
return restConnector.delete(path, properties, new GenericType<>(responseType), headers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Pair<Integer, T> patch(String path, Entity<?> request, Class<T> responseType, Map<String, Object> headers, boolean rawResponse) {
|
||||
return restConnector.patch(path, request, new GenericType<>(responseType), headers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public StoreAccessor getStore() {
|
||||
return store;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
package cz.moneta.test.harness.exception;
|
||||
|
||||
public class MessagingTimeoutException extends HarnessException {
|
||||
|
||||
public MessagingTimeoutException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@ -1,83 +0,0 @@
|
||||
package cz.moneta.test.harness.messaging;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.node.NullNode;
|
||||
|
||||
/**
|
||||
* Wrapper for extracted JSON path values.
|
||||
* Provides fluent methods for value extraction and conversion.
|
||||
*/
|
||||
public class JsonPathValue {
|
||||
|
||||
private final JsonNode node;
|
||||
private final String rawValue;
|
||||
|
||||
public JsonPathValue(JsonNode node) {
|
||||
this.node = node;
|
||||
this.rawValue = node != null ? node.asText() : null;
|
||||
}
|
||||
|
||||
public JsonPathValue(String rawValue) {
|
||||
this.node = null;
|
||||
this.rawValue = rawValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value as a string.
|
||||
*/
|
||||
public String asText() {
|
||||
if (node != null && !(node instanceof NullNode)) {
|
||||
return node.asText();
|
||||
}
|
||||
return rawValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value as an integer.
|
||||
*/
|
||||
public int asInt() {
|
||||
if (node != null && !(node instanceof NullNode)) {
|
||||
return node.asInt();
|
||||
}
|
||||
return Integer.parseInt(rawValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value as a long.
|
||||
*/
|
||||
public long asLong() {
|
||||
if (node != null && !(node instanceof NullNode)) {
|
||||
return node.asLong();
|
||||
}
|
||||
return Long.parseLong(rawValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value as a boolean.
|
||||
*/
|
||||
public boolean asBoolean() {
|
||||
if (node != null && !(node instanceof NullNode)) {
|
||||
return node.asBoolean();
|
||||
}
|
||||
return Boolean.parseBoolean(rawValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the value is null or missing.
|
||||
*/
|
||||
public boolean isNull() {
|
||||
return node == null || node instanceof NullNode || rawValue == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the underlying JsonNode.
|
||||
*/
|
||||
public JsonNode getNode() {
|
||||
return node;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return asText();
|
||||
}
|
||||
}
|
||||
@ -1,21 +0,0 @@
|
||||
package cz.moneta.test.harness.messaging;
|
||||
|
||||
/**
|
||||
* Content type of a received message.
|
||||
*/
|
||||
public enum MessageContentType {
|
||||
/**
|
||||
* JSON content - body is a JSON string.
|
||||
*/
|
||||
JSON,
|
||||
|
||||
/**
|
||||
* XML content - body is an XML string.
|
||||
*/
|
||||
XML,
|
||||
|
||||
/**
|
||||
* Raw text content - body is plain text (e.g., EBCDIC decoded, UTF-8).
|
||||
*/
|
||||
RAW_TEXT
|
||||
}
|
||||
@ -1,111 +0,0 @@
|
||||
package cz.moneta.test.harness.messaging;
|
||||
|
||||
import org.assertj.core.api.AbstractObjectAssert;
|
||||
|
||||
/**
|
||||
* Response interface for received messages.
|
||||
* Provides assertion methods for verifying message content.
|
||||
* Shared interface for both Kafka and IBM MQ message responses.
|
||||
*/
|
||||
public interface MessageResponse {
|
||||
|
||||
/**
|
||||
* Assert that a field in the message body has the expected value.
|
||||
* For JSON: uses JSON path (dot/bracket notation).
|
||||
* For XML: uses XPath expression.
|
||||
*
|
||||
* @param path JSON path or XPath expression
|
||||
* @param value expected value as string
|
||||
* @return this instance for fluent assertions
|
||||
* @throws AssertionError if assertion fails
|
||||
*/
|
||||
MessageResponse andAssertFieldValue(String path, String value);
|
||||
|
||||
/**
|
||||
* Assert that a field exists in the message body.
|
||||
*
|
||||
* @param path JSON path or XPath expression
|
||||
* @return this instance for fluent assertions
|
||||
* @throws AssertionError if assertion fails
|
||||
*/
|
||||
MessageResponse andAssertPresent(String path);
|
||||
|
||||
/**
|
||||
* Assert that a field does not exist in the message body.
|
||||
*
|
||||
* @param path JSON path or XPath expression
|
||||
* @return this instance for fluent assertions
|
||||
* @throws AssertionError if assertion fails
|
||||
*/
|
||||
MessageResponse andAssertNotPresent(String path);
|
||||
|
||||
/**
|
||||
* Assert that a header (Kafka header or JMS property) has the expected value.
|
||||
*
|
||||
* @param headerName name of the header/property
|
||||
* @param value expected value
|
||||
* @return this instance for fluent assertions
|
||||
* @throws AssertionError if assertion fails
|
||||
*/
|
||||
MessageResponse andAssertHeaderValue(String headerName, String value);
|
||||
|
||||
/**
|
||||
* Assert that the message body contains a substring.
|
||||
* Primarily used for EBCDIC/UTF-8 raw text assertions.
|
||||
*
|
||||
* @param substring expected substring
|
||||
* @return this instance for fluent assertions
|
||||
* @throws AssertionError if assertion fails
|
||||
*/
|
||||
MessageResponse andAssertBodyContains(String substring);
|
||||
|
||||
/**
|
||||
* Get AssertJ fluent assertion for complex object assertions.
|
||||
*
|
||||
* @return AssertJ AbstractObjectAssert for fluent assertions
|
||||
*/
|
||||
AbstractObjectAssert<?, ?> andAssertWithAssertJ();
|
||||
|
||||
/**
|
||||
* Extract a value from the message body.
|
||||
* For JSON: uses JSON path (dot/bracket notation).
|
||||
* For XML: uses XPath expression.
|
||||
*
|
||||
* @param path JSON path or XPath expression
|
||||
* @return JsonPathValue wrapper for the extracted value
|
||||
*/
|
||||
JsonPathValue extract(String path);
|
||||
|
||||
/**
|
||||
* Deserialize the message body to a Java object.
|
||||
* For JSON: uses Jackson ObjectMapper.
|
||||
* For XML: uses JAXB or Jackson XmlMapper.
|
||||
*
|
||||
* @param type target type
|
||||
* @param <T> target type
|
||||
* @return deserialized object
|
||||
*/
|
||||
<T> T mapTo(Class<T> type);
|
||||
|
||||
/**
|
||||
* Get the raw message body.
|
||||
*
|
||||
* @return message body as string
|
||||
*/
|
||||
String getBody();
|
||||
|
||||
/**
|
||||
* Get a header value (Kafka header or JMS property).
|
||||
*
|
||||
* @param name header/property name
|
||||
* @return header value or null if not present
|
||||
*/
|
||||
String getHeader(String name);
|
||||
|
||||
/**
|
||||
* Get the underlying received message.
|
||||
*
|
||||
* @return ReceivedMessage instance
|
||||
*/
|
||||
ReceivedMessage getMessage();
|
||||
}
|
||||
@ -1,31 +0,0 @@
|
||||
package cz.moneta.test.harness.messaging;
|
||||
|
||||
/**
|
||||
* Message format for IBM MQ.
|
||||
* Defines how messages are encoded and transmitted.
|
||||
*/
|
||||
public enum MqMessageFormat {
|
||||
/**
|
||||
* JSON format - JMS TextMessage with plain JSON string.
|
||||
* Default format for IBM MQ.
|
||||
*/
|
||||
JSON,
|
||||
|
||||
/**
|
||||
* XML format - JMS TextMessage with XML string.
|
||||
* XML is decoded and can be queried using XPath.
|
||||
*/
|
||||
XML,
|
||||
|
||||
/**
|
||||
* EBCDIC format - JMS BytesMessage with EBCDIC IBM-870 encoding.
|
||||
* Used for mainframe systems (Czech/Slovak characters).
|
||||
*/
|
||||
EBCDIC_870,
|
||||
|
||||
/**
|
||||
* UTF-8 format - JMS BytesMessage with UTF-8 (CCSID 1208) encoding.
|
||||
* Used for binary data with explicit UTF-8 encoding.
|
||||
*/
|
||||
UTF8_1208
|
||||
}
|
||||
@ -1,386 +0,0 @@
|
||||
package cz.moneta.test.harness.messaging;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.xpath.XPath;
|
||||
import javax.xml.xpath.XPathConstants;
|
||||
import javax.xml.xpath.XPathFactory;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Represents a received message from a messaging system.
|
||||
* Body is always normalized to a String regardless of source and wire format.
|
||||
* <p>
|
||||
* For Kafka: Avro GenericRecord is automatically converted to JSON.
|
||||
* For IBM MQ (JSON): JSON string from JMS TextMessage.
|
||||
* For IBM MQ (XML): XML string from JMS TextMessage.
|
||||
* For IBM MQ (EBCDIC): byte[] from JMS BytesMessage decoded from IBM-870.
|
||||
*/
|
||||
public class ReceivedMessage {
|
||||
|
||||
private static final ObjectMapper JSON_MAPPER = new ObjectMapper();
|
||||
private static final Pattern JSON_PATH_PATTERN = Pattern.compile("^([\\w.]+)\\[([\\d]+)\\](.*)$");
|
||||
|
||||
private final String body;
|
||||
private final MessageContentType contentType;
|
||||
private final Map<String, String> headers;
|
||||
private final long timestamp;
|
||||
private final String source;
|
||||
private final String key;
|
||||
|
||||
public ReceivedMessage(String body, MessageContentType contentType, Map<String, String> headers,
|
||||
long timestamp, String source, String key) {
|
||||
this.body = body;
|
||||
this.contentType = contentType;
|
||||
this.headers = headers != null ? Collections.unmodifiableMap(new HashMap<>(headers)) : new HashMap<>();
|
||||
this.timestamp = timestamp;
|
||||
this.source = source;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a JSON value using JSON path (dot/bracket notation).
|
||||
* Supports paths like "items[0].sku" or "nested.field".
|
||||
*
|
||||
* @param path JSON path
|
||||
* @return JsonNode for the extracted value
|
||||
*/
|
||||
public JsonNode extractJson(String path) {
|
||||
if (body == null || StringUtils.isEmpty(path)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
JsonNode root = JSON_MAPPER.readTree(body);
|
||||
return evaluateJsonPath(root, path);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to extract JSON path: " + path, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a value using XPath (for XML messages).
|
||||
*
|
||||
* @param xpathExpression XPath expression
|
||||
* @return extracted value as string
|
||||
*/
|
||||
public String extractXml(String xpathExpression) {
|
||||
if (body == null || StringUtils.isEmpty(xpathExpression)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
|
||||
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
|
||||
DocumentBuilder builder = factory.newDocumentBuilder();
|
||||
javax.xml.parsers.DocumentBuilder finalBuilder = builder;
|
||||
|
||||
var document = finalBuilder.parse(new java.io.ByteArrayInputStream(body.getBytes()));
|
||||
document.getDocumentElement().normalize();
|
||||
|
||||
XPath xpath = XPathFactory.newInstance().newXPath();
|
||||
Object result = xpath.evaluate(xpathExpression, document, XPathConstants.NODE);
|
||||
|
||||
if (result instanceof org.w3c.dom.Node domNode) {
|
||||
return domNode.getTextContent();
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to evaluate XPath: " + xpathExpression, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Universal extract method - auto-detects content type and uses appropriate extraction.
|
||||
*
|
||||
* @param expression JSON path or XPath expression
|
||||
* @return extracted value as string
|
||||
*/
|
||||
public String extract(String expression) {
|
||||
return switch (contentType) {
|
||||
case JSON -> extractJson(expression).asText();
|
||||
case XML -> extractXml(expression);
|
||||
case RAW_TEXT -> body;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate JSON path on a JSON node.
|
||||
* Supports dot notation and bracket notation for arrays.
|
||||
*/
|
||||
private JsonNode evaluateJsonPath(JsonNode node, String path) {
|
||||
if (StringUtils.isEmpty(path)) {
|
||||
return node;
|
||||
}
|
||||
|
||||
String[] parts = tokenizePath(path);
|
||||
JsonNode current = node;
|
||||
|
||||
for (String part : parts) {
|
||||
if (current == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (part.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (part.contains("[")) {
|
||||
// Array access
|
||||
Matcher matcher = JSON_PATH_PATTERN.matcher(part);
|
||||
if (matcher.matches()) {
|
||||
String arrayName = matcher.group(1);
|
||||
int index = Integer.parseInt(matcher.group(2));
|
||||
String remaining = matcher.group(3);
|
||||
|
||||
if (current.isArray()) {
|
||||
current = index < current.size() ? current.get(index) : null;
|
||||
} else if (current.isObject()) {
|
||||
JsonNode arrayNode = current.get(arrayName);
|
||||
if (arrayNode != null && arrayNode.isArray()) {
|
||||
current = index < arrayNode.size() ? arrayNode.get(index) : null;
|
||||
} else {
|
||||
current = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Continue with remaining path
|
||||
if (StringUtils.isNotBlank(remaining)) {
|
||||
current = evaluateJsonPath(current, remaining);
|
||||
}
|
||||
} else {
|
||||
current = current.get(part);
|
||||
}
|
||||
} else if (part.contains(".")) {
|
||||
// Navigate through object properties
|
||||
String[] segments = part.split("\\.");
|
||||
for (String segment : segments) {
|
||||
if (StringUtils.isEmpty(segment)) {
|
||||
continue;
|
||||
}
|
||||
current = current.get(segment);
|
||||
}
|
||||
} else {
|
||||
current = current.get(part);
|
||||
}
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tokenize JSON path into segments.
|
||||
*/
|
||||
private String[] tokenizePath(String path) {
|
||||
if (StringUtils.isEmpty(path)) {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
java.util.List<String> tokens = new java.util.ArrayList<>();
|
||||
StringBuilder current = new StringBuilder();
|
||||
boolean inBracket = false;
|
||||
|
||||
for (int i = 0; i < path.length(); i++) {
|
||||
char c = path.charAt(i);
|
||||
if (c == '[') {
|
||||
inBracket = true;
|
||||
current.append(c);
|
||||
} else if (c == ']') {
|
||||
inBracket = false;
|
||||
current.append(c);
|
||||
} else if (c == '.' && !inBracket) {
|
||||
if (current.length() > 0) {
|
||||
tokens.add(current.toString());
|
||||
current.setLength(0);
|
||||
}
|
||||
} else {
|
||||
current.append(c);
|
||||
}
|
||||
}
|
||||
|
||||
if (current.length() > 0) {
|
||||
tokens.add(current.toString());
|
||||
}
|
||||
|
||||
return tokens.toArray(new String[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message key (Kafka message key, null for IBM MQ).
|
||||
*/
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a header value (Kafka header or JMS property).
|
||||
*/
|
||||
public String getHeader(String name) {
|
||||
return headers.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all headers.
|
||||
*/
|
||||
public Map<String, String> getHeaders() {
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message body.
|
||||
*/
|
||||
public String getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the message timestamp.
|
||||
*/
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the content type.
|
||||
*/
|
||||
public MessageContentType getContentType() {
|
||||
return contentType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the source (topic or queue name).
|
||||
*/
|
||||
public String getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize the message body to a Java object.
|
||||
*
|
||||
* @param type target type
|
||||
* @param <T> target type
|
||||
* @return deserialized object
|
||||
*/
|
||||
public <T> T mapTo(Class<T> type) {
|
||||
if (body == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
if (contentType == MessageContentType.XML) {
|
||||
// XML deserialization using JAXB
|
||||
return mapXmlTo(type);
|
||||
} else {
|
||||
// JSON deserialization using Jackson
|
||||
return JSON_MAPPER.readValue(body, type);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to deserialize message to " + type.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize the message body to a Java object for XML.
|
||||
* Uses JAXB-like parsing - simplified for basic XML structures.
|
||||
*/
|
||||
private <T> T mapXmlTo(Class<T> type) {
|
||||
try {
|
||||
// For XML, parse to a simple map structure
|
||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
DocumentBuilder builder = factory.newDocumentBuilder();
|
||||
javax.xml.parsers.DocumentBuilder finalBuilder = builder;
|
||||
|
||||
var document = finalBuilder.parse(new java.io.ByteArrayInputStream(body.getBytes()));
|
||||
document.getDocumentElement().normalize();
|
||||
|
||||
Map<String, Object> xmlMap = xmlToMap(document.getDocumentElement());
|
||||
return JSON_MAPPER.convertValue(xmlMap, type);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to deserialize XML message", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert XML element to Map.
|
||||
*/
|
||||
private Map<String, Object> xmlToMap(org.w3c.dom.Element element) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
for (int i = 0; i < element.getAttributes().getLength(); i++) {
|
||||
org.w3c.dom.NamedNodeMap attributes = element.getAttributes();
|
||||
org.w3c.dom.Node attr = attributes.item(i);
|
||||
result.put("@" + attr.getNodeName(), attr.getNodeValue());
|
||||
}
|
||||
|
||||
// Add children
|
||||
for (int i = 0; i < element.getChildNodes().getLength(); i++) {
|
||||
org.w3c.dom.Node node = element.getChildNodes().item(i);
|
||||
if (node.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE) {
|
||||
org.w3c.dom.Element childElement = (org.w3c.dom.Element) node;
|
||||
String tagName = childElement.getTagName();
|
||||
|
||||
if (childElement.getChildNodes().getLength() == 0) {
|
||||
// Leaf element
|
||||
result.put(tagName, childElement.getTextContent());
|
||||
} else {
|
||||
// Check if all children are elements (complex) or text (simple)
|
||||
boolean hasElement = false;
|
||||
for (int j = 0; j < childElement.getChildNodes().getLength(); j++) {
|
||||
org.w3c.dom.Node childNode = childElement.getChildNodes().item(j);
|
||||
if (childNode.getNodeType() == org.w3c.dom.Node.TEXT_NODE &&
|
||||
StringUtils.isNotBlank(childNode.getTextContent())) {
|
||||
} else if (childNode.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE) {
|
||||
hasElement = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasElement) {
|
||||
Map<String, Object> childMap = xmlToMap(childElement);
|
||||
if (result.containsKey(tagName)) {
|
||||
// Convert to list if multiple elements with same name
|
||||
java.util.List<Object> list = new java.util.ArrayList<>();
|
||||
if (result.get(tagName) instanceof Map) {
|
||||
list.add(result.get(tagName));
|
||||
}
|
||||
list.add(childMap);
|
||||
result.put(tagName, list);
|
||||
} else {
|
||||
result.put(tagName, childMap);
|
||||
}
|
||||
} else {
|
||||
result.put(tagName, childElement.getTextContent());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If element has only text content and no attributes or children, return text
|
||||
if (element.getChildNodes().getLength() == 0) {
|
||||
Map<String, Object> textMap = new HashMap<>();
|
||||
textMap.put("#text", element.getTextContent());
|
||||
return textMap;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ReceivedMessage{" +
|
||||
"contentType=" + contentType +
|
||||
", source='" + source + '\'' +
|
||||
", key='" + key + '\'' +
|
||||
", body='" + body + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
package cz.moneta.test.harness.messaging.exception;
|
||||
|
||||
/**
|
||||
* Exception thrown when connection to messaging system fails.
|
||||
* Includes authentication failures, network issues, etc.
|
||||
*/
|
||||
public class MessagingConnectionException extends MessagingException {
|
||||
|
||||
public MessagingConnectionException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public MessagingConnectionException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public MessagingConnectionException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
package cz.moneta.test.harness.messaging.exception;
|
||||
|
||||
/**
|
||||
* Exception thrown when destination (topic/queue) does not exist or is inaccessible.
|
||||
* Includes permission issues and unknown object errors.
|
||||
*/
|
||||
public class MessagingDestinationException extends MessagingException {
|
||||
|
||||
public MessagingDestinationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public MessagingDestinationException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public MessagingDestinationException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
package cz.moneta.test.harness.messaging.exception;
|
||||
|
||||
/**
|
||||
* Base exception for all messaging-related errors.
|
||||
* Extends RuntimeException for unchecked behavior.
|
||||
*/
|
||||
public abstract class MessagingException extends RuntimeException {
|
||||
|
||||
protected MessagingException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
protected MessagingException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
protected MessagingException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
package cz.moneta.test.harness.messaging.exception;
|
||||
|
||||
/**
|
||||
* Exception thrown when schema validation fails or schema is not found.
|
||||
* Used for Avro schema mismatches or missing schema in Schema Registry.
|
||||
*/
|
||||
public class MessagingSchemaException extends MessagingException {
|
||||
|
||||
public MessagingSchemaException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public MessagingSchemaException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public MessagingSchemaException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
package cz.moneta.test.harness.messaging.exception;
|
||||
|
||||
/**
|
||||
* Exception thrown when message receiving times out.
|
||||
* No matching message found within the specified timeout period.
|
||||
*/
|
||||
public class MessagingTimeoutException extends MessagingException {
|
||||
|
||||
public MessagingTimeoutException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public MessagingTimeoutException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public MessagingTimeoutException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
package cz.moneta.test.harness.messaging.model;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public abstract class Destination {
|
||||
|
||||
private final String name;
|
||||
private final String type;
|
||||
|
||||
protected Destination(String name, String type) {
|
||||
this.name = Objects.requireNonNull(name, "name");
|
||||
this.type = Objects.requireNonNull(type, "type");
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
package cz.moneta.test.harness.messaging.model;
|
||||
|
||||
public enum MessageContentType {
|
||||
JSON,
|
||||
XML,
|
||||
RAW_TEXT
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
package cz.moneta.test.harness.messaging.model;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
|
||||
public enum MqMessageFormat {
|
||||
JSON,
|
||||
XML,
|
||||
EBCDIC_870,
|
||||
UTF8_1208;
|
||||
|
||||
public static MqMessageFormat fromConfig(String value, MqMessageFormat defaultValue) {
|
||||
return Optional.ofNullable(value)
|
||||
.map(v -> v.toUpperCase(Locale.ROOT).replace('-', '_'))
|
||||
.map(MqMessageFormat::valueOf)
|
||||
.orElse(defaultValue);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
package cz.moneta.test.harness.messaging.model;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class Queue extends Destination {
|
||||
|
||||
private final String queueName;
|
||||
private final MqMessageFormat format;
|
||||
|
||||
public Queue(String name, String queueName, MqMessageFormat format) {
|
||||
super(name, "ibmmq");
|
||||
this.queueName = Objects.requireNonNull(queueName, "queueName");
|
||||
this.format = Objects.requireNonNull(format, "format");
|
||||
}
|
||||
|
||||
public String getQueueName() {
|
||||
return queueName;
|
||||
}
|
||||
|
||||
public MqMessageFormat getFormat() {
|
||||
return format;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,167 @@
|
||||
package cz.moneta.test.harness.messaging.model;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.MissingNode;
|
||||
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javax.xml.XMLConstants;
|
||||
import javax.xml.namespace.NamespaceContext;
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.xpath.XPath;
|
||||
import javax.xml.xpath.XPathConstants;
|
||||
import javax.xml.xpath.XPathFactory;
|
||||
import java.io.StringReader;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.w3c.dom.Document;
|
||||
import org.xml.sax.InputSource;
|
||||
|
||||
public class ReceivedMessage {
|
||||
|
||||
private static final Pattern ARRAY_NODE_PATTERN = Pattern.compile("(.*?)\\[([0-9]*?)\\]");
|
||||
private static final ObjectMapper JSON_MAPPER = new ObjectMapper();
|
||||
private static final XmlMapper XML_MAPPER = new XmlMapper();
|
||||
|
||||
private final String body;
|
||||
private final MessageContentType contentType;
|
||||
private final Map<String, String> headers;
|
||||
private final long timestamp;
|
||||
private final String source;
|
||||
|
||||
public ReceivedMessage(String body,
|
||||
MessageContentType contentType,
|
||||
Map<String, String> headers,
|
||||
long timestamp,
|
||||
String source) {
|
||||
this.body = Optional.ofNullable(body).orElse("");
|
||||
this.contentType = Objects.requireNonNull(contentType, "contentType");
|
||||
this.headers = Collections.unmodifiableMap(new LinkedHashMap<>(Optional.ofNullable(headers).orElseGet(Collections::emptyMap)));
|
||||
this.timestamp = timestamp;
|
||||
this.source = Optional.ofNullable(source).orElse("");
|
||||
}
|
||||
|
||||
public JsonNode extractJson(String path) {
|
||||
JsonNode root = readJsonLikeNode();
|
||||
return extractNode(path, root);
|
||||
}
|
||||
|
||||
public String extractXml(String xpathExpression) {
|
||||
try {
|
||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
|
||||
factory.setNamespaceAware(false);
|
||||
DocumentBuilder builder = factory.newDocumentBuilder();
|
||||
Document document = builder.parse(new InputSource(new StringReader(body)));
|
||||
XPath xpath = XPathFactory.newInstance().newXPath();
|
||||
xpath.setNamespaceContext(new EmptyNamespaceContext());
|
||||
return String.valueOf(xpath.evaluate(xpathExpression, document, XPathConstants.STRING));
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Failed to extract xml value for expression: " + xpathExpression, e);
|
||||
}
|
||||
}
|
||||
|
||||
public String extract(String expression) {
|
||||
return switch (contentType) {
|
||||
case JSON -> extractJson(expression).asText();
|
||||
case XML -> extractXml(expression);
|
||||
case RAW_TEXT -> body;
|
||||
};
|
||||
}
|
||||
|
||||
public <T> T mapTo(Class<T> type) {
|
||||
try {
|
||||
return switch (contentType) {
|
||||
case JSON -> JSON_MAPPER.readValue(body, type);
|
||||
case XML -> XML_MAPPER.readValue(body, type);
|
||||
case RAW_TEXT -> {
|
||||
if (String.class.equals(type)) {
|
||||
yield type.cast(body);
|
||||
}
|
||||
throw new IllegalStateException("RAW_TEXT can only be mapped to String");
|
||||
}
|
||||
};
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Failed to map message body", e);
|
||||
}
|
||||
}
|
||||
|
||||
public String getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
public MessageContentType getContentType() {
|
||||
return contentType;
|
||||
}
|
||||
|
||||
public Map<String, String> getHeaders() {
|
||||
return headers;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
public String getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
private JsonNode readJsonLikeNode() {
|
||||
try {
|
||||
return switch (contentType) {
|
||||
case JSON -> JSON_MAPPER.readTree(body);
|
||||
case XML -> XML_MAPPER.readTree(body.getBytes());
|
||||
case RAW_TEXT -> {
|
||||
if (StringUtils.isBlank(body)) {
|
||||
yield MissingNode.getInstance();
|
||||
}
|
||||
yield JSON_MAPPER.readTree(body);
|
||||
}
|
||||
};
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Unable to parse message as JSON-like content", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static JsonNode extractNode(String path, JsonNode rootNode) {
|
||||
return Arrays.stream(path.split("\\."))
|
||||
.filter(StringUtils::isNotEmpty)
|
||||
.reduce(rootNode,
|
||||
(r, p) -> {
|
||||
Matcher matcher = ARRAY_NODE_PATTERN.matcher(p);
|
||||
if (matcher.find()) {
|
||||
return r.path(matcher.group(1)).path(Integer.parseInt(matcher.group(2)));
|
||||
}
|
||||
return r.path(p);
|
||||
},
|
||||
(j1, j2) -> j1);
|
||||
}
|
||||
|
||||
private static final class EmptyNamespaceContext implements NamespaceContext {
|
||||
|
||||
@Override
|
||||
public String getNamespaceURI(String prefix) {
|
||||
return XMLConstants.NULL_NS_URI;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPrefix(String namespaceURI) {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<String> getPrefixes(String namespaceURI) {
|
||||
return Collections.emptyIterator();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
package cz.moneta.test.harness.messaging.model;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class Topic extends Destination {
|
||||
|
||||
private final String topicName;
|
||||
|
||||
public Topic(String name, String topicName) {
|
||||
super(name, "kafka");
|
||||
this.topicName = Objects.requireNonNull(topicName, "topicName");
|
||||
}
|
||||
|
||||
public String getTopicName() {
|
||||
return topicName;
|
||||
}
|
||||
}
|
||||
@ -1,595 +0,0 @@
|
||||
package cz.moneta.test.harness.support.messaging;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import cz.moneta.test.harness.endpoints.imq.ImqFirstVisionEndpoint;
|
||||
import cz.moneta.test.harness.endpoints.imq.ImqFirstVisionQueue;
|
||||
import cz.moneta.test.harness.exception.HarnessException;
|
||||
import cz.moneta.test.harness.messaging.MessageContentType;
|
||||
import cz.moneta.test.harness.messaging.MqMessageFormat;
|
||||
import cz.moneta.test.harness.messaging.ReceivedMessage;
|
||||
import cz.moneta.test.harness.messaging.exception.MessagingTimeoutException;
|
||||
import cz.moneta.test.harness.support.util.FileReader;
|
||||
import cz.moneta.test.harness.support.util.Template;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.*;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Fluent builder for IBM MQ requests.
|
||||
* <p>
|
||||
* Usage:
|
||||
* <pre>{@code
|
||||
* ImqRequest.toQueue(endpoint, queue)
|
||||
* .asJson()
|
||||
* .withPayload("{\"field\": \"value\"}")
|
||||
* .send();
|
||||
*
|
||||
* ImqRequest.fromQueue(endpoint, queue)
|
||||
* .receiveWhere(msg -> msg.extract("field").equals("value"))
|
||||
* .withTimeout(10, TimeUnit.SECONDS)
|
||||
* .andAssertFieldValue("result", "OK");
|
||||
* }</pre>
|
||||
* </p>
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public final class ImqRequest {
|
||||
|
||||
private static final ObjectMapper JSON_MAPPER = new ObjectMapper();
|
||||
|
||||
private static final String PROP_LOGICAL_QUEUE = "LogicalQueue";
|
||||
private static final String PROP_SELECTOR = "Selector";
|
||||
public static final String PROP_JMS_CORRELATION_ID = "JMSCorrelationID";
|
||||
public static final String PROP_JMS_MESSAGE_ID = "JMSMessageID";
|
||||
public static final String PROP_JMS_TYPE = "JMSType";
|
||||
|
||||
private ImqRequest() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Start building a message to send to a queue.
|
||||
*/
|
||||
public static QueuePhase toQueue(ImqFirstVisionEndpoint endpoint, ImqFirstVisionQueue queue) {
|
||||
return new QueueBuilder(endpoint, queue, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start building a message to receive from a queue.
|
||||
*/
|
||||
public static ReceivePhase fromQueue(ImqFirstVisionEndpoint endpoint, ImqFirstVisionQueue queue) {
|
||||
return new QueueBuilder(endpoint, queue, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start building a message to send to a queue by physical name.
|
||||
*/
|
||||
public static QueuePhase toQueue(ImqFirstVisionEndpoint endpoint, String queueName) {
|
||||
return new QueueBuilder(endpoint, null, queueName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start building a message to receive from a queue by physical name.
|
||||
*/
|
||||
public static ReceivePhase fromQueue(ImqFirstVisionEndpoint endpoint, String queueName) {
|
||||
return new QueueBuilder(endpoint, null, queueName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Phase after specifying queue - can send or receive.
|
||||
*/
|
||||
public interface QueuePhase extends PayloadPhase, ReceivePhase {
|
||||
}
|
||||
|
||||
/**
|
||||
* Phase for building message payload.
|
||||
*/
|
||||
public interface PayloadPhase {
|
||||
/**
|
||||
* Set message format to JSON (default).
|
||||
*/
|
||||
QueuePhase asJson();
|
||||
|
||||
/**
|
||||
* Set message format to XML.
|
||||
*/
|
||||
QueuePhase asXml();
|
||||
|
||||
/**
|
||||
* Set message format to EBCDIC (IBM-870).
|
||||
*/
|
||||
QueuePhase asEbcdic();
|
||||
|
||||
/**
|
||||
* Set message format to UTF-8 (CCSID 1208).
|
||||
*/
|
||||
QueuePhase asUtf8();
|
||||
|
||||
/**
|
||||
* Set message payload as JSON string.
|
||||
*/
|
||||
PayloadPhase withPayload(String payload);
|
||||
|
||||
/**
|
||||
* Load payload from resource file.
|
||||
*/
|
||||
PayloadPhase withPayloadFromFile(String path);
|
||||
|
||||
/**
|
||||
* Render payload from template.
|
||||
*/
|
||||
PayloadPhase withPayloadFromTemplate(Template template);
|
||||
|
||||
/**
|
||||
* Add field to JSON payload at root level.
|
||||
*/
|
||||
PayloadPhase addField(String fieldName, Object value);
|
||||
|
||||
/**
|
||||
* Add field to JSON payload at specified path.
|
||||
*/
|
||||
PayloadPhase addField(String path, String fieldName, Object value);
|
||||
|
||||
/**
|
||||
* Append value to JSON array at specified path.
|
||||
*/
|
||||
PayloadPhase appendToArray(String path, Object value);
|
||||
|
||||
/**
|
||||
* Set JMS correlation ID.
|
||||
*/
|
||||
PayloadPhase withCorrelationId(String correlationId);
|
||||
|
||||
/**
|
||||
* Set JMS message ID.
|
||||
*/
|
||||
PayloadPhase withMessageId(String messageId);
|
||||
|
||||
/**
|
||||
* Set JMS type.
|
||||
*/
|
||||
PayloadPhase withJmsType(String jmsType);
|
||||
|
||||
/**
|
||||
* Send the message.
|
||||
*/
|
||||
void send();
|
||||
}
|
||||
|
||||
/**
|
||||
* Phase for receiving messages.
|
||||
*/
|
||||
public interface ReceivePhase {
|
||||
/**
|
||||
* Set JMS message selector.
|
||||
*/
|
||||
ReceivePhase withSelector(String selector);
|
||||
|
||||
/**
|
||||
* Start receiving message matching predicate.
|
||||
*/
|
||||
AwaitingPhase receiveWhere(Predicate<ReceivedMessage> filter);
|
||||
|
||||
/**
|
||||
* Browse messages from queue (non-destructive).
|
||||
*/
|
||||
List<ReceivedMessage> browse(int maxMessages);
|
||||
|
||||
/**
|
||||
* Browse messages from queue with selector (non-destructive).
|
||||
*/
|
||||
List<ReceivedMessage> browse(String selector, int maxMessages);
|
||||
|
||||
/**
|
||||
* Set message format to JSON (default) when receiving.
|
||||
*/
|
||||
QueuePhase asJson();
|
||||
|
||||
/**
|
||||
* Set message format to XML when receiving.
|
||||
*/
|
||||
QueuePhase asXml();
|
||||
|
||||
/**
|
||||
* Set message format to EBCDIC (IBM-870) when receiving.
|
||||
*/
|
||||
QueuePhase asEbcdic();
|
||||
|
||||
/**
|
||||
* Set message format to UTF-8 (CCSID 1208) when receiving.
|
||||
*/
|
||||
QueuePhase asUtf8();
|
||||
}
|
||||
|
||||
/**
|
||||
* Phase after specifying filter - await message with timeout.
|
||||
*/
|
||||
public interface AwaitingPhase {
|
||||
/**
|
||||
* Set timeout and get message response for assertions.
|
||||
*/
|
||||
MessageResponse withTimeout(long duration, java.util.concurrent.TimeUnit unit);
|
||||
|
||||
/**
|
||||
* Set timeout duration and get message response for assertions.
|
||||
*/
|
||||
MessageResponse withTimeout(Duration timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Response with fluent assertions.
|
||||
*/
|
||||
public interface MessageResponse extends cz.moneta.test.harness.support.messaging.MessageResponse {
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for queue operations.
|
||||
*/
|
||||
private static class QueueBuilder implements QueuePhase, AwaitingPhase {
|
||||
|
||||
private final ImqFirstVisionEndpoint endpoint;
|
||||
private final ImqFirstVisionQueue logicalQueue;
|
||||
private final String physicalQueue;
|
||||
private String selector;
|
||||
private MqMessageFormat format = MqMessageFormat.JSON;
|
||||
private String payload;
|
||||
private Map<String, Object> fields = new HashMap<>();
|
||||
private List<Map.Entry<String, Object>> arrayAppends = new ArrayList<>();
|
||||
private String correlationId;
|
||||
private String messageId;
|
||||
private String jmsType;
|
||||
private Predicate<ReceivedMessage> filter;
|
||||
private Duration timeout;
|
||||
|
||||
public QueueBuilder(ImqFirstVisionEndpoint endpoint, ImqFirstVisionQueue logicalQueue, String physicalQueue) {
|
||||
this.endpoint = endpoint;
|
||||
this.logicalQueue = logicalQueue;
|
||||
this.physicalQueue = physicalQueue;
|
||||
}
|
||||
|
||||
private String getQueueName() {
|
||||
if (logicalQueue != null) {
|
||||
return endpoint.resolveQueue(logicalQueue);
|
||||
}
|
||||
return physicalQueue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueuePhase asJson() {
|
||||
this.format = MqMessageFormat.JSON;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueuePhase asXml() {
|
||||
this.format = MqMessageFormat.XML;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueuePhase asEbcdic() {
|
||||
this.format = MqMessageFormat.EBCDIC_870;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public QueuePhase asUtf8() {
|
||||
this.format = MqMessageFormat.UTF8_1208;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayloadPhase withPayload(String payload) {
|
||||
this.payload = payload;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayloadPhase withPayloadFromFile(String path) {
|
||||
this.payload = FileReader.readFileFromResources(path);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayloadPhase withPayloadFromTemplate(Template template) {
|
||||
this.payload = template.render();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayloadPhase addField(String fieldName, Object value) {
|
||||
return addField("", fieldName, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayloadPhase addField(String path, String fieldName, Object value) {
|
||||
String key = StringUtils.isNotBlank(path) && StringUtils.isNotBlank(fieldName) ? path + "." + fieldName : fieldName;
|
||||
this.fields.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayloadPhase appendToArray(String path, Object value) {
|
||||
this.arrayAppends.add(Map.entry(path, value));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayloadPhase withCorrelationId(String correlationId) {
|
||||
this.correlationId = correlationId;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayloadPhase withMessageId(String messageId) {
|
||||
this.messageId = messageId;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PayloadPhase withJmsType(String jmsType) {
|
||||
this.jmsType = jmsType;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send() {
|
||||
String finalPayload = buildPayload();
|
||||
Map<String, String> properties = new HashMap<>();
|
||||
|
||||
if (logicalQueue != null) {
|
||||
properties.put(PROP_LOGICAL_QUEUE, logicalQueue.name());
|
||||
}
|
||||
if (selector != null && !selector.isBlank()) {
|
||||
properties.put(PROP_SELECTOR, selector);
|
||||
}
|
||||
if (correlationId != null) {
|
||||
properties.put(PROP_JMS_CORRELATION_ID, correlationId);
|
||||
}
|
||||
if (messageId != null) {
|
||||
properties.put(PROP_JMS_MESSAGE_ID, messageId);
|
||||
}
|
||||
if (jmsType != null) {
|
||||
properties.put(PROP_JMS_TYPE, jmsType);
|
||||
}
|
||||
|
||||
endpoint.send(getQueueName(), finalPayload, format, properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReceivePhase withSelector(String selector) {
|
||||
this.selector = selector;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AwaitingPhase receiveWhere(Predicate<ReceivedMessage> filter) {
|
||||
this.filter = filter;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ReceivedMessage> browse(int maxMessages) {
|
||||
return browse(selector, maxMessages);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ReceivedMessage> browse(String sel, int maxMessages) {
|
||||
return endpoint.browse(getQueueName(), sel, format, maxMessages);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageResponse withTimeout(long duration, java.util.concurrent.TimeUnit unit) {
|
||||
return withTimeout(Duration.of(duration, java.time.temporal.ChronoUnit.MILLIS));
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageResponse withTimeout(Duration timeout) {
|
||||
this.timeout = timeout;
|
||||
|
||||
if (filter == null) {
|
||||
throw new IllegalStateException("Must specify receiveWhere filter before withTimeout");
|
||||
}
|
||||
|
||||
ReceivedMessage message = receiveMessage();
|
||||
return new ResponseImpl(message);
|
||||
}
|
||||
|
||||
private ReceivedMessage receiveMessage() {
|
||||
long startTime = System.currentTimeMillis();
|
||||
long timeoutMs = timeout.toMillis();
|
||||
|
||||
while (System.currentTimeMillis() - startTime < timeoutMs) {
|
||||
try {
|
||||
ReceivedMessage message = endpoint.receive(getQueueName(), selector, format, Duration.ofSeconds(1));
|
||||
if (filter.test(message)) {
|
||||
return message;
|
||||
}
|
||||
} catch (MessagingTimeoutException e) {
|
||||
// Continue polling
|
||||
}
|
||||
}
|
||||
|
||||
throw new MessagingTimeoutException(
|
||||
"No message matching filter found on queue '" + getQueueName() +
|
||||
"' within " + timeout.toMillis() + "ms");
|
||||
}
|
||||
|
||||
private String buildPayload() {
|
||||
if (payload == null) {
|
||||
return "{}";
|
||||
}
|
||||
|
||||
if (fields.isEmpty() && arrayAppends.isEmpty()) {
|
||||
return payload;
|
||||
}
|
||||
|
||||
try {
|
||||
Map<String, Object> json = JSON_MAPPER.readValue(payload, Map.class);
|
||||
|
||||
for (Map.Entry<String, Object> entry : fields.entrySet()) {
|
||||
setField(json, entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
for (Map.Entry<String, Object> entry : arrayAppends) {
|
||||
appendToArray(json, entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
return JSON_MAPPER.writeValueAsString(json);
|
||||
} catch (Exception e) {
|
||||
throw new HarnessException("Failed to build payload", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void setField(Map<String, Object> json, String path, Object value) {
|
||||
if (StringUtils.isBlank(path)) {
|
||||
json.put(path, value);
|
||||
return;
|
||||
}
|
||||
|
||||
String[] parts = path.split("\\.");
|
||||
Map<String, Object> current = json;
|
||||
|
||||
for (int i = 0; i < parts.length - 1; i++) {
|
||||
String part = parts[i];
|
||||
Object next = current.get(part);
|
||||
if (!(next instanceof Map)) {
|
||||
next = new HashMap<String, Object>();
|
||||
current.put(part, next);
|
||||
}
|
||||
current = (Map<String, Object>) next;
|
||||
}
|
||||
|
||||
current.put(parts[parts.length - 1], value);
|
||||
}
|
||||
|
||||
private void appendToArray(Map<String, Object> json, String path, Object value) {
|
||||
String[] parts = path.split("\\.");
|
||||
Map<String, Object> current = json;
|
||||
|
||||
for (int i = 0; i < parts.length - 1; i++) {
|
||||
String part = parts[i];
|
||||
Object next = current.get(part);
|
||||
if (!(next instanceof Map)) {
|
||||
next = new HashMap<String, Object>();
|
||||
current.put(part, next);
|
||||
}
|
||||
current = (Map<String, Object>) next;
|
||||
}
|
||||
|
||||
String arrayKey = parts[parts.length - 1];
|
||||
Object arrayValue = current.get(arrayKey);
|
||||
List<Map<String, Object>> array;
|
||||
if (arrayValue instanceof List) {
|
||||
array = (List<Map<String, Object>>) arrayValue;
|
||||
} else {
|
||||
array = new ArrayList<>();
|
||||
current.put(arrayKey, array);
|
||||
}
|
||||
array.add((Map<String, Object>) value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Response implementation with assertions.
|
||||
*/
|
||||
private static class ResponseImpl implements MessageResponse {
|
||||
|
||||
private final ReceivedMessage message;
|
||||
|
||||
public ResponseImpl(ReceivedMessage message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageResponse andAssertFieldValue(String path, String value) {
|
||||
String actual = message.extract(path);
|
||||
if (!Objects.equals(value, actual)) {
|
||||
throw new AssertionError(String.format("Expected field '%s' to be '%s' but was '%s'", path, value, actual));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageResponse andAssertPresent(String path) {
|
||||
JsonPathValue extracted = new JsonPathValue(message.extract(path));
|
||||
if (extracted.asText() == null && !isPresentInJson(path)) {
|
||||
throw new AssertionError(String.format("Expected field '%s' to be present", path));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageResponse andAssertNotPresent(String path) {
|
||||
if (isPresentInJson(path)) {
|
||||
throw new AssertionError(String.format("Expected field '%s' to be absent", path));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageResponse andAssertHeaderValue(String headerName, String value) {
|
||||
String actual = message.getHeader(headerName);
|
||||
if (!Objects.equals(value, actual)) {
|
||||
throw new AssertionError(String.format("Expected header '%s' to be '%s' but was '%s'", headerName, value, actual));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageResponse andAssertBodyContains(String substring) {
|
||||
if (!message.getBody().contains(substring)) {
|
||||
throw new AssertionError(String.format("Body does not contain '%s'", substring));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonPathValue extract(String path) {
|
||||
return new JsonPathValue(message.extract(path));
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T mapTo(Class<T> type) {
|
||||
return message.mapTo(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public cz.moneta.test.harness.support.messaging.ReceivedMessage getMessage() {
|
||||
return cz.moneta.test.harness.support.messaging.ReceivedMessage.fromMessagingReceivedMessage(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBody() {
|
||||
return message.getBody();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHeader(String name) {
|
||||
return message.getHeader(name);
|
||||
}
|
||||
|
||||
private boolean isPresentInJson(String path) {
|
||||
try {
|
||||
String body = message.getBody();
|
||||
if (message.getContentType() != MessageContentType.JSON) {
|
||||
return false;
|
||||
}
|
||||
JsonNode node = JSON_MAPPER.readTree(body);
|
||||
JsonNode target = extractNode(path, node);
|
||||
return target != null && !target.isMissingNode();
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private JsonNode extractNode(String path, JsonNode node) {
|
||||
return Arrays.stream(path.split("\\."))
|
||||
.filter(StringUtils::isNotEmpty)
|
||||
.reduce(node,
|
||||
(n, p) -> n.isContainerNode() ? n.get(p) : n,
|
||||
(n1, n2) -> n1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,65 +0,0 @@
|
||||
package cz.moneta.test.harness.support.messaging;
|
||||
|
||||
/**
|
||||
* Wrapper for extracted JSON path value.
|
||||
*/
|
||||
public class JsonPathValue {
|
||||
|
||||
private final String value;
|
||||
|
||||
public JsonPathValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value as String.
|
||||
*/
|
||||
public String asText() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value as Boolean.
|
||||
*/
|
||||
public Boolean asBoolean() {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
return Boolean.parseBoolean(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value as Integer.
|
||||
*/
|
||||
public Integer asInteger() {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return Integer.parseInt(value);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException("Cannot parse '" + value + "' as Integer", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value as Long.
|
||||
*/
|
||||
public Long asLong() {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return Long.parseLong(value);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IllegalArgumentException("Cannot parse '" + value + "' as Long", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the underlying value.
|
||||
*/
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
package cz.moneta.test.harness.support.messaging;
|
||||
|
||||
/**
|
||||
* Content type of a received message.
|
||||
*/
|
||||
public enum MessageContentType {
|
||||
/**
|
||||
* JSON content - body is a JSON string, can use dot-path or bracket notation for extraction
|
||||
*/
|
||||
JSON,
|
||||
/**
|
||||
* XML content - body is an XML string, can use XPath for extraction
|
||||
*/
|
||||
XML,
|
||||
/**
|
||||
* Raw text content - body is plain text without structured format
|
||||
*/
|
||||
RAW_TEXT
|
||||
}
|
||||
@ -1,90 +0,0 @@
|
||||
package cz.moneta.test.harness.support.messaging;
|
||||
|
||||
/**
|
||||
* Response from a messaging operation (send/receive).
|
||||
* Provides fluent assertion API for received messages.
|
||||
*/
|
||||
public interface MessageResponse {
|
||||
|
||||
/**
|
||||
* Asserts that a field has the expected value.
|
||||
*
|
||||
* @param path the field path (JSON dot-path for JSON, XPath for XML)
|
||||
* @param value the expected value
|
||||
* @return this for method chaining
|
||||
*/
|
||||
MessageResponse andAssertFieldValue(String path, String value);
|
||||
|
||||
/**
|
||||
* Asserts that a field is present in the message.
|
||||
*
|
||||
* @param path the field path
|
||||
* @return this for method chaining
|
||||
*/
|
||||
MessageResponse andAssertPresent(String path);
|
||||
|
||||
/**
|
||||
* Asserts that a field is NOT present in the message.
|
||||
*
|
||||
* @param path the field path
|
||||
* @return this for method chaining
|
||||
*/
|
||||
MessageResponse andAssertNotPresent(String path);
|
||||
|
||||
/**
|
||||
* Asserts that a header has the expected value.
|
||||
*
|
||||
* @param headerName the header name
|
||||
* @param value the expected value
|
||||
* @return this for method chaining
|
||||
*/
|
||||
MessageResponse andAssertHeaderValue(String headerName, String value);
|
||||
|
||||
/**
|
||||
* Asserts that the body contains the given substring.
|
||||
* Useful for raw text or EBCDIC/UTF-8 encoded messages.
|
||||
*
|
||||
* @param substring the expected substring
|
||||
* @return this for method chaining
|
||||
*/
|
||||
MessageResponse andAssertBodyContains(String substring);
|
||||
|
||||
/**
|
||||
* Extracts a value from the message.
|
||||
*
|
||||
* @param path the path expression
|
||||
* @return JsonPathValue for further assertion
|
||||
*/
|
||||
JsonPathValue extract(String path);
|
||||
|
||||
/**
|
||||
* Deserializes the message body into a Java object.
|
||||
*
|
||||
* @param type the target class
|
||||
* @param <T> the target type
|
||||
* @return the deserialized object
|
||||
*/
|
||||
<T> T mapTo(Class<T> type);
|
||||
|
||||
/**
|
||||
* Returns the received message.
|
||||
*
|
||||
* @return the message
|
||||
*/
|
||||
ReceivedMessage getMessage();
|
||||
|
||||
/**
|
||||
* Returns the message body.
|
||||
*
|
||||
* @return the body
|
||||
*/
|
||||
String getBody();
|
||||
|
||||
/**
|
||||
* Returns a header value.
|
||||
*
|
||||
* @param name the header name
|
||||
* @return the header value or null
|
||||
*/
|
||||
String getHeader(String name);
|
||||
}
|
||||
@ -0,0 +1,193 @@
|
||||
package cz.moneta.test.harness.support.messaging;
|
||||
|
||||
import cz.moneta.test.harness.endpoints.messaging.MessagingEndpoint;
|
||||
import cz.moneta.test.harness.messaging.model.MqMessageFormat;
|
||||
import cz.moneta.test.harness.messaging.model.ReceivedMessage;
|
||||
import cz.moneta.test.harness.support.util.FileReader;
|
||||
import cz.moneta.test.harness.support.util.Template;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public final class MessagingRequest {
|
||||
|
||||
private static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(30);
|
||||
|
||||
private final MessagingEndpoint endpoint;
|
||||
private String destinationName;
|
||||
private String key;
|
||||
private String payload;
|
||||
private Duration timeout = DEFAULT_TIMEOUT;
|
||||
private final Map<String, String> headers = new LinkedHashMap<>();
|
||||
private MqMessageFormat formatOverride;
|
||||
private Predicate<ReceivedMessage> receiveFilter;
|
||||
private ReceivedMessage receivedMessage;
|
||||
private boolean receivePending;
|
||||
private Mode mode = Mode.UNSET;
|
||||
|
||||
private MessagingRequest(MessagingEndpoint endpoint) {
|
||||
this.endpoint = endpoint;
|
||||
}
|
||||
|
||||
public static MessagingRequest builder(MessagingEndpoint endpoint) {
|
||||
return new MessagingRequest(endpoint);
|
||||
}
|
||||
|
||||
public MessagingRequest to(String destinationName) {
|
||||
this.destinationName = destinationName;
|
||||
this.mode = Mode.SEND;
|
||||
resetReceiveState();
|
||||
return this;
|
||||
}
|
||||
|
||||
public MessagingRequest from(String destinationName) {
|
||||
this.destinationName = destinationName;
|
||||
this.mode = Mode.RECEIVE;
|
||||
resetReceiveState();
|
||||
return this;
|
||||
}
|
||||
|
||||
public MessagingRequest withKey(String key) {
|
||||
this.key = key;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MessagingRequest withPayload(String payload) {
|
||||
this.payload = payload;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MessagingRequest withPayloadFromTemplate(Template template) {
|
||||
this.payload = template.render();
|
||||
return this;
|
||||
}
|
||||
|
||||
public MessagingRequest withPayloadFromFile(String path) {
|
||||
this.payload = FileReader.readFileFromResources(path);
|
||||
return this;
|
||||
}
|
||||
|
||||
public MessagingRequest withHeader(String key, String value) {
|
||||
this.headers.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public MessagingRequest withTraceparent(String value) {
|
||||
return withHeader("traceparent", value);
|
||||
}
|
||||
|
||||
public MessagingRequest withRequestID(String value) {
|
||||
return withHeader("requestID", value);
|
||||
}
|
||||
|
||||
public MessagingRequest withActivityID(String value) {
|
||||
return withHeader("activityID", value);
|
||||
}
|
||||
|
||||
public MessagingRequest withSourceCodebookId(String value) {
|
||||
return withHeader("sourceCodebookId", value);
|
||||
}
|
||||
|
||||
public MessagingRequest asJson() {
|
||||
this.formatOverride = MqMessageFormat.JSON;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MessagingRequest asXml() {
|
||||
this.formatOverride = MqMessageFormat.XML;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MessagingRequest asEbcdic() {
|
||||
this.formatOverride = MqMessageFormat.EBCDIC_870;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MessagingRequest asUtf8() {
|
||||
this.formatOverride = MqMessageFormat.UTF8_1208;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MessagingRequest withTimeout(long value, TimeUnit unit) {
|
||||
this.timeout = Duration.ofMillis(unit.toMillis(value));
|
||||
if (receivePending && receivedMessage == null) {
|
||||
doReceive();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public MessagingRequest send() {
|
||||
ensureMode(Mode.SEND);
|
||||
if (payload == null) {
|
||||
throw new IllegalStateException("Message payload must be provided before send()");
|
||||
}
|
||||
endpoint.send(destinationName, key, payload, formatOverride, headers);
|
||||
return this;
|
||||
}
|
||||
|
||||
public MessagingRequest receiveWhere(Predicate<ReceivedMessage> filter) {
|
||||
ensureMode(Mode.RECEIVE);
|
||||
this.receiveFilter = filter;
|
||||
this.receivePending = true;
|
||||
this.receivedMessage = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
public MessagingRequest andAssertFieldValue(String expression, String expectedValue) {
|
||||
ReceivedMessage message = getReceivedMessage();
|
||||
Assertions.assertEquals(expectedValue,
|
||||
message.extract(expression),
|
||||
String.format("Unexpected message field value for '%s'. Message body: %s", expression, message.getBody()));
|
||||
return this;
|
||||
}
|
||||
|
||||
public String extract(String expression) {
|
||||
return getReceivedMessage().extract(expression);
|
||||
}
|
||||
|
||||
public ReceivedMessage getReceivedMessage() {
|
||||
ensureMode(Mode.RECEIVE);
|
||||
if (receivedMessage == null) {
|
||||
doReceive();
|
||||
}
|
||||
return receivedMessage;
|
||||
}
|
||||
|
||||
private void doReceive() {
|
||||
if (!receivePending) {
|
||||
receiveFilter = msg -> true;
|
||||
receivePending = true;
|
||||
}
|
||||
|
||||
receivedMessage = endpoint.receive(
|
||||
destinationName,
|
||||
Optional.ofNullable(receiveFilter).orElse(msg -> true),
|
||||
timeout,
|
||||
formatOverride
|
||||
);
|
||||
receivePending = false;
|
||||
}
|
||||
|
||||
private void ensureMode(Mode requiredMode) {
|
||||
if (this.mode != requiredMode) {
|
||||
throw new IllegalStateException("Messaging request is not in " + requiredMode + " mode");
|
||||
}
|
||||
}
|
||||
|
||||
private void resetReceiveState() {
|
||||
this.receiveFilter = null;
|
||||
this.receivedMessage = null;
|
||||
this.receivePending = false;
|
||||
}
|
||||
|
||||
private enum Mode {
|
||||
UNSET,
|
||||
SEND,
|
||||
RECEIVE
|
||||
}
|
||||
}
|
||||
@ -1,23 +0,0 @@
|
||||
package cz.moneta.test.harness.support.messaging;
|
||||
|
||||
/**
|
||||
* Message format for IBM MQ messages.
|
||||
*/
|
||||
public enum MqMessageFormat {
|
||||
/**
|
||||
* JSON format - JMS TextMessage with plain JSON string (UTF-8 default)
|
||||
*/
|
||||
JSON,
|
||||
/**
|
||||
* XML format - JMS TextMessage with XML string (UTF-8 default)
|
||||
*/
|
||||
XML,
|
||||
/**
|
||||
* EBCDIC format - JMS BytesMessage with EBCDIC IBM-870 encoding (CZ/SK mainframe)
|
||||
*/
|
||||
EBCDIC_870,
|
||||
/**
|
||||
* UTF-8 format - JMS BytesMessage with UTF-8 IBM CCSID 1208 encoding
|
||||
*/
|
||||
UTF8_1208
|
||||
}
|
||||
@ -1,304 +0,0 @@
|
||||
package cz.moneta.test.harness.support.messaging;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.xpath.XPath;
|
||||
import javax.xml.xpath.XPathExpressionException;
|
||||
import javax.xml.xpath.XPathFactory;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import org.w3c.dom.Document;
|
||||
|
||||
/**
|
||||
* Represents a received message from a messaging system (IBM MQ or Kafka).
|
||||
* <p>
|
||||
* Provides unified API for accessing message content regardless of source system.
|
||||
* Body is always normalized to a String, with content type detection for proper extraction.
|
||||
* </p>
|
||||
*/
|
||||
public class ReceivedMessage {
|
||||
|
||||
private static final ObjectMapper JSON_MAPPER = new ObjectMapper();
|
||||
private static final XmlMapper XML_MAPPER = new XmlMapper();
|
||||
|
||||
private final String body;
|
||||
private final MessageContentType contentType;
|
||||
private final Map<String, String> headers;
|
||||
private final long timestamp;
|
||||
private final String source;
|
||||
private final String key;
|
||||
|
||||
private ReceivedMessage(Builder builder) {
|
||||
this.body = builder.body;
|
||||
this.contentType = builder.contentType;
|
||||
this.headers = builder.headers != null ? Collections.unmodifiableMap(new HashMap<>(builder.headers)) : Collections.emptyMap();
|
||||
this.timestamp = builder.timestamp;
|
||||
this.source = builder.source;
|
||||
this.key = builder.key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new builder for ReceivedMessage.
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a value from the message body using JSON dot-path or bracket notation.
|
||||
* <p>
|
||||
* Supports paths like:
|
||||
* <ul>
|
||||
* <li>{@code "field"} - top-level field</li>
|
||||
* <li>{@code "parent.child"} - nested field</li>
|
||||
* <li>{@code "items[0]"} - array element</li>
|
||||
* <li>{@code "items[0].name"} - nested field in array element</li>
|
||||
* </ul>
|
||||
* </p>
|
||||
*
|
||||
* @param path the JSON path expression
|
||||
* @return the extracted value as JsonNode
|
||||
*/
|
||||
public JsonNode extractJson(String path) {
|
||||
if (contentType != MessageContentType.JSON) {
|
||||
throw new IllegalStateException("JSON extraction is only supported for JSON content type, got: " + contentType);
|
||||
}
|
||||
try {
|
||||
JsonNode rootNode = JSON_MAPPER.readTree(body);
|
||||
return extractNode(path, rootNode);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to parse JSON body: " + body, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a value from XML message body using XPath expression.
|
||||
*
|
||||
* @param xpath the XPath expression
|
||||
* @return the extracted value as String
|
||||
*/
|
||||
public String extractXml(String xpath) {
|
||||
if (contentType != MessageContentType.XML) {
|
||||
throw new IllegalStateException("XML extraction is only supported for XML content type, got: " + contentType);
|
||||
}
|
||||
try {
|
||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
|
||||
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
|
||||
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
|
||||
DocumentBuilder builder = factory.newDocumentBuilder();
|
||||
Document doc = builder.parse(new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8)));
|
||||
XPath xPath = XPathFactory.newInstance().newXPath();
|
||||
return xPath.evaluate(xpath, doc);
|
||||
} catch (XPathExpressionException e) {
|
||||
throw new RuntimeException("Failed to evaluate XPath: " + xpath, e);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to parse XML body: " + body, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a value from the message body. Auto-detects content type and uses appropriate extraction method.
|
||||
*
|
||||
* @param expression the path expression (JSON path for JSON, XPath for XML)
|
||||
* @return the extracted value as String
|
||||
*/
|
||||
public String extract(String expression) {
|
||||
return switch (contentType) {
|
||||
case JSON -> extractJson(expression).asText();
|
||||
case XML -> extractXml(expression);
|
||||
case RAW_TEXT -> body;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the message body as a String.
|
||||
*
|
||||
* @return the body content
|
||||
*/
|
||||
public String getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the message content type.
|
||||
*
|
||||
* @return the content type
|
||||
*/
|
||||
public MessageContentType getContentType() {
|
||||
return contentType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a header value by name.
|
||||
*
|
||||
* @param name the header name
|
||||
* @return the header value, or null if not present
|
||||
*/
|
||||
public String getHeader(String name) {
|
||||
return headers.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all headers.
|
||||
*
|
||||
* @return unmodifiable map of headers
|
||||
*/
|
||||
public Map<String, String> getHeaders() {
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the message timestamp.
|
||||
*
|
||||
* @return timestamp in milliseconds
|
||||
*/
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the source (topic name for Kafka, queue name for IBM MQ).
|
||||
*
|
||||
* @return the source name
|
||||
*/
|
||||
public String getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the message key (Kafka only, null for IBM MQ).
|
||||
*
|
||||
* @return the message key or null
|
||||
*/
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes the message body into a Java object.
|
||||
* <p>
|
||||
* For JSON content: uses Jackson ObjectMapper.readValue
|
||||
* For XML content: uses Jackson XmlMapper
|
||||
* </p>
|
||||
*
|
||||
* @param type the target class
|
||||
* @param <T> the target type
|
||||
* @return the deserialized object
|
||||
*/
|
||||
public <T> T mapTo(Class<T> type) {
|
||||
try {
|
||||
if (contentType == MessageContentType.XML) {
|
||||
return XML_MAPPER.readValue(body, type);
|
||||
} else {
|
||||
return JSON_MAPPER.readValue(body, type);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to deserialize message body to " + type.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for ReceivedMessage.
|
||||
*/
|
||||
public static class Builder {
|
||||
private String body;
|
||||
private MessageContentType contentType = MessageContentType.JSON;
|
||||
private Map<String, String> headers;
|
||||
private long timestamp = System.currentTimeMillis();
|
||||
private String source;
|
||||
private String key;
|
||||
|
||||
public Builder body(String body) {
|
||||
this.body = body;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder contentType(MessageContentType contentType) {
|
||||
this.contentType = contentType;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder headers(Map<String, String> headers) {
|
||||
this.headers = headers;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder timestamp(long timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder source(String source) {
|
||||
this.source = source;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder key(String key) {
|
||||
this.key = key;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ReceivedMessage build() {
|
||||
return new ReceivedMessage(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts a node from JSON using dot/bracket notation.
|
||||
*/
|
||||
private static JsonNode extractNode(String path, JsonNode rootNode) {
|
||||
Pattern arrayPattern = Pattern.compile("(.*?)\\[([0-9]+)\\]");
|
||||
return Arrays.stream(path.split("\\."))
|
||||
.filter(StringUtils::isNotEmpty)
|
||||
.reduce(rootNode,
|
||||
(node, part) -> {
|
||||
Matcher matcher = arrayPattern.matcher(part);
|
||||
if (matcher.find()) {
|
||||
return node.path(matcher.group(1)).path(Integer.parseInt(matcher.group(2)));
|
||||
} else {
|
||||
return node.path(part);
|
||||
}
|
||||
},
|
||||
(n1, n2) -> n1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a cz.moneta.test.harness.messaging.ReceivedMessage to this class.
|
||||
*
|
||||
* @param other the message to convert
|
||||
* @return converted message
|
||||
*/
|
||||
public static ReceivedMessage fromMessagingReceivedMessage(
|
||||
cz.moneta.test.harness.messaging.ReceivedMessage other) {
|
||||
if (other == null) {
|
||||
return null;
|
||||
}
|
||||
cz.moneta.test.harness.support.messaging.MessageContentType contentType =
|
||||
switch (other.getContentType()) {
|
||||
case JSON -> cz.moneta.test.harness.support.messaging.MessageContentType.JSON;
|
||||
case XML -> cz.moneta.test.harness.support.messaging.MessageContentType.XML;
|
||||
case RAW_TEXT -> cz.moneta.test.harness.support.messaging.MessageContentType.RAW_TEXT;
|
||||
};
|
||||
return builder()
|
||||
.body(other.getBody())
|
||||
.contentType(contentType)
|
||||
.headers(other.getHeaders())
|
||||
.timestamp(other.getTimestamp())
|
||||
.source(other.getSource())
|
||||
.key(other.getKey())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@ -1,568 +0,0 @@
|
||||
package cz.moneta.test.harness.support.messaging.kafka;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
import cz.moneta.test.harness.endpoints.kafka.KafkaEndpoint;
|
||||
import cz.moneta.test.harness.support.util.FileReader;
|
||||
|
||||
/**
|
||||
* Fluent builder for creating and sending Kafka messages.
|
||||
* <p>
|
||||
* Usage:
|
||||
* <pre>{@code
|
||||
* harness.withKafka()
|
||||
* .toTopic("order-events")
|
||||
* .withKey("order-123")
|
||||
* .withPayload("{\"orderId\": \"123\", \"status\": \"CREATED\"}")
|
||||
* .withTraceparent("00-traceId-spanId-01")
|
||||
* .send();
|
||||
* }</pre>
|
||||
* </p>
|
||||
*/
|
||||
public class KafkaRequest {
|
||||
|
||||
private static final ObjectMapper MAPPER = new ObjectMapper();
|
||||
private static final Pattern ARRAY_NODE_PATTERN = Pattern.compile("(.*?)\\[([0-9]*?)\\]");
|
||||
|
||||
private KafkaRequest() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Kafka request builder.
|
||||
*/
|
||||
public static KafkaBuilder builder(KafkaEndpoint endpoint) {
|
||||
return new KafkaRequestBuilder(endpoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Phase 1: Select direction (send or receive).
|
||||
*/
|
||||
public interface KafkaBuilder {
|
||||
/**
|
||||
* Specifies the target topic for sending.
|
||||
*
|
||||
* @param topic Kafka topic name
|
||||
* @return KafkaPayloadPhase for payload configuration
|
||||
*/
|
||||
KafkaPayloadPhase toTopic(String topic);
|
||||
|
||||
/**
|
||||
* Specifies the source topic for receiving.
|
||||
*
|
||||
* @param topic Kafka topic name
|
||||
* @return KafkaReceiveFilterPhase for filter configuration
|
||||
*/
|
||||
KafkaReceiveFilterPhase fromTopic(String topic);
|
||||
}
|
||||
|
||||
/**
|
||||
* Phase 2a: Configure payload and headers for sending.
|
||||
*/
|
||||
public interface KafkaPayloadPhase {
|
||||
/**
|
||||
* Sets the message key.
|
||||
*
|
||||
* @param key Message key
|
||||
* @return this for chaining
|
||||
*/
|
||||
KafkaPayloadPhase withKey(String key);
|
||||
|
||||
/**
|
||||
* Sets the payload as JSON string.
|
||||
*
|
||||
* @param json JSON payload
|
||||
* @return this for chaining
|
||||
*/
|
||||
KafkaPayloadPhase withPayload(String json);
|
||||
|
||||
/**
|
||||
* Loads payload from a file.
|
||||
*
|
||||
* @param path Path to JSON file in resources
|
||||
* @return this for chaining
|
||||
*/
|
||||
KafkaPayloadPhase withPayloadFromFile(String path);
|
||||
|
||||
/**
|
||||
* Loads payload from a template file and renders it.
|
||||
*
|
||||
* @param path Path to template file
|
||||
* @param variables Variables for template rendering
|
||||
* @return this for chaining
|
||||
*/
|
||||
KafkaPayloadPhase withPayloadFromTemplate(String path, Map<String, Object> variables);
|
||||
|
||||
/**
|
||||
* Adds a field to the JSON payload.
|
||||
*
|
||||
* @param fieldName Field name
|
||||
* @param value Field value
|
||||
* @return this for chaining
|
||||
*/
|
||||
KafkaPayloadPhase addField(String fieldName, Object value);
|
||||
|
||||
/**
|
||||
* Adds a field to a nested path in the JSON payload.
|
||||
*
|
||||
* @param path Path to parent node (e.g., "items[1].details")
|
||||
* @param fieldName Field name to add
|
||||
* @param value Field value
|
||||
* @return this for chaining
|
||||
*/
|
||||
KafkaPayloadPhase addField(String path, String fieldName, Object value);
|
||||
|
||||
/**
|
||||
* Appends a value to an array in the JSON payload.
|
||||
*
|
||||
* @param path Path to array (e.g., "items")
|
||||
* @param value Value to append
|
||||
* @return this for chaining
|
||||
*/
|
||||
KafkaPayloadPhase appendToArray(String path, Object value);
|
||||
|
||||
/**
|
||||
* Adds W3C Trace Context traceparent header.
|
||||
*
|
||||
* @param value Traceparent value (e.g., "00-traceId-spanId-01")
|
||||
* @return this for chaining
|
||||
*/
|
||||
KafkaPayloadPhase withTraceparent(String value);
|
||||
|
||||
/**
|
||||
* Adds requestID header.
|
||||
*
|
||||
* @param value Request ID
|
||||
* @return this for chaining
|
||||
*/
|
||||
KafkaPayloadPhase withRequestID(String value);
|
||||
|
||||
/**
|
||||
* Adds activityID header.
|
||||
*
|
||||
* @param value Activity ID
|
||||
* @return this for chaining
|
||||
*/
|
||||
KafkaPayloadPhase withActivityID(String value);
|
||||
|
||||
/**
|
||||
* Adds sourceCodebookId header.
|
||||
*
|
||||
* @param value Source codebook ID
|
||||
* @return this for chaining
|
||||
*/
|
||||
KafkaPayloadPhase withSourceCodebookId(String value);
|
||||
|
||||
/**
|
||||
* Adds a custom header.
|
||||
*
|
||||
* @param key Header name
|
||||
* @param value Header value
|
||||
* @return this for chaining
|
||||
*/
|
||||
KafkaPayloadPhase withHeader(String key, String value);
|
||||
|
||||
/**
|
||||
* Sends the message.
|
||||
*/
|
||||
void send();
|
||||
}
|
||||
|
||||
/**
|
||||
* Phase 2b: Configure filter for receiving.
|
||||
*/
|
||||
public interface KafkaReceiveFilterPhase {
|
||||
/**
|
||||
* Specifies the filter predicate for receiving messages.
|
||||
*
|
||||
* @param filter Predicate to filter messages
|
||||
* @return KafkaAwaitingPhase for timeout configuration
|
||||
*/
|
||||
KafkaAwaitingPhase receiveWhere(Predicate<ReceivedMessage> filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Phase 3: Configure timeout.
|
||||
*/
|
||||
public interface KafkaAwaitingPhase {
|
||||
/**
|
||||
* Sets the timeout for waiting for a message.
|
||||
*
|
||||
* @param duration Timeout duration
|
||||
* @param unit Time unit
|
||||
* @return MessageResponse for assertions
|
||||
*/
|
||||
MessageResponse withTimeout(long duration, TimeUnit unit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal implementation class.
|
||||
*/
|
||||
private static class KafkaRequestBuilder implements
|
||||
KafkaBuilder,
|
||||
KafkaPayloadPhase, KafkaReceiveFilterPhase, KafkaAwaitingPhase,
|
||||
MessageResponse {
|
||||
|
||||
private final KafkaEndpoint endpoint;
|
||||
|
||||
// Send configuration
|
||||
private String topic;
|
||||
private String key;
|
||||
private String payload;
|
||||
private final Map<String, String> headers = new HashMap<>();
|
||||
|
||||
// Receive configuration
|
||||
private Predicate<ReceivedMessage> filter;
|
||||
private Duration timeout;
|
||||
|
||||
// Response (after receive)
|
||||
private List<ReceivedMessage> messages;
|
||||
|
||||
public KafkaRequestBuilder(KafkaEndpoint endpoint) {
|
||||
this.endpoint = endpoint;
|
||||
}
|
||||
|
||||
// === Send phase ===
|
||||
|
||||
@Override
|
||||
public KafkaPayloadPhase toTopic(String topic) {
|
||||
this.topic = topic;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KafkaPayloadPhase withKey(String key) {
|
||||
this.key = key;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KafkaPayloadPhase withPayload(String json) {
|
||||
this.payload = json;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KafkaPayloadPhase withPayloadFromFile(String path) {
|
||||
this.payload = FileReader.readFileFromResources(path);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KafkaPayloadPhase withPayloadFromTemplate(String path, Map<String, Object> variables) {
|
||||
String templateContent = FileReader.readFileFromResources(path);
|
||||
cz.moneta.test.harness.support.util.Template template =
|
||||
new cz.moneta.test.harness.support.util.Template(templateContent);
|
||||
if (variables != null) {
|
||||
variables.forEach((k, v) -> template.set(k, v != null ? v.toString() : ""));
|
||||
}
|
||||
this.payload = template.render();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KafkaPayloadPhase addField(String fieldName, Object value) {
|
||||
return addField("", fieldName, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KafkaPayloadPhase addField(String path, String fieldName, Object value) {
|
||||
try {
|
||||
if (payload == null) {
|
||||
payload = "{}";
|
||||
}
|
||||
JsonNode rootNode = MAPPER.readTree(payload);
|
||||
JsonNode targetNode = extractNode(path, rootNode);
|
||||
JsonNode newNode = MAPPER.valueToTree(value);
|
||||
((ObjectNode) targetNode).set(fieldName, newNode);
|
||||
payload = MAPPER.writeValueAsString(rootNode);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Failed to add field '" + fieldName + "' at path '" + path + "': " + e.getMessage(), e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KafkaPayloadPhase appendToArray(String path, Object value) {
|
||||
try {
|
||||
if (payload == null) {
|
||||
payload = "{}";
|
||||
}
|
||||
JsonNode rootNode = MAPPER.readTree(payload);
|
||||
JsonNode targetNode = extractNode(path, rootNode);
|
||||
JsonNode newNode = MAPPER.valueToTree(value);
|
||||
if (targetNode.isArray()) {
|
||||
((ArrayNode) targetNode).add(newNode);
|
||||
} else {
|
||||
throw new IllegalStateException("Path '" + path + "' does not point to an array");
|
||||
}
|
||||
payload = MAPPER.writeValueAsString(rootNode);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Failed to append to array at path '" + path + "': " + e.getMessage(), e);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KafkaPayloadPhase withTraceparent(String value) {
|
||||
return withHeader("traceparent", value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KafkaPayloadPhase withRequestID(String value) {
|
||||
return withHeader("requestID", value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KafkaPayloadPhase withActivityID(String value) {
|
||||
return withHeader("activityID", value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KafkaPayloadPhase withSourceCodebookId(String value) {
|
||||
return withHeader("sourceCodebookId", value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public KafkaPayloadPhase withHeader(String key, String value) {
|
||||
this.headers.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send() {
|
||||
if (topic == null) {
|
||||
throw new IllegalStateException("Topic not specified. Call toTopic() first.");
|
||||
}
|
||||
if (payload == null) {
|
||||
throw new IllegalStateException("Payload not specified. Call withPayload() or withPayloadFromFile() first.");
|
||||
}
|
||||
endpoint.send(topic, key, payload, headers);
|
||||
}
|
||||
|
||||
// === Receive phase ===
|
||||
|
||||
@Override
|
||||
public KafkaReceiveFilterPhase fromTopic(String topic) {
|
||||
this.topic = topic;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public KafkaAwaitingPhase receiveWhere(Predicate<ReceivedMessage> filter) {
|
||||
this.filter = filter;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageResponse withTimeout(long duration, TimeUnit unit) {
|
||||
this.timeout = Duration.of(duration, unit.toChronoUnit());
|
||||
messages = endpoint.receive(topic, filter, timeout);
|
||||
return this;
|
||||
}
|
||||
|
||||
// === MessageResponse implementation ===
|
||||
|
||||
@Override
|
||||
public MessageResponse andAssertFieldValue(String path, String value) {
|
||||
if (messages == null || messages.isEmpty()) {
|
||||
throw new IllegalStateException("No message received to assert on");
|
||||
}
|
||||
ReceivedMessage msg = messages.get(0);
|
||||
String actual = msg.extract(path);
|
||||
if (!Objects.equals(value, actual)) {
|
||||
throw new AssertionError(
|
||||
String.format("Field '%s' has value '%s', expected '%s'", path, actual, value));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageResponse andAssertPresent(String path) {
|
||||
if (messages == null || messages.isEmpty()) {
|
||||
throw new IllegalStateException("No message received to assert on");
|
||||
}
|
||||
ReceivedMessage msg = messages.get(0);
|
||||
JsonNode node = msg.extractJson(path);
|
||||
if (node.isMissingNode()) {
|
||||
throw new AssertionError("Field '" + path + "' is missing");
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageResponse andAssertNotPresent(String path) {
|
||||
if (messages == null || messages.isEmpty()) {
|
||||
throw new IllegalStateException("No message received to assert on");
|
||||
}
|
||||
ReceivedMessage msg = messages.get(0);
|
||||
JsonNode node = msg.extractJson(path);
|
||||
if (!node.isMissingNode()) {
|
||||
throw new AssertionError("Field '" + path + "' is present with value: " + node.asText());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageResponse andAssertHeaderValue(String headerName, String value) {
|
||||
if (messages == null || messages.isEmpty()) {
|
||||
throw new IllegalStateException("No message received to assert on");
|
||||
}
|
||||
ReceivedMessage msg = messages.get(0);
|
||||
String actual = msg.getHeader(headerName);
|
||||
if (!Objects.equals(value, actual)) {
|
||||
throw new AssertionError(
|
||||
String.format("Header '%s' has value '%s', expected '%s'", headerName, actual, value));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageResponse andAssertBodyContains(String substring) {
|
||||
if (messages == null || messages.isEmpty()) {
|
||||
throw new IllegalStateException("No message received to assert on");
|
||||
}
|
||||
ReceivedMessage msg = messages.get(0);
|
||||
String body = msg.getBody();
|
||||
if (body == null || !body.contains(substring)) {
|
||||
throw new AssertionError(
|
||||
String.format("Body does not contain '%s'. Actual body: %s", substring, body));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public net.javacrumbs.jsonunit.assertj.JsonAssert.ConfigurableJsonAssert andAssertWithAssertJ() {
|
||||
if (messages == null || messages.isEmpty()) {
|
||||
throw new IllegalStateException("No message received to assert on");
|
||||
}
|
||||
return net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson(messages.get(0).getBody());
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonPathValue extract(String path) {
|
||||
if (messages == null || messages.isEmpty()) {
|
||||
throw new IllegalStateException("No message received to extract from");
|
||||
}
|
||||
JsonNode node = messages.get(0).extractJson(path);
|
||||
return new JsonPathValueImpl(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T mapTo(Class<T> type) {
|
||||
if (messages == null || messages.isEmpty()) {
|
||||
throw new IllegalStateException("No message received to map");
|
||||
}
|
||||
return messages.get(0).mapTo(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReceivedMessage getMessage() {
|
||||
if (messages == null || messages.isEmpty()) {
|
||||
throw new IllegalStateException("No message received");
|
||||
}
|
||||
return messages.get(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBody() {
|
||||
if (messages == null || messages.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return messages.get(0).getBody();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHeader(String name) {
|
||||
if (messages == null || messages.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return messages.get(0).getHeader(name);
|
||||
}
|
||||
|
||||
// === Helper methods ===
|
||||
|
||||
private JsonNode extractNode(String path, JsonNode rootNode) {
|
||||
if (StringUtils.isBlank(path)) {
|
||||
return rootNode;
|
||||
}
|
||||
|
||||
return Arrays.stream(path.split("\\."))
|
||||
.filter(StringUtils::isNotEmpty)
|
||||
.reduce(rootNode,
|
||||
(r, p) -> {
|
||||
Matcher matcher = ARRAY_NODE_PATTERN.matcher(p);
|
||||
if (matcher.find()) {
|
||||
return r.path(matcher.group(1)).path(Integer.valueOf(matcher.group(2)));
|
||||
} else {
|
||||
return r.path(p);
|
||||
}
|
||||
},
|
||||
(j1, j2) -> j1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of JsonPathValue.
|
||||
*/
|
||||
private static class JsonPathValueImpl implements MessageResponse.JsonPathValue {
|
||||
|
||||
private final JsonNode node;
|
||||
|
||||
public JsonPathValueImpl(JsonNode node) {
|
||||
this.node = node;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String asText() {
|
||||
if (node == null || node.isMissingNode()) {
|
||||
return null;
|
||||
}
|
||||
return node.asText();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer asInt() {
|
||||
if (node == null || node.isMissingNode()) {
|
||||
return null;
|
||||
}
|
||||
return node.asInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long asLong() {
|
||||
if (node == null || node.isMissingNode()) {
|
||||
return null;
|
||||
}
|
||||
return node.asLong();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean asBoolean() {
|
||||
if (node == null || node.isMissingNode()) {
|
||||
return null;
|
||||
}
|
||||
return node.asBoolean();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMissing() {
|
||||
return node == null || node.isMissingNode();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
package cz.moneta.test.harness.support.messaging.kafka;
|
||||
|
||||
/**
|
||||
* Enum representing the content type of a received message.
|
||||
*/
|
||||
public enum MessageContentType {
|
||||
/**
|
||||
* JSON content - parsed with Jackson ObjectMapper
|
||||
*/
|
||||
JSON,
|
||||
/**
|
||||
* XML content - can be parsed with XPath or Jackson XmlMapper
|
||||
*/
|
||||
XML,
|
||||
/**
|
||||
* Raw text content - not parsed, returned as-is
|
||||
*/
|
||||
RAW_TEXT
|
||||
}
|
||||
@ -1,139 +0,0 @@
|
||||
package cz.moneta.test.harness.support.messaging.kafka;
|
||||
|
||||
import net.javacrumbs.jsonunit.assertj.JsonAssert;
|
||||
|
||||
/**
|
||||
* Interface representing the response from a message receive operation.
|
||||
* Provides fluent assertion methods for testing received messages.
|
||||
*/
|
||||
public interface MessageResponse {
|
||||
|
||||
/**
|
||||
* Asserts that a field has the expected value.
|
||||
*
|
||||
* @param path JSON path or XPath
|
||||
* @param value expected value as string
|
||||
* @return this for chaining
|
||||
*/
|
||||
MessageResponse andAssertFieldValue(String path, String value);
|
||||
|
||||
/**
|
||||
* Asserts that a field is present.
|
||||
*
|
||||
* @param path JSON path or XPath
|
||||
* @return this for chaining
|
||||
*/
|
||||
MessageResponse andAssertPresent(String path);
|
||||
|
||||
/**
|
||||
* Asserts that a field is not present.
|
||||
*
|
||||
* @param path JSON path or XPath
|
||||
* @return this for chaining
|
||||
*/
|
||||
MessageResponse andAssertNotPresent(String path);
|
||||
|
||||
/**
|
||||
* Asserts that a header has the expected value.
|
||||
*
|
||||
* @param headerName name of the header
|
||||
* @param value expected value
|
||||
* @return this for chaining
|
||||
*/
|
||||
MessageResponse andAssertHeaderValue(String headerName, String value);
|
||||
|
||||
/**
|
||||
* Asserts that the body contains a substring.
|
||||
* Useful for EBCDIC/UTF-8 raw text messages.
|
||||
*
|
||||
* @param substring expected substring
|
||||
* @return this for chaining
|
||||
*/
|
||||
MessageResponse andAssertBodyContains(String substring);
|
||||
|
||||
/**
|
||||
* Returns AssertJ JsonAssert for complex assertions.
|
||||
*
|
||||
* @return JsonAssert instance
|
||||
*/
|
||||
JsonAssert.ConfigurableJsonAssert andAssertWithAssertJ();
|
||||
|
||||
/**
|
||||
* Extracts a value from the message body.
|
||||
*
|
||||
* @param path JSON path or XPath
|
||||
* @return JsonPathValue wrapper for further conversion
|
||||
*/
|
||||
JsonPathValue extract(String path);
|
||||
|
||||
/**
|
||||
* Deserializes the message body to a Java object.
|
||||
*
|
||||
* @param type the target type
|
||||
* @param <T> the target type
|
||||
* @return deserialized object
|
||||
*/
|
||||
<T> T mapTo(Class<T> type);
|
||||
|
||||
/**
|
||||
* Gets the underlying received message.
|
||||
*
|
||||
* @return the received message
|
||||
*/
|
||||
ReceivedMessage getMessage();
|
||||
|
||||
/**
|
||||
* Gets the message body as string.
|
||||
*
|
||||
* @return body string
|
||||
*/
|
||||
String getBody();
|
||||
|
||||
/**
|
||||
* Gets a header value.
|
||||
*
|
||||
* @param name header name
|
||||
* @return header value or null
|
||||
*/
|
||||
String getHeader(String name);
|
||||
|
||||
/**
|
||||
* Interface for extracted path values.
|
||||
*/
|
||||
interface JsonPathValue {
|
||||
/**
|
||||
* Converts to string.
|
||||
*
|
||||
* @return string value
|
||||
*/
|
||||
String asText();
|
||||
|
||||
/**
|
||||
* Converts to integer.
|
||||
*
|
||||
* @return integer value
|
||||
*/
|
||||
Integer asInt();
|
||||
|
||||
/**
|
||||
* Converts to long.
|
||||
*
|
||||
* @return long value
|
||||
*/
|
||||
Long asLong();
|
||||
|
||||
/**
|
||||
* Converts to boolean.
|
||||
*
|
||||
* @return boolean value
|
||||
*/
|
||||
Boolean asBoolean();
|
||||
|
||||
/**
|
||||
* Checks if value is missing/null.
|
||||
*
|
||||
* @return true if value is missing
|
||||
*/
|
||||
boolean isMissing();
|
||||
}
|
||||
}
|
||||
@ -1,365 +0,0 @@
|
||||
package cz.moneta.test.harness.support.messaging.kafka;
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.xpath.XPath;
|
||||
import javax.xml.xpath.XPathConstants;
|
||||
import javax.xml.xpath.XPathFactory;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.InputSource;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
|
||||
|
||||
import net.javacrumbs.jsonunit.assertj.JsonAssert;
|
||||
|
||||
/**
|
||||
* Represents a received message from a messaging system (Kafka or IBM MQ).
|
||||
* Provides unified API for extracting data regardless of the source system.
|
||||
*/
|
||||
public class ReceivedMessage {
|
||||
|
||||
private static final ObjectMapper JSON_MAPPER = new ObjectMapper();
|
||||
private static final XmlMapper XML_MAPPER = new XmlMapper();
|
||||
private static final Pattern ARRAY_NODE_PATTERN = Pattern.compile("(.*?)\\[([0-9]*?)\\]");
|
||||
|
||||
private final String body;
|
||||
private final MessageContentType contentType;
|
||||
private final Map<String, String> headers;
|
||||
private final long timestamp;
|
||||
private final String source;
|
||||
private final String key;
|
||||
|
||||
private ReceivedMessage(Builder builder) {
|
||||
this.body = builder.body;
|
||||
this.contentType = builder.contentType;
|
||||
this.headers = builder.headers != null ? new HashMap<>(builder.headers) : new HashMap<>();
|
||||
this.timestamp = builder.timestamp;
|
||||
this.source = builder.source;
|
||||
this.key = builder.key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new builder for ReceivedMessage.
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts value using JSON path (dot/bracket notation).
|
||||
* For XML content, automatically converts to XPath evaluation.
|
||||
*
|
||||
* @param path JSON path (e.g., "items[0].sku") or XPath for XML
|
||||
* @return JsonNode representing the extracted value
|
||||
*/
|
||||
public JsonNode extractJson(String path) {
|
||||
if (body == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (contentType == MessageContentType.XML) {
|
||||
// For XML, try XPath first, then fall back to JSON path
|
||||
try {
|
||||
return extractAsXml(path);
|
||||
} catch (Exception e) {
|
||||
// Fall through to JSON parsing
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
JsonNode rootNode = JSON_MAPPER.readTree(body);
|
||||
return extractNode(path, rootNode);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to extract JSON path '" + path + "' from body: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts value using XPath expression (for XML messages).
|
||||
*
|
||||
* @param xpath XPath expression (e.g., "/response/balance")
|
||||
* @return String value of the extracted node
|
||||
*/
|
||||
public String extractXml(String xpath) {
|
||||
if (body == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
Document doc = XML_MAPPER.readTree(body).isMissingNode() ? null : XML_MAPPER.readTree(body).isObject()
|
||||
? parseXmlToDocument(body)
|
||||
: parseXmlToDocument(body);
|
||||
|
||||
if (doc == null) {
|
||||
doc = parseXmlToDocument(body);
|
||||
}
|
||||
|
||||
XPath xPath = XPathFactory.newInstance().newXPath();
|
||||
NodeList nodes = (NodeList) xPath.evaluate(xpath, doc, XPathConstants.NODESET);
|
||||
|
||||
if (nodes.getLength() == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return nodes.item(0).getTextContent();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to evaluate XPath '" + xpath + "' on body: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Universal extract method - auto-detects content type and evaluates expression accordingly.
|
||||
*
|
||||
* @param expression JSON path for JSON content, XPath for XML content
|
||||
* @return String value of the extracted node
|
||||
*/
|
||||
public String extract(String expression) {
|
||||
if (body == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return switch (contentType) {
|
||||
case JSON -> extractJson(expression).asText();
|
||||
case XML -> extractXml(expression);
|
||||
case RAW_TEXT -> body;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes the message body to a Java object.
|
||||
*
|
||||
* @param type the target type
|
||||
* @param <T> the target type
|
||||
* @return deserialized object
|
||||
*/
|
||||
public <T> T mapTo(Class<T> type) {
|
||||
if (body == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
if (contentType == MessageContentType.JSON) {
|
||||
return JSON_MAPPER.readValue(body, type);
|
||||
} else if (contentType == MessageContentType.XML) {
|
||||
return XML_MAPPER.readValue(body, type);
|
||||
} else {
|
||||
throw new IllegalStateException("Cannot deserialize RAW_TEXT to " + type.getName());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to deserialize body to " + type.getName() + ": " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes the message body to a list of objects.
|
||||
*
|
||||
* @param type the element type
|
||||
* @param <T> the element type
|
||||
* @return list of deserialized objects
|
||||
*/
|
||||
public <T> List<T> mapToList(Class<T> type) {
|
||||
if (body == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
try {
|
||||
if (contentType == MessageContentType.JSON) {
|
||||
return JSON_MAPPER.readValue(body, new TypeReference<List<T>>() {});
|
||||
} else {
|
||||
throw new IllegalStateException("Cannot deserialize to list for content type " + contentType);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to deserialize body to List<" + type.getName() + ">: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the message body as string.
|
||||
*/
|
||||
public String getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the message key (Kafka) or null (IBM MQ).
|
||||
*/
|
||||
public String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets header value by name (Kafka header or JMS property).
|
||||
*/
|
||||
public String getHeader(String name) {
|
||||
return headers.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all headers.
|
||||
*/
|
||||
public Map<String, String> getHeaders() {
|
||||
return Collections.unmodifiableMap(headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the message timestamp.
|
||||
*/
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the source (topic name for Kafka, queue name for IBM MQ).
|
||||
*/
|
||||
public String getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the content type.
|
||||
*/
|
||||
public MessageContentType getContentType() {
|
||||
return contentType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates JsonAssert for AssertJ-style assertions on the message body.
|
||||
*/
|
||||
public JsonAssert.ConfigurableJsonAssert andAssertWithAssertJ() {
|
||||
if (body == null) {
|
||||
throw new IllegalStateException("Cannot assert on null body");
|
||||
}
|
||||
return net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson(body);
|
||||
}
|
||||
|
||||
private JsonNode extractNode(String path, JsonNode rootNode) {
|
||||
if (StringUtils.isBlank(path)) {
|
||||
return rootNode;
|
||||
}
|
||||
|
||||
return Arrays.stream(path.split("\\."))
|
||||
.filter(StringUtils::isNotEmpty)
|
||||
.reduce(rootNode,
|
||||
(r, p) -> {
|
||||
Matcher matcher = ARRAY_NODE_PATTERN.matcher(p);
|
||||
if (matcher.find()) {
|
||||
return r.path(matcher.group(1)).path(Integer.valueOf(matcher.group(2)));
|
||||
} else {
|
||||
return r.path(p);
|
||||
}
|
||||
},
|
||||
(j1, j2) -> j1);
|
||||
}
|
||||
|
||||
private JsonNode extractAsXml(String path) throws Exception {
|
||||
// Try XPath first
|
||||
try {
|
||||
String value = extractXml(path);
|
||||
if (value != null) {
|
||||
return JSON_MAPPER.valueToTree(value);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Fall back to JSON path on XML body
|
||||
}
|
||||
return extractNode(path, XML_MAPPER.readTree(body));
|
||||
}
|
||||
|
||||
private Document parseXmlToDocument(String xml) throws Exception {
|
||||
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
||||
factory.setFeature(javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING, true);
|
||||
DocumentBuilder builder = factory.newDocumentBuilder();
|
||||
return builder.parse(new InputSource(new StringReader(xml)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for ReceivedMessage.
|
||||
*/
|
||||
public static class Builder {
|
||||
private String body;
|
||||
private MessageContentType contentType = MessageContentType.JSON;
|
||||
private Map<String, String> headers = new HashMap<>();
|
||||
private long timestamp = System.currentTimeMillis();
|
||||
private String source;
|
||||
private String key;
|
||||
|
||||
public Builder body(String body) {
|
||||
this.body = body;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder contentType(MessageContentType contentType) {
|
||||
this.contentType = contentType;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder headers(Map<String, String> headers) {
|
||||
this.headers = headers;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder timestamp(long timestamp) {
|
||||
this.timestamp = timestamp;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder source(String source) {
|
||||
this.source = source;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder key(String key) {
|
||||
this.key = key;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ReceivedMessage build() {
|
||||
return new ReceivedMessage(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
ReceivedMessage that = (ReceivedMessage) o;
|
||||
return timestamp == that.timestamp &&
|
||||
Objects.equals(body, that.body) &&
|
||||
contentType == that.contentType &&
|
||||
Objects.equals(headers, that.headers) &&
|
||||
Objects.equals(source, that.source) &&
|
||||
Objects.equals(key, that.key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(body, contentType, headers, timestamp, source, key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ReceivedMessage{" +
|
||||
"body='" + body + '\'' +
|
||||
", contentType=" + contentType +
|
||||
", headers=" + headers +
|
||||
", timestamp=" + timestamp +
|
||||
", source='" + source + '\'' +
|
||||
", key='" + key + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@ -14,7 +14,7 @@ public class Template {
|
||||
}
|
||||
|
||||
public Template(String templateString) {
|
||||
this.template = new ST(templateString);
|
||||
this.template = new ST(templateString, '$', '$');
|
||||
}
|
||||
|
||||
public Template set(String variable, String value) {
|
||||
|
||||
Binary file not shown.
7
tests/.checkstyle
Normal file
7
tests/.checkstyle
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<fileset-config file-format-version="1.2.0" simple-config="true" sync-formatter="false">
|
||||
<fileset name="all" enabled="true" check-config-name="Google Checks" local="false">
|
||||
<file-match-pattern match-pattern="." include-pattern="true"/>
|
||||
</fileset>
|
||||
</fileset-config>
|
||||
462
tests/pom.xml
462
tests/pom.xml
@ -1,77 +1,95 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>cz.moneta.test</groupId>
|
||||
<artifactId>tests</artifactId>
|
||||
<version>2.29-SNAPSHOT</version>
|
||||
<properties>
|
||||
<harness.version>7.55-SNAPSHOT</harness.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<junit.platform.version>1.5.1</junit.platform.version>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cz.moneta.test</groupId>
|
||||
<artifactId>harness</artifactId>
|
||||
<version>${harness.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>1.7.25</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.dataformat</groupId>
|
||||
<artifactId>jackson-dataformat-csv</artifactId>
|
||||
<version>2.16.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
<version>2.16.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.30</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.microsoft.sqlserver</groupId>
|
||||
<artifactId>mssql-jdbc_auth</artifactId>
|
||||
<version>8.2.0.x86</version>
|
||||
<type>dll</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains</groupId>
|
||||
<artifactId>annotations</artifactId>
|
||||
<version>15.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.platform</groupId>
|
||||
<artifactId>junit-platform-reporting</artifactId>
|
||||
<version>${junit.platform.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.platform</groupId>
|
||||
<artifactId>junit-platform-console</artifactId>
|
||||
<version>${junit.platform.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.yaml</groupId>
|
||||
<artifactId>snakeyaml</artifactId>
|
||||
<version>1.26</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>Moneta Artifactory</id>
|
||||
<activation>
|
||||
<activeByDefault>true</activeByDefault>
|
||||
</activation>
|
||||
<build>
|
||||
<project
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>cz.moneta.test</groupId>
|
||||
<artifactId>tests</artifactId>
|
||||
<version>2.28-SNAPSHOT</version>
|
||||
<properties>
|
||||
<harness.version>7.55-SNAPSHOT</harness.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<junit.platform.version>1.5.1</junit.platform.version>
|
||||
<ibm.mq.version>9.4.5.0</ibm.mq.version>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>cz.moneta.test</groupId>
|
||||
<artifactId>harness</artifactId>
|
||||
<version>${harness.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.ibm.mq</groupId>
|
||||
<artifactId>com.ibm.mq.allclient</artifactId>
|
||||
<version>${ibm.mq.version}</version>
|
||||
</dependency>
|
||||
<!-- StringTemplate -->
|
||||
<dependency>
|
||||
<groupId>org.antlr</groupId>
|
||||
<artifactId>ST4</artifactId>
|
||||
<version>4.3.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.jms</groupId>
|
||||
<artifactId>javax.jms-api</artifactId>
|
||||
<version>2.0.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>1.7.25</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.dataformat</groupId>
|
||||
<artifactId>jackson-dataformat-csv</artifactId>
|
||||
<version>2.16.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
<version>2.16.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.30</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.microsoft.sqlserver</groupId>
|
||||
<artifactId>mssql-jdbc_auth</artifactId>
|
||||
<version>8.2.0.x86</version>
|
||||
<type>dll</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jetbrains</groupId>
|
||||
<artifactId>annotations</artifactId>
|
||||
<version>15.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.platform</groupId>
|
||||
<artifactId>junit-platform-reporting</artifactId>
|
||||
<version>${junit.platform.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.platform</groupId>
|
||||
<artifactId>junit-platform-console</artifactId>
|
||||
<version>${junit.platform.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.yaml</groupId>
|
||||
<artifactId>snakeyaml</artifactId>
|
||||
<version>1.26</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>Moneta Artifactory</id>
|
||||
<activation>
|
||||
<activeByDefault>true</activeByDefault>
|
||||
</activation>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
@ -91,30 +109,37 @@
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.13.0</version>
|
||||
<configuration>
|
||||
<source>17</source>
|
||||
<target>17</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>2.22.2</version>
|
||||
<configuration>
|
||||
<argLine>--add-opens java.base/java.lang.invoke=ALL-UNNAMED -Dhttp.proxyHost=wsa-aws.mbid.cz -Dhttp.proxyPort=8008 -Dhttps.proxyHost=wsa-aws.mbid.cz -Dhttps.proxyPort=8008
|
||||
-Dhttp.nonProxyHosts="elasticclusterawscoord*|elasticclusterawsingest*|jenkinslivex*|cbltstx|vault|vault.svc.k8s.moneta-containers.net|selenium-hub.svc.k8s.moneta-containers.net|jira*|d000*|x000*|l000*|digdev*|r000|spii-live-significant|mbczvl1dl0ihat3.ux.mbid.cz|mbczvl1dl0ihet3.ux.mbid.cz|wso2-fve-gw.ux.mbid.cz|wso2eifve.lb.mbid.cz|wso2eippe.lb.mbid.cz|wso2-ppe-gw.ux.mbid.cz|mbczvl0bl0enin3.ux.mbid.cz|wso2-tst1-gw.ux.mbid.cz|wso2eitst1.lb.mbid.cz|wso2-edu-gw.ux.mbid.cz|mbczvl0bl0enin5.ux.mbid.cz|mbczvl1dl0enin6.ux.mbid.cz|wso2api01-wso2-02.ux.mbid.cz|api-szr.tst.moneta-containers.net|api-szr.ppe.moneta-containers.net|docker1|mbczvl1dl0mockt.ux.mbid.cz|api.tst.moneta-containers.net|api.ppe.moneta-containers.net"</argLine>
|
||||
<includes>
|
||||
<include>cz.moneta.test.sandbox.demo.HarnessDemoTest.java</include>
|
||||
</includes>
|
||||
<trimStackTrace>false</trimStackTrace>
|
||||
<useFile>false</useFile>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<repositories>
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.13.0</version>
|
||||
<configuration>
|
||||
<source>17</source>
|
||||
<target>17</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>2.22.2</version>
|
||||
<configuration>
|
||||
<argLine>
|
||||
--add-opens
|
||||
java.base/java.lang.invoke=ALL-UNNAMED
|
||||
-Dhttp.proxyHost=wsa-aws.mbid.cz
|
||||
-Dhttp.proxyPort=8008
|
||||
-Dhttps.proxyHost=wsa-aws.mbid.cz
|
||||
-Dhttps.proxyPort=8008
|
||||
-Dhttp.nonProxyHosts="elasticclusterawscoord*|elasticclusterawsingest*|jenkinslivex*|cbltstx|vault|vault.svc.k8s.moneta-containers.net|selenium-hub.svc.k8s.moneta-containers.net|jira*|d000*|x000*|l000*|digdev*|r000|spii-live-significant|mbczvl1dl0ihat3.ux.mbid.cz|mbczvl1dl0ihet3.ux.mbid.cz|wso2-fve-gw.ux.mbid.cz|wso2eifve.lb.mbid.cz|wso2eippe.lb.mbid.cz|wso2-ppe-gw.ux.mbid.cz|mbczvl0bl0enin3.ux.mbid.cz|wso2-tst1-gw.ux.mbid.cz|wso2eitst1.lb.mbid.cz|wso2-edu-gw.ux.mbid.cz|mbczvl0bl0enin5.ux.mbid.cz|mbczvl1dl0enin6.ux.mbid.cz|wso2api01-wso2-02.ux.mbid.cz|api-szr.tst.moneta-containers.net|api-szr.ppe.moneta-containers.net|docker1|mbczvl1dl0mockt.ux.mbid.cz|api.tst.moneta-containers.net|api.ppe.moneta-containers.net"</argLine>
|
||||
<includes>
|
||||
<include>
|
||||
cz.moneta.test.sandbox.demo.HarnessDemoTest.java</include>
|
||||
</includes>
|
||||
<trimStackTrace>false</trimStackTrace>
|
||||
<useFile>false</useFile>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<repositories>
|
||||
<repository>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
@ -148,122 +173,129 @@
|
||||
<name>MVN Repository</name>
|
||||
<url>https://mvnrepository.com/artifact/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
<releases>
|
||||
<enabled>true</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
<id>central</id>
|
||||
<name>plugins-release</name>
|
||||
<url>https://artifactory-aws.ux.mbid.cz/artifactory/plugins-release</url>
|
||||
</pluginRepository>
|
||||
<pluginRepository>
|
||||
<releases>
|
||||
<enabled>false</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
<id>snapshots</id>
|
||||
<name>plugins-snapshot</name>
|
||||
<url>https://artifactory-aws.ux.mbid.cz/artifactory/plugins-snapshot</url>
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>build.package</id>
|
||||
<activation>
|
||||
<property>
|
||||
<name>build.package</name>
|
||||
<value>true</value>
|
||||
</property>
|
||||
</activation>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.13.0</version>
|
||||
<configuration>
|
||||
<source>17</source>
|
||||
<target>17</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<version>3.6.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
<mainClass>cz.moneta.test.testrunner.TestRunner</mainClass>
|
||||
</manifest>
|
||||
</archive>
|
||||
<descriptors>
|
||||
<descriptor>src/assembly/assembly.xml</descriptor>
|
||||
</descriptors>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<repositories>
|
||||
<repository>
|
||||
<releases>
|
||||
<enabled>true</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
<id>central</id>
|
||||
<name>libs-release</name>
|
||||
<url>https://artifactory-aws.ux.mbid.cz/artifactory/libs-release</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<releases>
|
||||
<enabled>false</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
<id>snapshots</id>
|
||||
<name>libs-snapshot</name>
|
||||
<url>https://artifactory-aws.ux.mbid.cz/artifactory/libs-snapshot</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
<releases>
|
||||
<enabled>true</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
<id>central</id>
|
||||
<name>plugins-release</name>
|
||||
<url>https://artifactory-aws.ux.mbid.cz/artifactory/plugins-release</url>
|
||||
</pluginRepository>
|
||||
<pluginRepository>
|
||||
<releases>
|
||||
<enabled>false</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
<id>snapshots</id>
|
||||
<name>plugins-snapshot</name>
|
||||
<url>https://artifactory-aws.ux.mbid.cz/artifactory/plugins-snapshot</url>
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
</profile>
|
||||
</profiles>
|
||||
</repositories>
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
<releases>
|
||||
<enabled>true</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
<id>central</id>
|
||||
<name>plugins-release</name>
|
||||
<url>
|
||||
https://artifactory-aws.ux.mbid.cz/artifactory/plugins-release</url>
|
||||
</pluginRepository>
|
||||
<pluginRepository>
|
||||
<releases>
|
||||
<enabled>false</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
<id>snapshots</id>
|
||||
<name>plugins-snapshot</name>
|
||||
<url>
|
||||
https://artifactory-aws.ux.mbid.cz/artifactory/plugins-snapshot</url>
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>build.package</id>
|
||||
<activation>
|
||||
<property>
|
||||
<name>build.package</name>
|
||||
<value>true</value>
|
||||
</property>
|
||||
</activation>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.13.0</version>
|
||||
<configuration>
|
||||
<source>17</source>
|
||||
<target>17</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<version>3.6.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
<mainClass>
|
||||
cz.moneta.test.testrunner.TestRunner</mainClass>
|
||||
</manifest>
|
||||
</archive>
|
||||
<descriptors>
|
||||
<descriptor>src/assembly/assembly.xml</descriptor>
|
||||
</descriptors>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<repositories>
|
||||
<repository>
|
||||
<releases>
|
||||
<enabled>true</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
<id>central</id>
|
||||
<name>libs-release</name>
|
||||
<url>
|
||||
https://artifactory-aws.ux.mbid.cz/artifactory/libs-release</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<releases>
|
||||
<enabled>false</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
<id>snapshots</id>
|
||||
<name>libs-snapshot</name>
|
||||
<url>
|
||||
https://artifactory-aws.ux.mbid.cz/artifactory/libs-snapshot</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
<releases>
|
||||
<enabled>true</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
<id>central</id>
|
||||
<name>plugins-release</name>
|
||||
<url>
|
||||
https://artifactory-aws.ux.mbid.cz/artifactory/plugins-release</url>
|
||||
</pluginRepository>
|
||||
<pluginRepository>
|
||||
<releases>
|
||||
<enabled>false</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
<id>snapshots</id>
|
||||
<name>plugins-snapshot</name>
|
||||
<url>
|
||||
https://artifactory-aws.ux.mbid.cz/artifactory/plugins-snapshot</url>
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
|
||||
@ -7,7 +7,7 @@ import cz.moneta.test.dsl.auto.smartauto.setman.SmartAutoSetman;
|
||||
import cz.moneta.test.dsl.broadcom.Broadcom;
|
||||
import cz.moneta.test.dsl.brokerportal.BrokerPortal;
|
||||
import cz.moneta.test.dsl.caapi.CaApiBuilder;
|
||||
import cz.moneta.test.dsl.cagw.CaGw;
|
||||
import cz.moneta.test.dsl.cagw.CaGwBuilder;
|
||||
import cz.moneta.test.dsl.cashman.Cashman;
|
||||
import cz.moneta.test.dsl.cebia.Cebia;
|
||||
import cz.moneta.test.dsl.demo.Spirits;
|
||||
@ -23,10 +23,9 @@ import cz.moneta.test.dsl.greenscreen.GreenScreen;
|
||||
import cz.moneta.test.dsl.hypos.Hypos;
|
||||
import cz.moneta.test.dsl.ib.Ib;
|
||||
import cz.moneta.test.dsl.ilods.Ilods;
|
||||
import cz.moneta.test.dsl.imq.ImqFirstVision;
|
||||
import cz.moneta.test.dsl.kafka.Kafka;
|
||||
import cz.moneta.test.dsl.kasanova.Kasanova;
|
||||
import cz.moneta.test.dsl.mobile.smartbanking.home.Sb;
|
||||
import cz.moneta.test.dsl.messaging.Messaging;
|
||||
import cz.moneta.test.dsl.monetaapiportal.MonetaApiPortal;
|
||||
import cz.moneta.test.dsl.monetaportal.MonetaPortal;
|
||||
import cz.moneta.test.dsl.mwf.IHub;
|
||||
@ -112,8 +111,8 @@ public class Harness extends BaseStoreAccessor {
|
||||
return new CaApiBuilder(this);
|
||||
}
|
||||
|
||||
public CaGw withCaGw() {
|
||||
return new CaGw(this);
|
||||
public CaGwBuilder withCaGw() {
|
||||
return new CaGwBuilder(this);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@ -284,12 +283,8 @@ public class Harness extends BaseStoreAccessor {
|
||||
return new Cashman(this);
|
||||
}
|
||||
|
||||
public ImqFirstVision withImqFirstVision() {
|
||||
return new ImqFirstVision(this);
|
||||
}
|
||||
|
||||
public Kafka withKafka() {
|
||||
return new Kafka(this);
|
||||
public Messaging withMessaging() {
|
||||
return new Messaging(this);
|
||||
}
|
||||
|
||||
private void initGenerators() {
|
||||
|
||||
@ -47,7 +47,7 @@ public interface NewCalculationPage extends SmartAutoFlow<NewCalculationPage>, S
|
||||
String VEHICLE_REGISTRATION_DATE_CHOOSE_BUTTON = "//button//span[contains(text(), 'Vybrat')]";
|
||||
String VEHICLE_FINANCE_PRODUCT = "(//DIV//p//span[contains(text(), 'Vyberte záznam ...')])[1]";
|
||||
String VEHICLE_FINANCE_PRODUCT_CHOOSE_BUTTON_DEFAULT = "(//div/h3[contains(text(), '%s')])/..//following-sibling::div//button//span[contains(text(), 'Vybrat')][1]/..";
|
||||
String VEHICLE_CAR_INSURANCE = "//h4/following-sibling::div/div[contains(text(),'Vyberte záznam ...')][1]";
|
||||
String VEHICLE_CAR_INSURANCE = "//span[contains(text(),'Povinné ručení')]/following-sibling::div/div[contains(text(),'Vyberte záznam ...')][1]";
|
||||
String VEHICLE_CAR_ACCIDENT_INSURANCE = "(//DIV//p//span[contains(text(), 'Vyberte záznam ...')])[1]";
|
||||
String VEHICLE_CAR_BENEFITS = "(//DIV//p//span[contains(text(), 'Vyberte záznam ...')])[1]";
|
||||
String VEHICLE_CAR_BENEFITS_OPTION_DEFAULT = "(//label//span[contains(text(), '%s')])[1]/..";
|
||||
@ -75,7 +75,6 @@ public interface NewCalculationPage extends SmartAutoFlow<NewCalculationPage>, S
|
||||
String VEHICLE_CAR_INSURANCE_VEHICLE_SECOND_FUEL_TYPE_CHOOSE = "//label[contains(.,'Palivo')]/../div/ul/li[contains(.,'%s')]";
|
||||
String VEHICLE_CAR_INSURANCE_VEHICLE_SEARCH_NO_VIN = "//button[contains(.,'Vyhledat')]";
|
||||
String VEHICLE_CAR_INSURANCE_VEHICLE_VOLUME_AGRO = "//label[contains(.,'Objem')]/following-sibling::div/div/input";
|
||||
String VEHICLE_CAR_INSURANCE_VEHICLE_NUMBER_OF_SEATS = "//label[contains(.,'Počet sedadel')]/following-sibling::div/div/input";
|
||||
String VEHICLE_CAR_INSURANCE_VEHICLE_POWER_AGRO = "//label[contains(.,'Výkon')]/following-sibling::div/div/input";
|
||||
String VEHICLE_CAR_INSURANCE_VEHICLE_WEIGHT_AGRO = "//label[contains(.,'Hmotnost')]/following-sibling::div/div/input";
|
||||
String VEHICLE_CAR_INSURANCE_VEHICLE_FUEL_AGRO = "//label[contains(.,'Palivo')]/following-sibling::div/button";
|
||||
@ -85,8 +84,7 @@ public interface NewCalculationPage extends SmartAutoFlow<NewCalculationPage>, S
|
||||
String VEHICLE_CAR_INSURANCE_INSURANCE_HAV_FIELD_CHOOSE_DEFAULT = "//h4[contains(.,'Havarijní pojištění')]/../../../../div[2]/div[2]/div/div/section/div/div/div/button";
|
||||
String VEHICLE_CAR_INSURANCE_INSURANCE_POV_FIELD_DEFAULT = "//h4[contains(.,'Povinné ručení')]/../../../../div[2]/div[1]/div/div[1]/section/h4/button/span[2]";
|
||||
String VEHICLE_CAR_INSURANCE_INSURANCE_POV_FIELD_CHOOSE_DEFAULT = "//h4[contains(.,'Povinné ručení')]/../../../../div[2]/div[1]/div/div/section/div/div/div/button";
|
||||
String VEHICLE_CAR_INSURANCE_INSURANCE_CHOOSE = "//section[@data-testid='Collapse']//button[contains(text(),'Vybrat')]";
|
||||
String VEHICLE_CAR_INSURANCE_INSURANCE_CLOSE = "/html/body/aside[1]/div/div/div[2]/div/div/button[contains(text(),'Zavřít')]";
|
||||
String VEHICLE_CAR_INSURANCE_INSURANCE_CHOOSE = "//section[@data-testid='Tabs']/div[2]/div[3]/div/div[3]/div/div/button[contains(.,'Vybrat')]";
|
||||
String CREATE_APPLICATION = "//span[contains(text(), 'Vytvořit žádost')]/parent::button";
|
||||
String DIV_CLICK_BLOCKER = "//div[@class='_21KP5GbzHYNm19YwGmWy0r']";
|
||||
String DIV_CLICK_BLOCKER_2 = "//div[@class='_3Lp0XIzdhx1ktbMqO92O8T']";
|
||||
@ -253,8 +251,8 @@ public interface NewCalculationPage extends SmartAutoFlow<NewCalculationPage>, S
|
||||
@TypeInto(value = VEHICLE_CAR_INSURANCE_CUSTOMER_NATIONALITY, clear = true)
|
||||
NewCalculationPage typeVehicleInsuranceCustomerNationality(String nationality);
|
||||
|
||||
@Wait(value = VEHICLE_CAR_INSURANCE_CUSTOMER_NATIONALITY_FIELD)
|
||||
@Click(value = VEHICLE_CAR_INSURANCE_CUSTOMER_NATIONALITY_FIELD, jsClick = true)
|
||||
@Wait(VEHICLE_CAR_INSURANCE_CUSTOMER_NATIONALITY_FIELD)
|
||||
@Click(value = VEHICLE_CAR_INSURANCE_CUSTOMER_NATIONALITY_FIELD)
|
||||
NewCalculationPage clickVehicleInsuranceCustomerNationality();
|
||||
|
||||
@Wait(VEHICLE_CAR_INSURANCE_CUSTOMER_ADDRESS)
|
||||
@ -273,12 +271,9 @@ public interface NewCalculationPage extends SmartAutoFlow<NewCalculationPage>, S
|
||||
@TypeInto(value = VEHICLE_CAR_INSURANCE_VEHICLE_VOLUME_AGRO)
|
||||
NewCalculationPage typeVehicleInsuranceVehicleVolume(String volume);
|
||||
|
||||
@Wait(VEHICLE_CAR_INSURANCE_VEHICLE_TAB)
|
||||
@TypeInto(value = VEHICLE_CAR_INSURANCE_VEHICLE_NUMBER_OF_SEATS)
|
||||
NewCalculationPage typeVehicleInsuranceVehicleNumberOfSeats(String numberOfSeats);
|
||||
|
||||
@Wait(VEHICLE_CAR_INSURANCE_VEHICLE_TAB)
|
||||
@TypeInto(value = VEHICLE_CAR_INSURANCE_VEHICLE_POWER_AGRO, andWait = @Wait(explicitWaitSeconds = 2))
|
||||
@TypeInto(value = VEHICLE_CAR_INSURANCE_VEHICLE_POWER_AGRO)
|
||||
NewCalculationPage typeVehicleInsuranceVehiclePower(String power);
|
||||
|
||||
@Wait(VEHICLE_CAR_INSURANCE_VEHICLE_TAB)
|
||||
@ -350,10 +345,6 @@ public interface NewCalculationPage extends SmartAutoFlow<NewCalculationPage>, S
|
||||
@Click(value = VEHICLE_CAR_INSURANCE_INSURANCE_CHOOSE,jsClick = true)
|
||||
NewCalculationPage clickVehicleInsuranceInsuranceChoose();
|
||||
|
||||
@Wait(value = VEHICLE_CAR_INSURANCE_INSURANCE_CLOSE, waitSecondsForElement = 30)
|
||||
@Click(value = VEHICLE_CAR_INSURANCE_INSURANCE_CLOSE,jsClick = true)
|
||||
NewCalculationPage clickVehicleInsuranceInsuranceClose();
|
||||
|
||||
@Wait(value = {DIV_CLICK_BLOCKER, DIV_CLICK_BLOCKER_2}, until = Until.GONE, waitSecondsForElement = 30)
|
||||
@Wait(value = VEHICLE_CAR_ACCIDENT_INSURANCE, waitSecondsForElement = 30)
|
||||
@Click(value = VEHICLE_CAR_ACCIDENT_INSURANCE, andWait = @Wait(value = CommonElements.MODAL, waitSecondsForElement = 30))
|
||||
|
||||
@ -14,7 +14,7 @@ public interface OthersPage extends SmartAutoFlow<OthersPage> {
|
||||
String ACCOUNT_NUMBER = "//input[@name='sign.bankContactC2C.accountNumber']";
|
||||
String BANK_CODE = "//span[contains(text(), 'Kód banky')]/../../..//input";
|
||||
String BUTTON = "//button[contains(text(), '%s')]";
|
||||
String APPROVAL_MESSAGE = "//h6[contains(text(), 'Zpráva pro schvalovatele')]/../../../div/div/span/textarea";
|
||||
String APPROVAL_MESSAGE = "//span[contains(text(), 'Zpráva pro schvalovatele')]/../../../textarea";
|
||||
String VEHICLE_PAGE = "//span[contains(text(), 'Předmět')]";
|
||||
String DUPLICATE_APPLICATION = "//span[contains(text(), 'Duplikovat')]";
|
||||
String CALCULATION_NAME = "//input[@name='calculationName']";
|
||||
|
||||
@ -13,7 +13,7 @@ import static cz.moneta.test.dsl.auto.smartauto.QueuePage.*;
|
||||
waitSecondsForElement = 40)
|
||||
public interface QueuePage extends SmartAutoFlow<QueuePage>, StoreAccessor, CalendarAccessor {
|
||||
|
||||
String HEADING_SEND_APPLICATIONS = "//h1[contains(text(), 'Odeslané žádosti')]";
|
||||
String HEADING_SEND_APPLICATIONS = "//span[contains(text(), 'Odeslané žádosti')]";
|
||||
String FIRST_APPLICATION = "//div/div/div/button/i";
|
||||
String DATE_FROM = "//label[contains(., 'Datum od')]/following-sibling::div/button";
|
||||
String DATE_TO = "//label[contains(., 'Datum do')]/following-sibling::div/button";
|
||||
|
||||
@ -6,11 +6,11 @@ import cz.moneta.test.harness.support.web.*;
|
||||
|
||||
public interface SavedCalculationsPage extends SmartAutoFlow<SavedCalculationsPage>, StoreAccessor, CalendarAccessor {
|
||||
|
||||
String HEADING_SAVED_CALCULATIONS = "//h1[contains(text(), 'Kalkulace')]";
|
||||
String FIRST_APPLICATION_DIV = "/html/body/div[1]/div[1]/div/div/div[1]/div[2]/div[1]/div/div[1]";
|
||||
String CALCULATION_NAME = FIRST_APPLICATION_DIV + "/div[2]/div[contains(text(), 'TestDuplikace')]";
|
||||
String CALCULATION_STATUS = FIRST_APPLICATION_DIV + "//span[contains(text(), 'Rozpracovaná žádost')]";
|
||||
String CALCULATION_DATE = FIRST_APPLICATION_DIV + "/div[3]/div[2]";
|
||||
String HEADING_SAVED_CALCULATIONS = "//h1/span[contains(text(), 'Kalkulace')]";
|
||||
String FIRST_APPLICATION_DIV = "//h2/../div[1]/div/";
|
||||
String CALCULATION_NAME = FIRST_APPLICATION_DIV + "div/div[1]";
|
||||
String CALCULATION_STATUS = FIRST_APPLICATION_DIV + "/span/span[contains(text(), 'Rozpracovaná žádost')]";
|
||||
String CALCULATION_DATE = FIRST_APPLICATION_DIV + "div/div[5]";
|
||||
String DATE_FROM = "//label[contains(., 'Datum od')]/following-sibling::div/button";
|
||||
String DATE_TO = "//label[contains(., 'Datum do')]/following-sibling::div/button";
|
||||
String SEARCH_BUTTON = "//button[@testid='bbutton' and span[text()='Zobrazit']]";
|
||||
|
||||
@ -31,7 +31,7 @@ public interface VehiclePage extends SmartAutoFlow<VehiclePage>, StoreAccessor {
|
||||
String SEATS = "//input[@name='vehicle.specificCar.sittingPlaceNr']";
|
||||
String FUEL = "(//span[contains(text(), 'Palivo')])[2]/../..//following-sibling::div//input[@value='%s']";
|
||||
String MILEAGE = "//input[@name='vehicle.specificCar.travel']";
|
||||
String IMPORT_VEHICLE = "//span[contains(text(), 'Vozidlo z dovozu')]/../../../div/div/div/span/button//span[contains(text(), '%s')]/..";
|
||||
String IMPORT_VEHICLE = "//span[contains(text(), 'Vozidlo z dovozu')]/../../../div/div/span/button/span[contains(text(), '%s')]";
|
||||
String DEALER_IS_OWNER = "//span[contains(text(), 'Dealer je majitelem vozidla')]/../../..//span[contains(text(), '%s')]/..";
|
||||
String DESCRIPTION = "//textarea[@name='vehicle.specificCar.description']";
|
||||
String OTHERS_PAGE = "//span[contains(text(), 'Ostatní')]";
|
||||
@ -130,10 +130,10 @@ public interface VehiclePage extends SmartAutoFlow<VehiclePage>, StoreAccessor {
|
||||
@TypeInto(value = DESCRIPTION, andWait = @Wait(value = LOADER_DIV, until = Until.GONE))
|
||||
VehiclePage typeDescription(String description);
|
||||
|
||||
@Click(value = IMPORT_VEHICLE, jsClick = true)
|
||||
@Click(IMPORT_VEHICLE)
|
||||
VehiclePage clickImportVehicle(ImportVehicle importVehicle);
|
||||
|
||||
@Click(value = DEALER_IS_OWNER,jsClick = true)
|
||||
@Click(DEALER_IS_OWNER)
|
||||
VehiclePage clickDealerIsOwner(DealerIsOwner dealerIsOwner);
|
||||
|
||||
@Click(value = OTHERS_PAGE, jsClick = true)
|
||||
|
||||
@ -23,8 +23,8 @@ public interface ForBillingPage extends SmartAutoFlow<ForBillingPage> {
|
||||
String FROM_DATE_TEXT = "//span[contains(text(), 'Od data')]";
|
||||
String TO_DATE_TEXT = "//span[contains(text(), 'Do data')]";
|
||||
String COMMISSIONS_CREDIT_NOTES_TEXT = "//span[contains(text(), 'Provize/Dobropis')]";
|
||||
String FILTER_TITLE = "//h6[contains(text(), 'Filtr')]";
|
||||
String BILLING_COMMISSIONS_TITLE = "//h6[contains(text(), 'Fakturace')]";
|
||||
String FILTER_TITLE = "//span[contains(text(), 'Filtr')]";
|
||||
String BILLING_COMMISSIONS_TITLE = "//span[contains(text(), 'Provize k fakturaci')]";
|
||||
String RESET_BUTTON = "//span[contains(text(), 'Resetovat')]";
|
||||
String SEARCH_BUTTON = "//span[contains(text(), 'Vyhledat')]";
|
||||
String INVOICE_BUTTON = "//span[contains(text(), 'Vyfakturovat')]";
|
||||
|
||||
@ -25,7 +25,7 @@ public interface DashboardPage extends SmartAutoFlow<DashboardPage> {
|
||||
String TOTAL_AVAILABLE_LIMIT_TEXT = "//span[contains(text(), 'Volný limit')]";
|
||||
String FOR_PAYMENT_TEXT = "//span[contains(text(), 'K úhradě')]";
|
||||
String OVERDUE_PAYMENT_TEXT = "//span[contains(text(), 'Z toho po splatnosti')]";
|
||||
String PRODUCT_LIST_TITLE = "//h6[contains(text(), 'Produkty')]";
|
||||
String PRODUCT_LIST_TITLE = "//span[contains(text(), 'Seznam produktů')]";
|
||||
String ALL_CONTRACT_BUTTON = "//span[contains(text(), 'Všechny zakázky')]";
|
||||
|
||||
@CheckElementsPresent(value = {
|
||||
|
||||
@ -25,7 +25,6 @@ public class CalculationData {
|
||||
private Subsidy subsidy;
|
||||
private ItemType itemType;
|
||||
private int firstPayment;
|
||||
private String numberOfSeats;
|
||||
|
||||
public CalculationData setCustomer(Customer customer) {
|
||||
this.customer = customer;
|
||||
@ -132,8 +131,4 @@ public class CalculationData {
|
||||
return this;
|
||||
}
|
||||
|
||||
public CalculationData setNumberOfSeats(String numberOfSeats) {
|
||||
this.numberOfSeats = numberOfSeats;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,33 +0,0 @@
|
||||
package cz.moneta.test.dsl.cagw;
|
||||
|
||||
import cz.moneta.test.dsl.Harness;
|
||||
import cz.moneta.test.dsl.cagw.api.ApiBuilder;
|
||||
import cz.moneta.test.dsl.cagw.auth.AuthBuilder;
|
||||
import cz.moneta.test.dsl.cagw.oauth2.Oauth2Builder;
|
||||
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
|
||||
import cz.moneta.test.harness.support.rest.RawRestRequest;
|
||||
|
||||
public class CaGw extends CaGwBuilder {
|
||||
|
||||
public CaGw(Harness harness) {
|
||||
super(harness);
|
||||
}
|
||||
|
||||
public ApiBuilder api() {
|
||||
return new ApiBuilder(harness);
|
||||
}
|
||||
|
||||
public Oauth2Builder oauth2() {
|
||||
return new Oauth2Builder(harness);
|
||||
}
|
||||
|
||||
public AuthBuilder auth() {
|
||||
return new AuthBuilder(harness);
|
||||
}
|
||||
|
||||
public RawRestRequest.Path prepareRequest() {
|
||||
CaGwEndpoint endpoint = harness.getEndpoint(CaGwEndpoint.class);
|
||||
return RawRestRequest.jsonBuilder(endpoint);
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,20 +1,34 @@
|
||||
package cz.moneta.test.dsl.cagw;
|
||||
|
||||
import cz.moneta.test.dsl.Harness;
|
||||
import cz.moneta.test.dsl.cagw.api.ApiBuilder;
|
||||
import cz.moneta.test.dsl.cagw.auth.AuthBuilder;
|
||||
import cz.moneta.test.dsl.cagw.oauth2.Oauth2Builder;
|
||||
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
|
||||
import cz.moneta.test.harness.support.rest.Builders;
|
||||
import cz.moneta.test.harness.support.rest.RestRequest;
|
||||
|
||||
public class CaGwBuilder {
|
||||
|
||||
protected final Harness harness;
|
||||
private final Harness harness;
|
||||
|
||||
protected CaGwBuilder(Harness harness) {
|
||||
public CaGwBuilder(Harness harness) {
|
||||
this.harness = harness;
|
||||
}
|
||||
|
||||
protected <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
||||
public ApiBuilder api() {
|
||||
return new ApiBuilder(harness);
|
||||
}
|
||||
|
||||
}
|
||||
public Oauth2Builder oauth2() {
|
||||
return new Oauth2Builder(harness);
|
||||
}
|
||||
|
||||
public AuthBuilder auth() {
|
||||
return new AuthBuilder(harness);
|
||||
}
|
||||
|
||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,16 +1,19 @@
|
||||
package cz.moneta.test.dsl.cagw.api;
|
||||
|
||||
import cz.moneta.test.dsl.Harness;
|
||||
import cz.moneta.test.dsl.cagw.CaGwBuilder;
|
||||
import cz.moneta.test.dsl.cagw.api.cbl.CblBuilder;
|
||||
import cz.moneta.test.dsl.cagw.api.v2.V2Builder;
|
||||
import cz.moneta.test.dsl.cagw.api.v4.V4Builder;
|
||||
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
|
||||
import cz.moneta.test.dsl.cagw.api.v1.V1Builder;
|
||||
import cz.moneta.test.harness.support.rest.Builders;
|
||||
import cz.moneta.test.harness.support.rest.RestRequest;
|
||||
|
||||
public class ApiBuilder extends CaGwBuilder {
|
||||
public class ApiBuilder {
|
||||
private final Harness harness;
|
||||
|
||||
public ApiBuilder(Harness harness) {
|
||||
super(harness);
|
||||
this.harness = harness;
|
||||
}
|
||||
|
||||
public CblBuilder cbl() {
|
||||
@ -29,4 +32,7 @@ public class ApiBuilder extends CaGwBuilder {
|
||||
return new V1Builder(harness);
|
||||
}
|
||||
|
||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
||||
}
|
||||
}
|
||||
@ -1,17 +1,23 @@
|
||||
package cz.moneta.test.dsl.cagw.api.cbl;
|
||||
|
||||
import cz.moneta.test.dsl.Harness;
|
||||
import cz.moneta.test.dsl.cagw.CaGwBuilder;
|
||||
import cz.moneta.test.dsl.cagw.api.cbl.psd.PsdBuilder;
|
||||
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
|
||||
import cz.moneta.test.harness.support.rest.Builders;
|
||||
import cz.moneta.test.harness.support.rest.RestRequest;
|
||||
|
||||
public class CblBuilder extends CaGwBuilder {
|
||||
public class CblBuilder {
|
||||
private final Harness harness;
|
||||
|
||||
public CblBuilder(Harness harness) {
|
||||
super(harness);
|
||||
this.harness = harness;
|
||||
}
|
||||
|
||||
public PsdBuilder psd() {
|
||||
return new PsdBuilder(harness);
|
||||
}
|
||||
|
||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,14 +1,17 @@
|
||||
package cz.moneta.test.dsl.cagw.api.cbl.psd;
|
||||
|
||||
import cz.moneta.test.dsl.Harness;
|
||||
import cz.moneta.test.dsl.cagw.CaGwBuilder;
|
||||
import cz.moneta.test.dsl.cagw.api.cbl.psd.aisp.AispBuilder;
|
||||
import cz.moneta.test.dsl.cagw.api.cbl.psd.pisp.PispBuilder;
|
||||
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
|
||||
import cz.moneta.test.harness.support.rest.Builders;
|
||||
import cz.moneta.test.harness.support.rest.RestRequest;
|
||||
|
||||
public class PsdBuilder extends CaGwBuilder {
|
||||
public class PsdBuilder {
|
||||
private final Harness harness;
|
||||
|
||||
public PsdBuilder(Harness harness) {
|
||||
super(harness);
|
||||
this.harness = harness;
|
||||
}
|
||||
|
||||
public PispBuilder pisp() {
|
||||
@ -19,4 +22,7 @@ public class PsdBuilder extends CaGwBuilder {
|
||||
return new AispBuilder(harness);
|
||||
}
|
||||
|
||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
||||
}
|
||||
}
|
||||
@ -1,17 +1,23 @@
|
||||
package cz.moneta.test.dsl.cagw.api.cbl.psd.aisp;
|
||||
|
||||
import cz.moneta.test.dsl.Harness;
|
||||
import cz.moneta.test.dsl.cagw.CaGwBuilder;
|
||||
import cz.moneta.test.dsl.cagw.api.cbl.psd.aisp.my.MyBuilder;
|
||||
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
|
||||
import cz.moneta.test.harness.support.rest.Builders;
|
||||
import cz.moneta.test.harness.support.rest.RestRequest;
|
||||
|
||||
public class AispBuilder extends CaGwBuilder {
|
||||
public class AispBuilder {
|
||||
private final Harness harness;
|
||||
|
||||
public AispBuilder(Harness harness) {
|
||||
super(harness);
|
||||
this.harness = harness;
|
||||
}
|
||||
|
||||
public MyBuilder my() {
|
||||
return new MyBuilder(harness);
|
||||
}
|
||||
|
||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
||||
}
|
||||
}
|
||||
@ -1,16 +1,19 @@
|
||||
package cz.moneta.test.dsl.cagw.api.cbl.psd.aisp.my;
|
||||
|
||||
import cz.moneta.test.dsl.Harness;
|
||||
import cz.moneta.test.dsl.cagw.CaGwBuilder;
|
||||
import cz.moneta.test.dsl.cagw.api.cbl.psd.aisp.my.accounts.Account;
|
||||
import cz.moneta.test.dsl.cagw.api.cbl.psd.aisp.my.accounts.AccountsBuilder;
|
||||
import cz.moneta.test.dsl.cagw.api.cbl.psd.aisp.my.accounts.AccountsWithParams;
|
||||
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
|
||||
import cz.moneta.test.harness.support.rest.Builders;
|
||||
import cz.moneta.test.harness.support.rest.RestRequest;
|
||||
|
||||
|
||||
public class MyBuilder extends CaGwBuilder {
|
||||
public class MyBuilder {
|
||||
private final Harness harness;
|
||||
|
||||
public MyBuilder(Harness harness) {
|
||||
super(harness);
|
||||
this.harness = harness;
|
||||
}
|
||||
|
||||
public AccountsBuilder accounts() {
|
||||
@ -25,4 +28,7 @@ public class MyBuilder extends CaGwBuilder {
|
||||
return getBuilder(AccountsWithParams.class);
|
||||
}
|
||||
|
||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
||||
}
|
||||
}
|
||||
@ -1,17 +1,23 @@
|
||||
package cz.moneta.test.dsl.cagw.api.cbl.psd.aisp.my.accounts;
|
||||
|
||||
import cz.moneta.test.dsl.Harness;
|
||||
import cz.moneta.test.dsl.cagw.CaGwBuilder;
|
||||
import cz.moneta.test.dsl.cagw.api.cbl.psd.aisp.my.accounts.id.IdBuilder;
|
||||
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
|
||||
import cz.moneta.test.harness.support.rest.Builders;
|
||||
import cz.moneta.test.harness.support.rest.RestRequest;
|
||||
|
||||
public class AccountsBuilder extends CaGwBuilder {
|
||||
public class AccountsBuilder {
|
||||
private final Harness harness;
|
||||
|
||||
public AccountsBuilder(Harness harness) {
|
||||
super(harness);
|
||||
this.harness = harness;
|
||||
}
|
||||
|
||||
public IdBuilder id() {
|
||||
return new IdBuilder(harness);
|
||||
}
|
||||
|
||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
||||
}
|
||||
}
|
||||
@ -1,15 +1,18 @@
|
||||
package cz.moneta.test.dsl.cagw.api.cbl.psd.aisp.my.accounts.id;
|
||||
|
||||
import cz.moneta.test.dsl.Harness;
|
||||
import cz.moneta.test.dsl.cagw.CaGwBuilder;
|
||||
import cz.moneta.test.dsl.cagw.api.cbl.psd.aisp.my.accounts.id.balance.Balance;
|
||||
import cz.moneta.test.dsl.cagw.api.cbl.psd.aisp.my.accounts.id.transactions.Transactions;
|
||||
import cz.moneta.test.dsl.cagw.api.cbl.psd.aisp.my.accounts.id.transactions.TransactionsWithParams;
|
||||
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
|
||||
import cz.moneta.test.harness.support.rest.Builders;
|
||||
import cz.moneta.test.harness.support.rest.RestRequest;
|
||||
|
||||
public class IdBuilder extends CaGwBuilder {
|
||||
public class IdBuilder {
|
||||
private final Harness harness;
|
||||
|
||||
public IdBuilder(Harness harness) {
|
||||
super(harness);
|
||||
this.harness = harness;
|
||||
}
|
||||
|
||||
public Balance prepareBalanceRequest() {
|
||||
@ -23,5 +26,8 @@ public class IdBuilder extends CaGwBuilder {
|
||||
public TransactionsWithParams prepareTransactionsWithParamsRequest() {
|
||||
return getBuilder(TransactionsWithParams.class);
|
||||
}
|
||||
|
||||
|
||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
||||
}
|
||||
}
|
||||
@ -1,17 +1,23 @@
|
||||
package cz.moneta.test.dsl.cagw.api.cbl.psd.pisp;
|
||||
|
||||
import cz.moneta.test.dsl.Harness;
|
||||
import cz.moneta.test.dsl.cagw.CaGwBuilder;
|
||||
import cz.moneta.test.dsl.cagw.api.cbl.psd.pisp.my.MyBuilder;
|
||||
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
|
||||
import cz.moneta.test.harness.support.rest.Builders;
|
||||
import cz.moneta.test.harness.support.rest.RestRequest;
|
||||
|
||||
public class PispBuilder extends CaGwBuilder {
|
||||
public class PispBuilder {
|
||||
private final Harness harness;
|
||||
|
||||
public PispBuilder(Harness harness) {
|
||||
super(harness);
|
||||
this.harness = harness;
|
||||
}
|
||||
|
||||
public MyBuilder my() {
|
||||
return new MyBuilder(harness);
|
||||
}
|
||||
|
||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
||||
}
|
||||
}
|
||||
@ -1,14 +1,17 @@
|
||||
package cz.moneta.test.dsl.cagw.api.cbl.psd.pisp.my;
|
||||
|
||||
import cz.moneta.test.dsl.Harness;
|
||||
import cz.moneta.test.dsl.cagw.CaGwBuilder;
|
||||
import cz.moneta.test.dsl.cagw.api.cbl.psd.pisp.my.payments.Payment;
|
||||
import cz.moneta.test.dsl.cagw.api.cbl.psd.pisp.my.payments.PaymentsBuilder;
|
||||
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
|
||||
import cz.moneta.test.harness.support.rest.Builders;
|
||||
import cz.moneta.test.harness.support.rest.RestRequest;
|
||||
|
||||
public class MyBuilder extends CaGwBuilder {
|
||||
public class MyBuilder {
|
||||
private final Harness harness;
|
||||
|
||||
public MyBuilder(Harness harness) {
|
||||
super(harness);
|
||||
this.harness = harness;
|
||||
}
|
||||
|
||||
public PaymentsBuilder payments() {
|
||||
@ -19,4 +22,7 @@ public class MyBuilder extends CaGwBuilder {
|
||||
return getBuilder(Payment.class);
|
||||
}
|
||||
|
||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
||||
}
|
||||
}
|
||||
@ -1,17 +1,23 @@
|
||||
package cz.moneta.test.dsl.cagw.api.cbl.psd.pisp.my.payments;
|
||||
|
||||
import cz.moneta.test.dsl.Harness;
|
||||
import cz.moneta.test.dsl.cagw.CaGwBuilder;
|
||||
import cz.moneta.test.dsl.cagw.api.cbl.psd.pisp.my.payments.paymentid.PaymentIdBuilder;
|
||||
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
|
||||
import cz.moneta.test.harness.support.rest.Builders;
|
||||
import cz.moneta.test.harness.support.rest.RestRequest;
|
||||
|
||||
public class PaymentsBuilder extends CaGwBuilder {
|
||||
public class PaymentsBuilder {
|
||||
private final Harness harness;
|
||||
|
||||
public PaymentsBuilder(Harness harness) {
|
||||
super(harness);
|
||||
this.harness = harness;
|
||||
}
|
||||
|
||||
public PaymentIdBuilder paymentId() {
|
||||
return new PaymentIdBuilder(harness);
|
||||
}
|
||||
|
||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
||||
}
|
||||
}
|
||||
@ -1,17 +1,23 @@
|
||||
package cz.moneta.test.dsl.cagw.api.cbl.psd.pisp.my.payments.paymentid;
|
||||
|
||||
import cz.moneta.test.dsl.Harness;
|
||||
import cz.moneta.test.dsl.cagw.CaGwBuilder;
|
||||
import cz.moneta.test.dsl.cagw.api.cbl.psd.pisp.my.payments.paymentid.status.Status;
|
||||
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
|
||||
import cz.moneta.test.harness.support.rest.Builders;
|
||||
import cz.moneta.test.harness.support.rest.RestRequest;
|
||||
|
||||
public class PaymentIdBuilder extends CaGwBuilder {
|
||||
public class PaymentIdBuilder {
|
||||
private final Harness harness;
|
||||
|
||||
public PaymentIdBuilder(Harness harness) {
|
||||
super(harness);
|
||||
this.harness = harness;
|
||||
}
|
||||
|
||||
public Status prepareStatusRequest() {
|
||||
return getBuilder(Status.class);
|
||||
}
|
||||
|
||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
||||
}
|
||||
}
|
||||
@ -1,17 +1,23 @@
|
||||
package cz.moneta.test.dsl.cagw.api.v1;
|
||||
|
||||
import cz.moneta.test.dsl.Harness;
|
||||
import cz.moneta.test.dsl.cagw.CaGwBuilder;
|
||||
import cz.moneta.test.dsl.cagw.api.v1.external.ExternalBuilder;
|
||||
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
|
||||
import cz.moneta.test.harness.support.rest.Builders;
|
||||
import cz.moneta.test.harness.support.rest.RestRequest;
|
||||
|
||||
public class V1Builder extends CaGwBuilder {
|
||||
public class V1Builder {
|
||||
private final Harness harness;
|
||||
|
||||
public V1Builder(Harness harness) {
|
||||
super(harness);
|
||||
this.harness = harness;
|
||||
}
|
||||
|
||||
public ExternalBuilder external() {
|
||||
return new ExternalBuilder(harness);
|
||||
}
|
||||
|
||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,14 +1,17 @@
|
||||
package cz.moneta.test.dsl.cagw.api.v1.external;
|
||||
|
||||
import cz.moneta.test.dsl.Harness;
|
||||
import cz.moneta.test.dsl.cagw.CaGwBuilder;
|
||||
import cz.moneta.test.dsl.cagw.api.v1.external.product.ProductBuilder;
|
||||
import cz.moneta.test.dsl.cagw.api.v1.external.service.ServiceBuilder;
|
||||
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
|
||||
import cz.moneta.test.harness.support.rest.Builders;
|
||||
import cz.moneta.test.harness.support.rest.RestRequest;
|
||||
|
||||
public class ExternalBuilder extends CaGwBuilder {
|
||||
public class ExternalBuilder {
|
||||
private final Harness harness;
|
||||
|
||||
public ExternalBuilder(Harness harness) {
|
||||
super(harness);
|
||||
this.harness = harness;
|
||||
}
|
||||
|
||||
public ProductBuilder product() {
|
||||
@ -19,4 +22,7 @@ public class ExternalBuilder extends CaGwBuilder {
|
||||
return new ServiceBuilder(harness);
|
||||
}
|
||||
|
||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,17 +1,23 @@
|
||||
package cz.moneta.test.dsl.cagw.api.v1.external.product;
|
||||
|
||||
import cz.moneta.test.dsl.Harness;
|
||||
import cz.moneta.test.dsl.cagw.CaGwBuilder;
|
||||
import cz.moneta.test.dsl.cagw.api.v1.external.product.cloan.CloanBuilder;
|
||||
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
|
||||
import cz.moneta.test.harness.support.rest.Builders;
|
||||
import cz.moneta.test.harness.support.rest.RestRequest;
|
||||
|
||||
public class ProductBuilder {
|
||||
private final Harness harness;
|
||||
|
||||
public class ProductBuilder extends CaGwBuilder {
|
||||
|
||||
public ProductBuilder(Harness harness) {
|
||||
super(harness);
|
||||
this.harness = harness;
|
||||
}
|
||||
|
||||
public CloanBuilder cbl() {
|
||||
return new CloanBuilder(harness);
|
||||
}
|
||||
|
||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,17 +1,23 @@
|
||||
package cz.moneta.test.dsl.cagw.api.v1.external.product.cloan;
|
||||
|
||||
import cz.moneta.test.dsl.Harness;
|
||||
import cz.moneta.test.dsl.cagw.CaGwBuilder;
|
||||
import cz.moneta.test.dsl.cagw.api.v1.external.product.cloan.application.ApplicationBuilder;
|
||||
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
|
||||
import cz.moneta.test.harness.support.rest.Builders;
|
||||
import cz.moneta.test.harness.support.rest.RestRequest;
|
||||
|
||||
public class CloanBuilder extends CaGwBuilder {
|
||||
public class CloanBuilder {
|
||||
private final Harness harness;
|
||||
|
||||
public CloanBuilder(Harness harness) {
|
||||
super(harness);
|
||||
this.harness = harness;
|
||||
}
|
||||
|
||||
public ApplicationBuilder application() {
|
||||
return new ApplicationBuilder(harness);
|
||||
}
|
||||
|
||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,16 +1,22 @@
|
||||
package cz.moneta.test.dsl.cagw.api.v1.external.product.cloan.application;
|
||||
|
||||
import cz.moneta.test.dsl.Harness;
|
||||
import cz.moneta.test.dsl.cagw.CaGwBuilder;
|
||||
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
|
||||
import cz.moneta.test.harness.support.rest.Builders;
|
||||
import cz.moneta.test.harness.support.rest.RestRequest;
|
||||
|
||||
public class ApplicationBuilder extends CaGwBuilder {
|
||||
public class ApplicationBuilder {
|
||||
private final Harness harness;
|
||||
|
||||
public ApplicationBuilder(Harness harness) {
|
||||
super(harness);
|
||||
this.harness = harness;
|
||||
}
|
||||
|
||||
public Application prepareApplicationRequest() {
|
||||
return getBuilder(Application.class);
|
||||
}
|
||||
|
||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,17 +1,23 @@
|
||||
package cz.moneta.test.dsl.cagw.api.v1.external.service;
|
||||
|
||||
import cz.moneta.test.dsl.Harness;
|
||||
import cz.moneta.test.dsl.cagw.CaGwBuilder;
|
||||
import cz.moneta.test.dsl.cagw.api.v1.external.service.voicebot.VoicebotBuilder;
|
||||
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
|
||||
import cz.moneta.test.harness.support.rest.Builders;
|
||||
import cz.moneta.test.harness.support.rest.RestRequest;
|
||||
|
||||
public class ServiceBuilder extends CaGwBuilder {
|
||||
public class ServiceBuilder {
|
||||
private final Harness harness;
|
||||
|
||||
public ServiceBuilder(Harness harness) {
|
||||
super(harness);
|
||||
this.harness = harness;
|
||||
}
|
||||
|
||||
public VoicebotBuilder voicebot() {
|
||||
return new VoicebotBuilder(harness);
|
||||
}
|
||||
|
||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,17 +1,23 @@
|
||||
package cz.moneta.test.dsl.cagw.api.v1.external.service.voicebot;
|
||||
|
||||
import cz.moneta.test.dsl.Harness;
|
||||
import cz.moneta.test.dsl.cagw.CaGwBuilder;
|
||||
import cz.moneta.test.dsl.cagw.api.v1.external.service.voicebot.voicepassworddetail.VoicePasswordDetail;
|
||||
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
|
||||
import cz.moneta.test.harness.support.rest.Builders;
|
||||
import cz.moneta.test.harness.support.rest.RestRequest;
|
||||
|
||||
public class VoicebotBuilder extends CaGwBuilder {
|
||||
public class VoicebotBuilder {
|
||||
private final Harness harness;
|
||||
|
||||
public VoicebotBuilder(Harness harness) {
|
||||
super(harness);
|
||||
this.harness = harness;
|
||||
}
|
||||
|
||||
public VoicePasswordDetail prepareVoicePasswordDetail() {
|
||||
public VoicePasswordDetail prepareVoicePasswordDetailApi() {
|
||||
return getBuilder(VoicePasswordDetail.class);
|
||||
}
|
||||
|
||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,17 +1,23 @@
|
||||
package cz.moneta.test.dsl.cagw.api.v2;
|
||||
|
||||
import cz.moneta.test.dsl.Harness;
|
||||
import cz.moneta.test.dsl.cagw.CaGwBuilder;
|
||||
import cz.moneta.test.dsl.cagw.api.v2.external.ExternalBuilder;
|
||||
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
|
||||
import cz.moneta.test.harness.support.rest.Builders;
|
||||
import cz.moneta.test.harness.support.rest.RestRequest;
|
||||
|
||||
public class V2Builder extends CaGwBuilder {
|
||||
public class V2Builder {
|
||||
private final Harness harness;
|
||||
|
||||
public V2Builder(Harness harness) {
|
||||
super(harness);
|
||||
this.harness = harness;
|
||||
}
|
||||
|
||||
public ExternalBuilder external() {
|
||||
return new ExternalBuilder(harness);
|
||||
}
|
||||
|
||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
||||
}
|
||||
}
|
||||
@ -1,17 +1,23 @@
|
||||
package cz.moneta.test.dsl.cagw.api.v2.external;
|
||||
|
||||
import cz.moneta.test.dsl.Harness;
|
||||
import cz.moneta.test.dsl.cagw.CaGwBuilder;
|
||||
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
|
||||
import cz.moneta.test.harness.support.rest.Builders;
|
||||
import cz.moneta.test.harness.support.rest.RestRequest;
|
||||
import cz.moneta.test.dsl.cagw.api.v2.external.client.ClientBuilder;
|
||||
|
||||
public class ExternalBuilder extends CaGwBuilder {
|
||||
public class ExternalBuilder {
|
||||
private final Harness harness;
|
||||
|
||||
public ExternalBuilder(Harness harness) {
|
||||
super(harness);
|
||||
this.harness = harness;
|
||||
}
|
||||
|
||||
public ClientBuilder client() {
|
||||
return new ClientBuilder(harness);
|
||||
}
|
||||
|
||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
||||
}
|
||||
}
|
||||
@ -1,17 +1,23 @@
|
||||
package cz.moneta.test.dsl.cagw.api.v2.external.client;
|
||||
|
||||
import cz.moneta.test.dsl.Harness;
|
||||
import cz.moneta.test.dsl.cagw.CaGwBuilder;
|
||||
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
|
||||
import cz.moneta.test.harness.support.rest.Builders;
|
||||
import cz.moneta.test.harness.support.rest.RestRequest;
|
||||
import cz.moneta.test.dsl.cagw.api.v2.external.client.kyc.KycBuilder;
|
||||
|
||||
public class ClientBuilder extends CaGwBuilder {
|
||||
public class ClientBuilder {
|
||||
private final Harness harness;
|
||||
|
||||
public ClientBuilder(Harness harness) {
|
||||
super(harness);
|
||||
this.harness = harness;
|
||||
}
|
||||
|
||||
public KycBuilder kyc() {
|
||||
return new KycBuilder(harness);
|
||||
}
|
||||
|
||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
||||
}
|
||||
}
|
||||
@ -1,17 +1,23 @@
|
||||
package cz.moneta.test.dsl.cagw.api.v2.external.client.kyc;
|
||||
|
||||
import cz.moneta.test.dsl.Harness;
|
||||
import cz.moneta.test.dsl.cagw.CaGwBuilder;
|
||||
import cz.moneta.test.dsl.cagw.api.v2.external.client.kyc.businesscard.BusinessCardBuilder;
|
||||
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
|
||||
import cz.moneta.test.harness.support.rest.Builders;
|
||||
import cz.moneta.test.harness.support.rest.RestRequest;
|
||||
|
||||
public class KycBuilder extends CaGwBuilder {
|
||||
public class KycBuilder {
|
||||
private final Harness harness;
|
||||
|
||||
public KycBuilder(Harness harness) {
|
||||
super(harness);
|
||||
this.harness = harness;
|
||||
}
|
||||
|
||||
public BusinessCardBuilder bussinesCard() {
|
||||
return new BusinessCardBuilder(harness);
|
||||
}
|
||||
|
||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
||||
}
|
||||
}
|
||||
@ -1,16 +1,22 @@
|
||||
package cz.moneta.test.dsl.cagw.api.v2.external.client.kyc.businesscard;
|
||||
|
||||
import cz.moneta.test.dsl.Harness;
|
||||
import cz.moneta.test.dsl.cagw.CaGwBuilder;
|
||||
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
|
||||
import cz.moneta.test.harness.support.rest.Builders;
|
||||
import cz.moneta.test.harness.support.rest.RestRequest;
|
||||
|
||||
public class BusinessCardBuilder extends CaGwBuilder {
|
||||
public class BusinessCardBuilder {
|
||||
private final Harness harness;
|
||||
|
||||
public BusinessCardBuilder(Harness harness) {
|
||||
super(harness);
|
||||
this.harness = harness;
|
||||
}
|
||||
|
||||
public BusinessCard prepareBusinessCardRequest() {
|
||||
return getBuilder(BusinessCard.class);
|
||||
}
|
||||
|
||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
||||
}
|
||||
}
|
||||
@ -1,17 +1,23 @@
|
||||
package cz.moneta.test.dsl.cagw.api.v4;
|
||||
|
||||
import cz.moneta.test.dsl.Harness;
|
||||
import cz.moneta.test.dsl.cagw.CaGwBuilder;
|
||||
import cz.moneta.test.dsl.cagw.api.v4.token.TokenBuilder;
|
||||
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
|
||||
import cz.moneta.test.harness.support.rest.Builders;
|
||||
import cz.moneta.test.harness.support.rest.RestRequest;
|
||||
|
||||
public class V4Builder extends CaGwBuilder {
|
||||
public class V4Builder {
|
||||
private final Harness harness;
|
||||
|
||||
public V4Builder(Harness harness) {
|
||||
super(harness);
|
||||
this.harness = harness;
|
||||
}
|
||||
|
||||
public TokenBuilder token() {
|
||||
return new TokenBuilder(harness);
|
||||
}
|
||||
|
||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
||||
}
|
||||
}
|
||||
@ -1,16 +1,23 @@
|
||||
package cz.moneta.test.dsl.cagw.api.v4.token;
|
||||
|
||||
import cz.moneta.test.dsl.Harness;
|
||||
import cz.moneta.test.dsl.cagw.CaGwBuilder;
|
||||
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
|
||||
import cz.moneta.test.harness.support.rest.Builders;
|
||||
import cz.moneta.test.harness.support.rest.RestRequest;
|
||||
|
||||
public class TokenBuilder extends CaGwBuilder {
|
||||
public class TokenBuilder {
|
||||
|
||||
private final Harness harness;
|
||||
|
||||
public TokenBuilder(Harness harness) {
|
||||
super(harness);
|
||||
this.harness = harness;
|
||||
}
|
||||
|
||||
public ApiV4Token prepareTokenRequest() {
|
||||
return getBuilder(ApiV4Token.class);
|
||||
}
|
||||
|
||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
||||
}
|
||||
}
|
||||
@ -1,17 +1,23 @@
|
||||
package cz.moneta.test.dsl.cagw.auth;
|
||||
|
||||
import cz.moneta.test.dsl.Harness;
|
||||
import cz.moneta.test.dsl.cagw.CaGwBuilder;
|
||||
import cz.moneta.test.dsl.cagw.auth.oauth.OauthBuilder;
|
||||
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
|
||||
import cz.moneta.test.harness.support.rest.Builders;
|
||||
import cz.moneta.test.harness.support.rest.RestRequest;
|
||||
|
||||
public class AuthBuilder extends CaGwBuilder {
|
||||
public class AuthBuilder {
|
||||
private final Harness harness;
|
||||
|
||||
public AuthBuilder(Harness harness) {
|
||||
super(harness);
|
||||
this.harness = harness;
|
||||
}
|
||||
|
||||
public OauthBuilder oauth() {
|
||||
return new OauthBuilder(harness);
|
||||
}
|
||||
|
||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
||||
}
|
||||
}
|
||||
@ -1,17 +1,23 @@
|
||||
package cz.moneta.test.dsl.cagw.auth.oauth;
|
||||
|
||||
import cz.moneta.test.dsl.Harness;
|
||||
import cz.moneta.test.dsl.cagw.CaGwBuilder;
|
||||
import cz.moneta.test.dsl.cagw.auth.oauth.v2.V2Builder;
|
||||
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
|
||||
import cz.moneta.test.harness.support.rest.Builders;
|
||||
import cz.moneta.test.harness.support.rest.RestRequest;
|
||||
|
||||
public class OauthBuilder extends CaGwBuilder {
|
||||
public class OauthBuilder {
|
||||
private final Harness harness;
|
||||
|
||||
public OauthBuilder(Harness harness) {
|
||||
super(harness);
|
||||
this.harness = harness;
|
||||
}
|
||||
|
||||
public V2Builder v2() {
|
||||
return new V2Builder(harness);
|
||||
}
|
||||
|
||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
||||
}
|
||||
}
|
||||
@ -1,17 +1,23 @@
|
||||
package cz.moneta.test.dsl.cagw.auth.oauth.v2;
|
||||
|
||||
import cz.moneta.test.dsl.Harness;
|
||||
import cz.moneta.test.dsl.cagw.CaGwBuilder;
|
||||
import cz.moneta.test.dsl.cagw.auth.oauth.v2.token.TokenBuilder;
|
||||
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
|
||||
import cz.moneta.test.harness.support.rest.Builders;
|
||||
import cz.moneta.test.harness.support.rest.RestRequest;
|
||||
|
||||
public class V2Builder extends CaGwBuilder {
|
||||
public class V2Builder {
|
||||
private final Harness harness;
|
||||
|
||||
public V2Builder(Harness harness) {
|
||||
super(harness);
|
||||
this.harness = harness;
|
||||
}
|
||||
|
||||
public TokenBuilder token() {
|
||||
return new TokenBuilder(harness);
|
||||
}
|
||||
|
||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
||||
}
|
||||
}
|
||||
@ -1,17 +1,24 @@
|
||||
package cz.moneta.test.dsl.cagw.auth.oauth.v2.token;
|
||||
|
||||
import cz.moneta.test.dsl.Harness;
|
||||
import cz.moneta.test.dsl.cagw.CaGwBuilder;
|
||||
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
|
||||
import cz.moneta.test.harness.support.rest.Builders;
|
||||
import cz.moneta.test.harness.support.rest.RestRequest;
|
||||
|
||||
|
||||
public class TokenBuilder extends CaGwBuilder {
|
||||
public class TokenBuilder {
|
||||
|
||||
private final Harness harness;
|
||||
|
||||
public TokenBuilder(Harness harness) {
|
||||
super(harness);
|
||||
this.harness = harness;
|
||||
}
|
||||
|
||||
public OauthToken prepareOauth2V2TokenRequest() {
|
||||
return getBuilder(OauthToken.class);
|
||||
}
|
||||
|
||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
||||
}
|
||||
}
|
||||
@ -1,17 +1,23 @@
|
||||
package cz.moneta.test.dsl.cagw.oauth2;
|
||||
|
||||
import cz.moneta.test.dsl.Harness;
|
||||
import cz.moneta.test.dsl.cagw.CaGwBuilder;
|
||||
import cz.moneta.test.dsl.cagw.oauth2.token.TokenBuilder;
|
||||
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
|
||||
import cz.moneta.test.harness.support.rest.Builders;
|
||||
import cz.moneta.test.harness.support.rest.RestRequest;
|
||||
|
||||
public class Oauth2Builder extends CaGwBuilder {
|
||||
public class Oauth2Builder {
|
||||
private final Harness harness;
|
||||
|
||||
public Oauth2Builder(Harness harness) {
|
||||
super(harness);
|
||||
this.harness = harness;
|
||||
}
|
||||
|
||||
public TokenBuilder token() {
|
||||
return new TokenBuilder(harness);
|
||||
}
|
||||
|
||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
||||
}
|
||||
}
|
||||
@ -1,18 +1,24 @@
|
||||
package cz.moneta.test.dsl.cagw.oauth2.token;
|
||||
|
||||
import cz.moneta.test.dsl.Harness;
|
||||
import cz.moneta.test.dsl.cagw.CaGwBuilder;
|
||||
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
|
||||
import cz.moneta.test.harness.support.rest.Builders;
|
||||
import cz.moneta.test.harness.support.rest.RestRequest;
|
||||
|
||||
|
||||
public class TokenBuilder extends CaGwBuilder {
|
||||
public class TokenBuilder {
|
||||
|
||||
private final Harness harness;
|
||||
|
||||
public TokenBuilder(Harness harness) {
|
||||
super(harness);
|
||||
this.harness = harness;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public Oauth2Token prepareOauth2TokenRequest() {
|
||||
return getBuilder(Oauth2Token.class);
|
||||
}
|
||||
|
||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,5 @@
|
||||
package cz.moneta.test.dsl.exevido.components;
|
||||
|
||||
import cz.moneta.test.dsl.exevido.pages.inheritance.FoldersPage;
|
||||
import cz.moneta.test.dsl.exevido.pages.IncomingMessagesPage;
|
||||
import cz.moneta.test.dsl.exevido.pages.OutgoingMessagesPage;
|
||||
import cz.moneta.test.harness.support.web.Click;
|
||||
@ -10,7 +9,6 @@ import cz.moneta.test.harness.support.web.Wait;
|
||||
|
||||
import static cz.moneta.test.dsl.exevido.components.ExevidoPanels.LOADER_DIV;
|
||||
import static cz.moneta.test.dsl.exevido.components.LeftMenu.PAGE_SA_SMART_MENU;
|
||||
import static cz.moneta.test.dsl.exevido.pages.inheritance.FoldersPage.PAGE_LABEL_XPATH;
|
||||
|
||||
@Wait(PAGE_SA_SMART_MENU)
|
||||
@Wait(value = LOADER_DIV, until = Until.GONE)
|
||||
@ -24,7 +22,6 @@ public interface LeftMenu<T> {
|
||||
String CATEGORY_INHERITANCE_MMB_XPATH = "//li[@id='Dědictví - MMB']";
|
||||
String SUBCATEGORY_WITHOUT_FOLDER_XPATH = CATEGORY_INHERITANCE_MMB_XPATH + "//a[contains(., 'Bez spisu')]";
|
||||
String SUBCATEGORY_FOR_PROCESSING_XPATH = CATEGORY_INHERITANCE_MMB_XPATH + "//a[contains(., 'Ke zpracování')]";
|
||||
String SUBCATEGORY_FOLDERS_XPATH = CATEGORY_INHERITANCE_MMB_XPATH + "//a[contains(., 'Spisy')]";
|
||||
String FOR_PROCESSING_D_REQUEST_MMB_XPATH = CATEGORY_INHERITANCE_MMB_XPATH + "//a[contains(., 'D - Žádost [M] - MMB')]";
|
||||
String REFRESH_COUNTERS_BUTTON = "refresh_counters";
|
||||
|
||||
@ -40,7 +37,7 @@ public interface LeftMenu<T> {
|
||||
@Click(SUBCATEGORY_ALL_OUTGOING_MESSAGES_A)
|
||||
OutgoingMessagesPage clickAllOutgoingMessages();
|
||||
|
||||
@Click(value = CATEGORY_INHERITANCE_MMB_XPATH, by = Lookup.XPATH, andWait = @Wait(value = SUBCATEGORY_WITHOUT_FOLDER_XPATH, by = Lookup.XPATH, until = Until.VISIBLE))
|
||||
@Click(value = CATEGORY_INHERITANCE_MMB_XPATH, by = Lookup.XPATH, andWait = @Wait(value = SUBCATEGORY_WITHOUT_FOLDER_XPATH, by = Lookup.XPATH ,until = Until.VISIBLE))
|
||||
@Click(value = SUBCATEGORY_WITHOUT_FOLDER_XPATH, by = Lookup.XPATH, andWait = @Wait(value = LOADER_DIV, until = Until.GONE))
|
||||
IncomingMessagesPage clickInheritanceMmbWithoutFolder();
|
||||
|
||||
@ -48,10 +45,6 @@ public interface LeftMenu<T> {
|
||||
@Click(value = SUBCATEGORY_FOR_PROCESSING_XPATH, by = Lookup.XPATH, andWait = @Wait(value = FOR_PROCESSING_D_REQUEST_MMB_XPATH, by = Lookup.XPATH, until = Until.VISIBLE))
|
||||
LeftMenu clickInheritanceMmbForProcessing();
|
||||
|
||||
@Click(value = CATEGORY_INHERITANCE_MMB_XPATH, by = Lookup.XPATH, andWait = @Wait(value = SUBCATEGORY_FOR_PROCESSING_XPATH, by = Lookup.XPATH, until = Until.VISIBLE))
|
||||
@Click(value = SUBCATEGORY_FOLDERS_XPATH, by = Lookup.XPATH, andWait = @Wait(value = PAGE_LABEL_XPATH, by = Lookup.XPATH, until = Until.VISIBLE))
|
||||
FoldersPage clickInheritanceMmbFolders();
|
||||
|
||||
@Click(value = FOR_PROCESSING_D_REQUEST_MMB_XPATH, by = Lookup.XPATH, andWait = @Wait(value = LOADER_DIV, until = Until.GONE))
|
||||
IncomingMessagesPage clickRequestMmb();
|
||||
|
||||
|
||||
@ -36,11 +36,6 @@ public interface DetailIncomingMessagePage extends ExevidoWebFlow<DetailIncoming
|
||||
String DOWNLOAD_ZFO = "//button[normalize-space()='Stáhnout ZFO']";
|
||||
String MESSAGE_RETURN_P = "//p[text()='Zpráva byla vrácena na podatelnu k dalšímu zpracování']";
|
||||
String INTERNAL_REFERENCE_NUMBER = "//accordion-group[.//b[text()='Spisy']]//table//tr/td[2][normalize-space()='%s']";
|
||||
String ADD_MESSAGE_FOLDER_BUTTON = "add_message_folder_button";
|
||||
String ADD_FOLDER_ISSUE_NUMBER_INPUT = "add_folder_issue_number";
|
||||
String SAVE_FOLDER_ISSUE_NUMBER = "//td//i[@title='Přiřadit ke spisu']";
|
||||
String REMOVE_FOLDER_ISSUE_NUMBER = "//tr[td[2][normalize-space(text())='%s']]//td[4]//i[@title='Odebrat spis']";
|
||||
String CONFIRM_BUTTON = "confirm_button";
|
||||
String MESSAGE_ID_SPAN = "message_id";
|
||||
|
||||
@Click(ASSIGN_BUTTON)
|
||||
@ -124,25 +119,6 @@ public interface DetailIncomingMessagePage extends ExevidoWebFlow<DetailIncoming
|
||||
@CheckElementPresent(value = INTERNAL_REFERENCE_NUMBER, by = Lookup.XPATH, isStringDynamicXpath = true)
|
||||
DetailIncomingMessagePage checkInternalReferenceNumber(String referenceNumber);
|
||||
|
||||
@CustomAction
|
||||
default DetailIncomingMessagePage clickAddMessageFolder() {
|
||||
ExevidoEndpoint endpoint = getEndpoint(ExevidoEndpoint.class);
|
||||
endpoint.moveToElement(ADD_MESSAGE_FOLDER_BUTTON);
|
||||
endpoint.click(() -> ADD_MESSAGE_FOLDER_BUTTON);
|
||||
return null;
|
||||
}
|
||||
|
||||
@TypeInto(value = ADD_FOLDER_ISSUE_NUMBER_INPUT, clear = true, andWait = @Wait(value = ExevidoPanels.LOADER_DIV, until = Until.GONE))
|
||||
@KeyPress(Key.ENTER)
|
||||
DetailIncomingMessagePage fillFolderIssueNumber(String folderIssueNumber);
|
||||
|
||||
@Click(value = SAVE_FOLDER_ISSUE_NUMBER, by = Lookup.XPATH, andWait = @Wait(value = ExevidoPanels.LOADER_DIV, until = Until.GONE))
|
||||
DetailIncomingMessagePage saveFolderIssueNumber();
|
||||
|
||||
@Click(value = REMOVE_FOLDER_ISSUE_NUMBER, by = Lookup.XPATH, isStringDynamicXpath = true)
|
||||
@Click(CONFIRM_BUTTON)
|
||||
DetailIncomingMessagePage removeFolderIssueNumber(String folderIssueNumber);
|
||||
|
||||
@CheckElementContent(value = MESSAGE_ID_SPAN)
|
||||
NewIncomingMessagePage checkMessageId(String id);
|
||||
}
|
||||
@ -49,15 +49,6 @@ public interface IncomingMessagesPage extends ExevidoWebFlow<IncomingMessagesPag
|
||||
return null;
|
||||
}
|
||||
|
||||
@CustomAction
|
||||
default IncomingMessagesPage verifyIncomingMessageRowNotPresent() {
|
||||
ExevidoEndpoint endpoint = getEndpoint(ExevidoEndpoint.class);
|
||||
if (endpoint.isElementPresent(INCOMING_MESSAGE_ROW, Lookup.ID)) {
|
||||
throw new AssertionError("Incoming message row should NOT be present.");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@TypeInto(value = DATE_RANGE_ACCEPTANCE_TIME, clear = true)
|
||||
@KeyPress(value = Key.ENTER, andWait = @Wait(value = ExevidoPanels.LOADER_DIV, until = Until.GONE))
|
||||
IncomingMessagesPage searchByDateRange(String date);
|
||||
|
||||
@ -42,8 +42,8 @@ public interface NewOutgoingMessagePage extends ExevidoWebFlow<NewOutgoingMessag
|
||||
String MESSAGE_IS_SENDING_DIV = "message_identification";
|
||||
String MESSAGE_SUCCESSFULLY_SEND_LOG_XPATH = "//span[contains(text(), 'byla odeslána')]";
|
||||
String MESSAGE_SEND_FAILURE_LOG_XPATH = "//span[contains(text(), 'selhání odeslání zprávy')]";
|
||||
String ATTACHMENT_PATH = "regression/exevido/web/testExevido.txt";
|
||||
String SECOND_ATTACHMENT_PATH = "regression/exevido/web/testExevido2.txt";
|
||||
String ATTACHMENT_PATH = "regression/exevido/testExevido.txt";
|
||||
String SECOND_ATTACHMENT_PATH = "regression/exevido/testExevido2.txt";
|
||||
String MESSAGE_ID_SPAN = "message_id";
|
||||
String MULTIPLE_RECIPIENTS_SPAN = "//label[@for='multiple_recipients']";
|
||||
String ADD_RECIPIENT_TO_LIST_BUTTON = "add_to_list_button";
|
||||
|
||||
@ -1,25 +0,0 @@
|
||||
package cz.moneta.test.dsl.exevido.pages.inheritance;
|
||||
|
||||
import cz.moneta.test.dsl.exevido.ExevidoWebFlow;
|
||||
import cz.moneta.test.dsl.exevido.pages.DetailIncomingMessagePage;
|
||||
import cz.moneta.test.harness.context.StoreAccessor;
|
||||
import cz.moneta.test.harness.endpoints.exevido.ExevidoEndpoint;
|
||||
import cz.moneta.test.harness.support.web.*;
|
||||
|
||||
import static cz.moneta.test.dsl.exevido.pages.inheritance.DetailFolderPage.PAGE_LABEL_XPATH;
|
||||
|
||||
@Wait(value = PAGE_LABEL_XPATH, by = Lookup.XPATH)
|
||||
public interface DetailFolderPage extends ExevidoWebFlow<DetailFolderPage>, StoreAccessor {
|
||||
|
||||
String PAGE_LABEL_XPATH = "//b[contains(text(),'Spis:')]";
|
||||
String OUTGOING_MESSAGES_LABEL_XPATH = "//a[contains(text(),'Odchozí zprávy')]";
|
||||
String ROW_BY_MESSAGE_ID = "//tr[contains(@class,'clickable-row')]//td[2]/div[normalize-space()='%s']/ancestor::tr";
|
||||
|
||||
@CustomAction
|
||||
default DetailIncomingMessagePage openMessageById(String messageId) {
|
||||
ExevidoEndpoint endpoint = getEndpoint(ExevidoEndpoint.class);
|
||||
endpoint.moveToElement(OUTGOING_MESSAGES_LABEL_XPATH, Lookup.XPATH);
|
||||
endpoint.click(() -> String.format(ROW_BY_MESSAGE_ID, messageId), Lookup.XPATH);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -1,26 +0,0 @@
|
||||
package cz.moneta.test.dsl.exevido.pages.inheritance;
|
||||
|
||||
import cz.moneta.test.dsl.exevido.ExevidoWebFlow;
|
||||
import cz.moneta.test.dsl.exevido.components.ExevidoPanels;
|
||||
import cz.moneta.test.harness.context.StoreAccessor;
|
||||
import cz.moneta.test.harness.endpoints.exevido.ExevidoEndpoint;
|
||||
import cz.moneta.test.harness.support.web.*;
|
||||
|
||||
import static cz.moneta.test.dsl.exevido.pages.inheritance.FoldersPage.PAGE_LABEL_XPATH;
|
||||
|
||||
|
||||
@Wait(value = PAGE_LABEL_XPATH, by = Lookup.XPATH)
|
||||
public interface FoldersPage extends ExevidoWebFlow<FoldersPage>, StoreAccessor {
|
||||
|
||||
String PAGE_LABEL_XPATH = "//b[contains(text(),'Spisy')]";
|
||||
String PRODUCT_OWNER_NAME_INPUT = "text_product_owner_name";
|
||||
String FOLDER_MESSAGE_ROW = "folder_0_row";
|
||||
|
||||
@TypeInto(value = PRODUCT_OWNER_NAME_INPUT)
|
||||
@KeyPress(value = Key.ENTER, andWait = @Wait(value = ExevidoPanels.LOADER_DIV, until = Until.GONE))
|
||||
FoldersPage fillProductOwnerName(String name);
|
||||
|
||||
@Click(value = FOLDER_MESSAGE_ROW, andWait = @Wait(value = ExevidoPanels.LOADER_DIV, until = Until.GONE))
|
||||
DetailFolderPage clickFolderRow();
|
||||
|
||||
}
|
||||
@ -97,7 +97,7 @@ public class HyposContractPrepare {
|
||||
.realtyPriceCurrent(3500000)
|
||||
.realtyType(RealtyType.FLAT.getValue())
|
||||
.pledgeType(PledgeType.PROPERTY_HOUSING.getValue())
|
||||
.contractRelation(ContractRelation.PLEDGE_WITH_CURRENT_REALTY.getValue())
|
||||
.contractRelation(ContranctRelation.PLEDGE_WITH_CURRENT_REALTY.getValue())
|
||||
.collateralType(CollateralType.FLAT.getValue())
|
||||
.appraiserCompany(AppraiserCompany.MONETA_SUPERVISION.getValue())
|
||||
.build();
|
||||
|
||||
@ -13,4 +13,4 @@ public enum AppraiserCompany {
|
||||
public String getValue() {
|
||||
return appraiserCompany;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
package cz.moneta.test.dsl.hypos.enums;
|
||||
|
||||
import cz.moneta.test.harness.support.web.SelectByValue;
|
||||
|
||||
public enum CadasterRequestType implements SelectByValue {
|
||||
public enum CadasterRequestType {
|
||||
PART_OWNER_LIST("Část. výpis LV"),
|
||||
CADASTER_MAP("Kat. mapa"),
|
||||
OWNER_LIST("List vlastnictví"),
|
||||
@ -14,7 +12,6 @@ public enum CadasterRequestType implements SelectByValue {
|
||||
this.cadasterRequestType = cadasterRequestType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue() {
|
||||
return cadasterRequestType;
|
||||
}
|
||||
|
||||
@ -12,4 +12,4 @@ public enum CollateralCode {
|
||||
public String getValue() {
|
||||
return collateralCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
package cz.moneta.test.dsl.hypos.enums;
|
||||
|
||||
import cz.moneta.test.harness.support.web.SelectByValue;
|
||||
|
||||
public enum CollectableFee implements SelectByValue {
|
||||
public enum CollectableFee {
|
||||
APPENDIX_CLIENT_INITIATIVE("DODATEK - Z PODNĚTU KLIENTA"),
|
||||
FEE_DOWNLOAD_LV_KM("Poplatek za stažení LV/KM"),
|
||||
OTHER_BANK_INFORMATION("OSTATNÍ - BANKOVNÍ INFORMACE");
|
||||
@ -13,7 +11,6 @@ public enum CollectableFee implements SelectByValue {
|
||||
this.collectableFee = collectableFee;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getValue() {
|
||||
return collectableFee;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user