Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
70c4540c4a | ||
|
|
b94a071fda | ||
|
|
51ee61e328 | ||
|
|
c43a0eacb0 | ||
|
|
923f43e008 | ||
|
|
4923d498ed | ||
|
|
4629a2fae7 | ||
|
|
26b6354875 | ||
|
|
e7d9acf13b | ||
|
|
23a5e9972d | ||
|
|
320bde9a39 | ||
|
|
486950c2b4 | ||
|
|
97b77a911f | ||
|
|
0833bcff06 | ||
|
|
579246d772 | ||
|
|
e6dd8c286b | ||
|
|
ae4bb84eef | ||
|
|
c4492913ad | ||
|
|
ce54e336ba | ||
|
|
ef4562ab74 | ||
|
|
6cf0aab157 | ||
|
|
7d0a1b953f |
1341
kafka_ibm_mq_support_c8518eaa.plan 2.md
Normal file
1341
kafka_ibm_mq_support_c8518eaa.plan 2.md
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,55 +0,0 @@
|
|||||||
<?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>
|
|
||||||
@ -1,69 +0,0 @@
|
|||||||
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,10 +29,11 @@
|
|||||||
<commons-beanutils.version>1.9.3</commons-beanutils.version>
|
<commons-beanutils.version>1.9.3</commons-beanutils.version>
|
||||||
<commons-configuration.version>1.6</commons-configuration.version>
|
<commons-configuration.version>1.6</commons-configuration.version>
|
||||||
<cxf.version>4.0.3</cxf.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>
|
<ibm.mq.version>9.4.5.0</ibm.mq.version>
|
||||||
<jakarta.jms.version>3.1.0</jakarta.jms.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>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
@ -270,48 +271,6 @@
|
|||||||
<version>${appium-java-client.version}</version>
|
<version>${appium-java-client.version}</version>
|
||||||
</dependency>
|
</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 -->
|
<!-- Used for Web Services connector -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.cxf</groupId>
|
<groupId>org.apache.cxf</groupId>
|
||||||
@ -331,6 +290,44 @@
|
|||||||
<version>${cxf.version}</version>
|
<version>${cxf.version}</version>
|
||||||
</dependency>
|
</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 -->
|
<!-- Used in Wso2ConnectorServlet -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>javax.servlet</groupId>
|
<groupId>javax.servlet</groupId>
|
||||||
@ -404,23 +401,6 @@
|
|||||||
|
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<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>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
|||||||
@ -1,264 +1,502 @@
|
|||||||
package cz.moneta.test.harness.connectors.messaging;
|
package cz.moneta.test.harness.connectors.messaging;
|
||||||
|
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.time.Duration;
|
import java.security.KeyStore;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.function.Predicate;
|
|
||||||
|
|
||||||
import javax.jms.BytesMessage;
|
import javax.jms.BytesMessage;
|
||||||
import javax.jms.JMSConsumer;
|
import javax.jms.JMSConsumer;
|
||||||
import javax.jms.JMSContext;
|
import javax.jms.JMSContext;
|
||||||
import javax.jms.JMSException;
|
import javax.jms.JMSException;
|
||||||
import javax.jms.JMSProducer;
|
import javax.jms.JMSRuntimeException;
|
||||||
import javax.jms.Message;
|
import javax.jms.Message;
|
||||||
import javax.jms.Queue;
|
|
||||||
import javax.jms.QueueBrowser;
|
|
||||||
import javax.jms.TextMessage;
|
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.mq.jms.MQConnectionFactory;
|
||||||
import com.ibm.msg.client.wmq.WMQConstants;
|
import com.ibm.msg.client.wmq.WMQConstants;
|
||||||
|
|
||||||
import cz.moneta.test.harness.connectors.Connector;
|
import cz.moneta.test.harness.connectors.Connector;
|
||||||
import cz.moneta.test.harness.exception.MessagingTimeoutException;
|
import cz.moneta.test.harness.messaging.MessageContentType;
|
||||||
import cz.moneta.test.harness.messaging.model.MessageContentType;
|
import cz.moneta.test.harness.messaging.MqMessageFormat;
|
||||||
import cz.moneta.test.harness.messaging.model.MqMessageFormat;
|
import cz.moneta.test.harness.messaging.ReceivedMessage;
|
||||||
import cz.moneta.test.harness.messaging.model.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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 {
|
public class IbmMqConnector implements Connector {
|
||||||
|
|
||||||
private static final Charset EBCDIC_870 = Charset.forName("IBM870");
|
private static final Logger LOG = LogManager.getLogger(IbmMqConnector.class);
|
||||||
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;
|
|
||||||
|
|
||||||
public IbmMqConnector(String host,
|
private static final Charset EBCDIC_870 = Charset.forName("IBM870");
|
||||||
int port,
|
private static final Charset UTF_8 = StandardCharsets.UTF_8;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
connectionFactory = new MQConnectionFactory();
|
private static final long DEFAULT_POLL_INTERVAL_MS = 100;
|
||||||
connectionFactory.setHostName(host);
|
private static final long DEFAULT_MAX_POLL_INTERVAL_MS = 1000;
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cipherSuite != null && !cipherSuite.isBlank()) {
|
private static final String TLS_VERSION = "TLSv1.2";
|
||||||
connectionFactory.setSSLCipherSuite(cipherSuite);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new IllegalStateException("Failed to initialize IBM MQ connection factory", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void send(String queueName,
|
private final MQConnectionFactory connectionFactory;
|
||||||
String payload,
|
private JMSContext jmsContext;
|
||||||
MqMessageFormat format,
|
private final String queueManager;
|
||||||
Map<String, String> properties) {
|
private final String user;
|
||||||
switch (Objects.requireNonNull(format, "format")) {
|
private final String password;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReceivedMessage receive(String queueName,
|
/**
|
||||||
String messageSelector,
|
* Constructor with multi-instance Queue Manager support.
|
||||||
MqMessageFormat expectedFormat,
|
*
|
||||||
Duration timeout) {
|
* @param connectionNameList Connection name list in format
|
||||||
JMSContext context = getContext();
|
* "host1(port1),host2(port2)"
|
||||||
Queue queue = context.createQueue("queue:///" + queueName);
|
* @param channel MQ channel name
|
||||||
try (JMSConsumer consumer = messageSelector == null || messageSelector.isBlank()
|
* @param queueManager Queue Manager name
|
||||||
? context.createConsumer(queue)
|
* @param user Username for authentication
|
||||||
: context.createConsumer(queue, messageSelector)) {
|
* @param password Password for authentication
|
||||||
Message message = consumer.receive(Optional.ofNullable(timeout).orElse(Duration.ofSeconds(30)).toMillis());
|
* @param keystorePath Path to SSL keystore (can be null for non-SSL)
|
||||||
if (message == null) {
|
* @param keystorePassword Password for SSL keystore
|
||||||
throw new MessagingTimeoutException("Timeout waiting for IBM MQ message from queue: " + queueName);
|
* @param sslCipherSuite SSL cipher suite to use (e.g.,
|
||||||
}
|
* "TLS_RSA_WITH_AES_256_CBC_SHA256")
|
||||||
return toReceivedMessage(message, queueName, expectedFormat);
|
*/
|
||||||
}
|
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 List<ReceivedMessage> browse(String queueName,
|
try {
|
||||||
Predicate<ReceivedMessage> filter,
|
connectionFactory = new MQConnectionFactory();
|
||||||
MqMessageFormat expectedFormat,
|
connectionFactory.setConnectionNameList(connectionNameList);
|
||||||
Duration timeout) {
|
connectionFactory.setQueueManager(queueManager);
|
||||||
long timeoutMillis = Optional.ofNullable(timeout).orElse(Duration.ofSeconds(30)).toMillis();
|
connectionFactory.setChannel(channel);
|
||||||
long deadline = System.currentTimeMillis() + timeoutMillis;
|
connectionFactory.setTransportType(WMQConstants.WMQ_CM_CLIENT);
|
||||||
long backoff = 100;
|
if (user != null && !user.isBlank()) {
|
||||||
JMSContext context = getContext();
|
connectionFactory.setStringProperty(WMQConstants.USERID, user);
|
||||||
Queue queue = context.createQueue("queue:///" + queueName);
|
}
|
||||||
|
if (password != null && !password.isBlank()) {
|
||||||
|
connectionFactory.setStringProperty(WMQConstants.PASSWORD, password);
|
||||||
|
}
|
||||||
|
|
||||||
while (System.currentTimeMillis() < deadline) {
|
if (keystorePath != null && !keystorePath.isBlank() && keystorePassword != null
|
||||||
List<ReceivedMessage> matched = new ArrayList<>();
|
&& !keystorePassword.isBlank()) {
|
||||||
try (QueueBrowser browser = context.createBrowser(queue)) {
|
connectionFactory.setSSLSocketFactory(getSslSocketFactory(keystorePath, keystorePassword));
|
||||||
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 (!matched.isEmpty()) {
|
if (sslCipherSuite != null && !sslCipherSuite.isBlank()) {
|
||||||
return matched;
|
connectionFactory.setSSLCipherSuite(sslCipherSuite);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
// Initialize JMS context
|
||||||
TimeUnit.MILLISECONDS.sleep(backoff);
|
connect();
|
||||||
} 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
} catch (Exception e) {
|
||||||
public void close() {
|
throw new MessagingConnectionException("Failed to create IBM MQ connection to " + queueManager, e);
|
||||||
JMSContext context = jmsContext;
|
}
|
||||||
if (context != null) {
|
}
|
||||||
context.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendTextMessage(String queueName, String payload, Map<String, String> properties) {
|
/**
|
||||||
JMSContext context = getContext();
|
* Connect to IBM MQ.
|
||||||
JMSProducer producer = context.createProducer();
|
*/
|
||||||
TextMessage message = context.createTextMessage(payload);
|
private void connect() {
|
||||||
applyProperties(message, properties);
|
try {
|
||||||
producer.send(context.createQueue("queue:///" + queueName), message);
|
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 sendBytesMessage(String queueName,
|
/**
|
||||||
String payload,
|
* Send a JSON or XML message as TextMessage.
|
||||||
Charset charset,
|
*/
|
||||||
int ccsid,
|
private void sendTextMessage(String queueName, String payload, Map<String, String> properties) {
|
||||||
Map<String, String> properties) {
|
javax.jms.Queue queue = getQueue(queueName);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void applyProperties(Message message, Map<String, String> properties) {
|
TextMessage message = jmsContext.createTextMessage(payload);
|
||||||
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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private ReceivedMessage toReceivedMessage(Message message, String queueName, MqMessageFormat format) {
|
// Set JMS properties
|
||||||
try {
|
if (properties != null) {
|
||||||
Map<String, String> headers = new LinkedHashMap<>();
|
for (Map.Entry<String, String> entry : properties.entrySet()) {
|
||||||
Enumeration<?> names = message.getPropertyNames();
|
try {
|
||||||
while (names.hasMoreElements()) {
|
if (entry.getKey().equals(ImqRequest.PROP_JMS_CORRELATION_ID)) {
|
||||||
String name = String.valueOf(names.nextElement());
|
message.setJMSCorrelationID(entry.getValue());
|
||||||
headers.put(name, String.valueOf(message.getObjectProperty(name)));
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
String body = decodeMessage(message, format);
|
try {
|
||||||
MessageContentType contentType = resolveContentType(message, format);
|
jmsContext.createProducer().send(queue, message);
|
||||||
return new ReceivedMessage(body, contentType, headers, message.getJMSTimestamp(), queueName);
|
} catch (RuntimeException e) {
|
||||||
} catch (JMSException e) {
|
throw new MessagingDestinationException("Failed to send message to queue: " + queueName, e);
|
||||||
throw new IllegalStateException("Failed to decode IBM MQ message", e);
|
}
|
||||||
}
|
LOG.debug("Sent JSON/XML message to queue: {}", queueName);
|
||||||
}
|
}
|
||||||
|
|
||||||
private MessageContentType resolveContentType(Message message, MqMessageFormat expectedFormat) {
|
/**
|
||||||
if (message instanceof TextMessage) {
|
* Send a message as BytesMessage with specific encoding and CCSID.
|
||||||
return expectedFormat == MqMessageFormat.XML ? MessageContentType.XML : MessageContentType.JSON;
|
*/
|
||||||
}
|
private void sendBytesMessage(String queueName, String payload, Charset charset, int ccsid,
|
||||||
if (expectedFormat == MqMessageFormat.XML) {
|
Map<String, String> properties) {
|
||||||
return MessageContentType.XML;
|
javax.jms.Queue queue = getQueue(queueName);
|
||||||
}
|
|
||||||
if (expectedFormat == MqMessageFormat.JSON) {
|
|
||||||
return MessageContentType.JSON;
|
|
||||||
}
|
|
||||||
return MessageContentType.RAW_TEXT;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String decodeMessage(Message jmsMessage, MqMessageFormat format) {
|
BytesMessage message = jmsContext.createBytesMessage();
|
||||||
try {
|
|
||||||
if (jmsMessage instanceof TextMessage textMessage) {
|
|
||||||
return textMessage.getText();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (jmsMessage instanceof BytesMessage bytesMessage) {
|
// Convert payload to bytes using specified charset
|
||||||
byte[] data = new byte[(int) bytesMessage.getBodyLength()];
|
byte[] bytes = payload.getBytes(charset);
|
||||||
bytesMessage.readBytes(data);
|
try {
|
||||||
Charset charset = switch (format) {
|
message.writeBytes(bytes);
|
||||||
case EBCDIC_870 -> EBCDIC_870;
|
message.setIntProperty("CCSID", ccsid);
|
||||||
case UTF8_1208, JSON, XML -> UTF_8;
|
} catch (JMSException e) {
|
||||||
};
|
throw new MessagingDestinationException("Failed to create bytes message", e);
|
||||||
return new String(data, charset);
|
}
|
||||||
}
|
|
||||||
return "";
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new IllegalStateException("Failed to decode JMS message", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private JMSContext getContext() {
|
// Set JMS properties
|
||||||
JMSContext current = jmsContext;
|
if (properties != null) {
|
||||||
if (current == null) {
|
for (Map.Entry<String, String> entry : properties.entrySet()) {
|
||||||
synchronized (contextLock) {
|
try {
|
||||||
current = jmsContext;
|
if (entry.getKey().equals(ImqRequest.PROP_JMS_CORRELATION_ID)) {
|
||||||
if (current == null) {
|
message.setJMSCorrelationID(entry.getValue());
|
||||||
jmsContext = current = connectionFactory.createContext(user, password, JMSContext.AUTO_ACKNOWLEDGE);
|
continue;
|
||||||
}
|
} else if (entry.getKey().equals(ImqRequest.PROP_JMS_TYPE)) {
|
||||||
}
|
message.setJMSType(entry.getValue());
|
||||||
}
|
continue;
|
||||||
return current;
|
} 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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,115 @@
|
|||||||
|
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,18 +1,16 @@
|
|||||||
package cz.moneta.test.harness.connectors.messaging;
|
package cz.moneta.test.harness.connectors.messaging;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import java.nio.charset.StandardCharsets;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import java.time.Duration;
|
||||||
import cz.moneta.test.harness.connectors.Connector;
|
import java.util.ArrayList;
|
||||||
import cz.moneta.test.harness.exception.MessagingTimeoutException;
|
import java.util.Collections;
|
||||||
import cz.moneta.test.harness.messaging.model.MessageContentType;
|
import java.util.HashMap;
|
||||||
import cz.moneta.test.harness.messaging.model.ReceivedMessage;
|
import java.util.List;
|
||||||
import io.confluent.kafka.schemaregistry.client.CachedSchemaRegistryClient;
|
import java.util.Map;
|
||||||
import io.confluent.kafka.serializers.AbstractKafkaSchemaSerDeConfig;
|
import java.util.Properties;
|
||||||
import io.confluent.kafka.serializers.KafkaAvroDeserializer;
|
import java.util.concurrent.ExecutionException;
|
||||||
import io.confluent.kafka.serializers.KafkaAvroDeserializerConfig;
|
import java.util.function.Predicate;
|
||||||
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.avro.generic.GenericRecord;
|
||||||
import org.apache.kafka.clients.consumer.ConsumerConfig;
|
import org.apache.kafka.clients.consumer.ConsumerConfig;
|
||||||
import org.apache.kafka.clients.consumer.ConsumerRecord;
|
import org.apache.kafka.clients.consumer.ConsumerRecord;
|
||||||
@ -22,332 +20,329 @@ import org.apache.kafka.clients.producer.KafkaProducer;
|
|||||||
import org.apache.kafka.clients.producer.ProducerConfig;
|
import org.apache.kafka.clients.producer.ProducerConfig;
|
||||||
import org.apache.kafka.clients.producer.ProducerRecord;
|
import org.apache.kafka.clients.producer.ProducerRecord;
|
||||||
import org.apache.kafka.common.TopicPartition;
|
import org.apache.kafka.common.TopicPartition;
|
||||||
import org.apache.kafka.common.header.Header;
|
import org.apache.kafka.common.header.Headers;
|
||||||
import org.apache.kafka.common.serialization.StringDeserializer;
|
import org.apache.kafka.common.serialization.StringDeserializer;
|
||||||
import org.apache.kafka.common.serialization.StringSerializer;
|
import org.apache.kafka.common.serialization.StringSerializer;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import cz.moneta.test.harness.messaging.exception.MessagingConnectionException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import cz.moneta.test.harness.messaging.exception.MessagingDestinationException;
|
||||||
import java.time.Duration;
|
import cz.moneta.test.harness.messaging.exception.MessagingSchemaException;
|
||||||
import java.util.ArrayList;
|
import cz.moneta.test.harness.messaging.exception.MessagingTimeoutException;
|
||||||
import java.util.Base64;
|
import cz.moneta.test.harness.support.messaging.kafka.MessageContentType;
|
||||||
import java.util.HashMap;
|
import cz.moneta.test.harness.support.messaging.kafka.ReceivedMessage;
|
||||||
import java.util.LinkedHashMap;
|
import io.confluent.kafka.schemaregistry.client.CachedSchemaRegistryClient;
|
||||||
import java.util.List;
|
import io.confluent.kafka.serializers.KafkaAvroDeserializer;
|
||||||
import java.util.Map;
|
import io.confluent.kafka.serializers.KafkaAvroSerializer;
|
||||||
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;
|
|
||||||
|
|
||||||
public class KafkaConnector implements Connector {
|
/**
|
||||||
|
* 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 {
|
||||||
|
|
||||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
private static final Logger LOG = LoggerFactory.getLogger(KafkaConnector.class);
|
||||||
|
|
||||||
private final Properties producerProps = new Properties();
|
private final Properties producerConfig;
|
||||||
private final Properties consumerProps = new Properties();
|
private final Properties consumerConfig;
|
||||||
|
private final String schemaRegistryUrl;
|
||||||
private final CachedSchemaRegistryClient schemaRegistryClient;
|
private final CachedSchemaRegistryClient schemaRegistryClient;
|
||||||
private volatile KafkaProducer<String, GenericRecord> producer;
|
private 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,
|
public KafkaConnector(String bootstrapServers,
|
||||||
String apiKey,
|
String apiKey,
|
||||||
String apiSecret,
|
String apiSecret,
|
||||||
String schemaRegistryUrl,
|
String schemaRegistryUrl,
|
||||||
String schemaRegistryApiKey,
|
String schemaRegistryApiKey,
|
||||||
String schemaRegistryApiSecret) {
|
String schemaRegistryApiSecret) {
|
||||||
String jaasConfig = String.format("org.apache.kafka.common.security.plain.PlainLoginModule required username=\"%s\" password=\"%s\";",
|
this.schemaRegistryUrl = schemaRegistryUrl;
|
||||||
apiKey,
|
this.schemaRegistryClient = new CachedSchemaRegistryClient(
|
||||||
apiSecret);
|
Collections.singletonList(schemaRegistryUrl), 100, new HashMap<>());
|
||||||
String schemaRegistryAuth = schemaRegistryApiKey + ":" + schemaRegistryApiSecret;
|
|
||||||
|
|
||||||
producerProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
|
this.producerConfig = createProducerConfig(bootstrapServers, apiKey, apiSecret);
|
||||||
producerProps.put("security.protocol", "SASL_SSL");
|
this.consumerConfig = createConsumerConfig(bootstrapServers, schemaRegistryApiKey, schemaRegistryApiSecret);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void send(String topic, String key, String jsonPayload, Map<String, String> headers) {
|
/**
|
||||||
Objects.requireNonNull(topic, "topic");
|
* Creates producer configuration.
|
||||||
Schema schema = getSchemaForTopic(topic);
|
*/
|
||||||
GenericRecord record = jsonToAvro(jsonPayload, schema);
|
private Properties createProducerConfig(String bootstrapServers, String apiKey, String apiSecret) {
|
||||||
ProducerRecord<String, GenericRecord> producerRecord = new ProducerRecord<>(topic, key, record);
|
Properties config = new Properties();
|
||||||
Optional.ofNullable(headers).orElseGet(HashMap::new)
|
config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
|
||||||
.forEach((headerKey, headerValue) -> producerRecord.headers()
|
config.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
|
||||||
.add(headerKey, String.valueOf(headerValue).getBytes(StandardCharsets.UTF_8)));
|
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) {
|
||||||
try {
|
try {
|
||||||
getProducer().send(producerRecord).get(30, TimeUnit.SECONDS);
|
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;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new IllegalStateException("Failed to send Kafka message to topic: " + topic, e);
|
throw new MessagingSchemaException(
|
||||||
|
"Failed to convert JSON to Avro for topic " + topic, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Receives a message from a Kafka topic matching the filter.
|
||||||
|
*/
|
||||||
public List<ReceivedMessage> receive(String topic,
|
public List<ReceivedMessage> receive(String topic,
|
||||||
Predicate<ReceivedMessage> filter,
|
Predicate<ReceivedMessage> filter,
|
||||||
Duration timeout) {
|
Duration timeout) {
|
||||||
long timeoutMillis = Optional.ofNullable(timeout).orElse(Duration.ofSeconds(30)).toMillis();
|
KafkaConsumer<String, GenericRecord> consumer = null;
|
||||||
long deadline = System.currentTimeMillis() + timeoutMillis;
|
try {
|
||||||
long backoff = 100;
|
consumer = new KafkaConsumer<>(consumerConfig);
|
||||||
|
|
||||||
try (KafkaConsumer<String, Object> consumer = createConsumer()) {
|
// Get partitions for the topic
|
||||||
List<TopicPartition> partitions = consumer.partitionsFor(topic).stream()
|
List<TopicPartition> partitions = getPartitionsForTopic(consumer, topic);
|
||||||
.map(info -> new TopicPartition(topic, info.partition()))
|
if (partitions.isEmpty()) {
|
||||||
.toList();
|
throw new MessagingDestinationException(
|
||||||
consumer.assign(partitions);
|
"Topic '" + topic + "' does not exist or has no partitions");
|
||||||
consumer.seekToEnd(partitions);
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<TopicPartition, Long> saveOffsets(String topic) {
|
// 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.assign(partitions);
|
||||||
consumer.seekToEnd(partitions);
|
// consumer.seekToBeginning(partitions);
|
||||||
Map<TopicPartition, Long> offsets = new HashMap<>();
|
consumer.seekToBeginning(partitions);
|
||||||
partitions.forEach(partition -> offsets.put(partition, consumer.position(partition)));
|
|
||||||
return offsets;
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the connector and releases resources.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
KafkaProducer<String, GenericRecord> current = producer;
|
if (producer != null) {
|
||||||
if (current != null) {
|
producer.close();
|
||||||
current.close(Duration.ofSeconds(5));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets or creates the producer (singleton, thread-safe).
|
||||||
|
*/
|
||||||
private KafkaProducer<String, GenericRecord> getProducer() {
|
private KafkaProducer<String, GenericRecord> getProducer() {
|
||||||
KafkaProducer<String, GenericRecord> current = producer;
|
if (producer == null) {
|
||||||
if (current == null) {
|
synchronized (this) {
|
||||||
synchronized (producerLock) {
|
if (producer == null) {
|
||||||
current = producer;
|
producer = new KafkaProducer<>(producerConfig);
|
||||||
if (current == null) {
|
|
||||||
producer = current = new KafkaProducer<>(producerProps);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return current;
|
return producer;
|
||||||
}
|
}
|
||||||
|
|
||||||
private KafkaConsumer<String, Object> createConsumer() {
|
/**
|
||||||
Properties properties = new Properties();
|
* Retrieves schema from Schema Registry based on topic name.
|
||||||
properties.putAll(consumerProps);
|
*/
|
||||||
properties.put(ConsumerConfig.GROUP_ID_CONFIG, "harness-" + UUID.randomUUID());
|
private org.apache.avro.Schema getSchemaForTopic(String topic) {
|
||||||
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";
|
String subject = topic + "-value";
|
||||||
try {
|
try {
|
||||||
// Get all versions and use the latest one
|
io.confluent.kafka.schemaregistry.client.SchemaMetadata metadata =
|
||||||
java.util.List<Integer> versions = schemaRegistryClient.getAllVersions(subject);
|
schemaRegistryClient.getLatestSchemaMetadata(subject);
|
||||||
int latestVersion = versions.get(versions.size() - 1);
|
return new org.apache.avro.Schema.Parser().parse(metadata.getSchema());
|
||||||
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) {
|
} catch (Exception e) {
|
||||||
throw new IllegalStateException("Failed to get schema for subject: " + subject, 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
private String avroToJson(GenericRecord record) {
|
||||||
try {
|
try {
|
||||||
return OBJECT_MAPPER.writeValueAsString(convertAvroObject(record));
|
return record.toString();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new IllegalStateException("Failed to convert Avro record to JSON", e);
|
throw new RuntimeException("Failed to convert Avro to JSON: " + e.getMessage(), 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,4 +55,5 @@ public class CaGwEndpoint implements RestEndpoint {
|
|||||||
public StoreAccessor getStore() {
|
public StoreAccessor getStore() {
|
||||||
return store;
|
return store;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,199 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,51 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,199 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,125 +0,0 @@
|
|||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,119 +0,0 @@
|
|||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,130 +0,0 @@
|
|||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,51 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,8 +0,0 @@
|
|||||||
package cz.moneta.test.harness.exception;
|
|
||||||
|
|
||||||
public class MessagingTimeoutException extends HarnessException {
|
|
||||||
|
|
||||||
public MessagingTimeoutException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,83 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@ -0,0 +1,111 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@ -0,0 +1,386 @@
|
|||||||
|
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 + '\'' +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,22 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
package cz.moneta.test.harness.messaging.model;
|
|
||||||
|
|
||||||
public enum MessageContentType {
|
|
||||||
JSON,
|
|
||||||
XML,
|
|
||||||
RAW_TEXT
|
|
||||||
}
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,167 +0,0 @@
|
|||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,595 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,65 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@ -0,0 +1,90 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
@ -1,193 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@ -0,0 +1,304 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,568 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@ -0,0 +1,139 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,365 @@
|
|||||||
|
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) {
|
public Template(String templateString) {
|
||||||
this.template = new ST(templateString, '$', '$');
|
this.template = new ST(templateString);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Template set(String variable, String value) {
|
public Template set(String variable, String value) {
|
||||||
|
|||||||
BIN
test-harness/src/main/resources/keystores/imq-keystore.jks
Normal file
BIN
test-harness/src/main/resources/keystores/imq-keystore.jks
Normal file
Binary file not shown.
@ -1,7 +0,0 @@
|
|||||||
<?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,95 +1,77 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project
|
<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"
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
<modelVersion>4.0.0</modelVersion>
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
<groupId>cz.moneta.test</groupId>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<artifactId>tests</artifactId>
|
||||||
<groupId>cz.moneta.test</groupId>
|
<version>2.29-SNAPSHOT</version>
|
||||||
<artifactId>tests</artifactId>
|
<properties>
|
||||||
<version>2.28-SNAPSHOT</version>
|
<harness.version>7.55-SNAPSHOT</harness.version>
|
||||||
<properties>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<harness.version>7.55-SNAPSHOT</harness.version>
|
<junit.platform.version>1.5.1</junit.platform.version>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
</properties>
|
||||||
<junit.platform.version>1.5.1</junit.platform.version>
|
<dependencies>
|
||||||
<ibm.mq.version>9.4.5.0</ibm.mq.version>
|
<dependency>
|
||||||
</properties>
|
<groupId>cz.moneta.test</groupId>
|
||||||
<dependencies>
|
<artifactId>harness</artifactId>
|
||||||
<dependency>
|
<version>${harness.version}</version>
|
||||||
<groupId>cz.moneta.test</groupId>
|
<scope>compile</scope>
|
||||||
<artifactId>harness</artifactId>
|
</dependency>
|
||||||
<version>${harness.version}</version>
|
<dependency>
|
||||||
</dependency>
|
<groupId>org.slf4j</groupId>
|
||||||
<dependency>
|
<artifactId>slf4j-api</artifactId>
|
||||||
<groupId>com.ibm.mq</groupId>
|
<version>1.7.25</version>
|
||||||
<artifactId>com.ibm.mq.allclient</artifactId>
|
</dependency>
|
||||||
<version>${ibm.mq.version}</version>
|
<dependency>
|
||||||
</dependency>
|
<groupId>com.fasterxml.jackson.dataformat</groupId>
|
||||||
<!-- StringTemplate -->
|
<artifactId>jackson-dataformat-csv</artifactId>
|
||||||
<dependency>
|
<version>2.16.1</version>
|
||||||
<groupId>org.antlr</groupId>
|
</dependency>
|
||||||
<artifactId>ST4</artifactId>
|
<dependency>
|
||||||
<version>4.3.4</version>
|
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||||
</dependency>
|
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||||
<dependency>
|
<version>2.16.1</version>
|
||||||
<groupId>javax.jms</groupId>
|
</dependency>
|
||||||
<artifactId>javax.jms-api</artifactId>
|
<dependency>
|
||||||
<version>2.0.1</version>
|
<groupId>org.projectlombok</groupId>
|
||||||
</dependency>
|
<artifactId>lombok</artifactId>
|
||||||
<dependency>
|
<version>1.18.30</version>
|
||||||
<groupId>org.slf4j</groupId>
|
<scope>provided</scope>
|
||||||
<artifactId>slf4j-api</artifactId>
|
</dependency>
|
||||||
<version>1.7.25</version>
|
<dependency>
|
||||||
</dependency>
|
<groupId>com.microsoft.sqlserver</groupId>
|
||||||
<dependency>
|
<artifactId>mssql-jdbc_auth</artifactId>
|
||||||
<groupId>com.fasterxml.jackson.dataformat</groupId>
|
<version>8.2.0.x86</version>
|
||||||
<artifactId>jackson-dataformat-csv</artifactId>
|
<type>dll</type>
|
||||||
<version>2.16.1</version>
|
</dependency>
|
||||||
</dependency>
|
<dependency>
|
||||||
<dependency>
|
<groupId>org.jetbrains</groupId>
|
||||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
<artifactId>annotations</artifactId>
|
||||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
<version>15.0</version>
|
||||||
<version>2.16.1</version>
|
</dependency>
|
||||||
</dependency>
|
<dependency>
|
||||||
<dependency>
|
<groupId>org.junit.platform</groupId>
|
||||||
<groupId>org.projectlombok</groupId>
|
<artifactId>junit-platform-reporting</artifactId>
|
||||||
<artifactId>lombok</artifactId>
|
<version>${junit.platform.version}</version>
|
||||||
<version>1.18.30</version>
|
</dependency>
|
||||||
<scope>provided</scope>
|
<dependency>
|
||||||
</dependency>
|
<groupId>org.junit.platform</groupId>
|
||||||
<dependency>
|
<artifactId>junit-platform-console</artifactId>
|
||||||
<groupId>com.microsoft.sqlserver</groupId>
|
<version>${junit.platform.version}</version>
|
||||||
<artifactId>mssql-jdbc_auth</artifactId>
|
</dependency>
|
||||||
<version>8.2.0.x86</version>
|
<dependency>
|
||||||
<type>dll</type>
|
<groupId>org.yaml</groupId>
|
||||||
</dependency>
|
<artifactId>snakeyaml</artifactId>
|
||||||
<dependency>
|
<version>1.26</version>
|
||||||
<groupId>org.jetbrains</groupId>
|
</dependency>
|
||||||
<artifactId>annotations</artifactId>
|
</dependencies>
|
||||||
<version>15.0</version>
|
<profiles>
|
||||||
</dependency>
|
<profile>
|
||||||
<dependency>
|
<id>Moneta Artifactory</id>
|
||||||
<groupId>org.junit.platform</groupId>
|
<activation>
|
||||||
<artifactId>junit-platform-reporting</artifactId>
|
<activeByDefault>true</activeByDefault>
|
||||||
<version>${junit.platform.version}</version>
|
</activation>
|
||||||
</dependency>
|
<build>
|
||||||
<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>
|
<plugins>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
@ -109,37 +91,30 @@
|
|||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
<version>3.13.0</version>
|
<version>3.13.0</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<source>17</source>
|
<source>17</source>
|
||||||
<target>17</target>
|
<target>17</target>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<artifactId>maven-surefire-plugin</artifactId>
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
<version>2.22.2</version>
|
<version>2.22.2</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<argLine>
|
<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
|
||||||
--add-opens
|
-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>
|
||||||
java.base/java.lang.invoke=ALL-UNNAMED
|
<includes>
|
||||||
-Dhttp.proxyHost=wsa-aws.mbid.cz
|
<include>cz.moneta.test.sandbox.demo.HarnessDemoTest.java</include>
|
||||||
-Dhttp.proxyPort=8008
|
</includes>
|
||||||
-Dhttps.proxyHost=wsa-aws.mbid.cz
|
<trimStackTrace>false</trimStackTrace>
|
||||||
-Dhttps.proxyPort=8008
|
<useFile>false</useFile>
|
||||||
-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>
|
</configuration>
|
||||||
<includes>
|
</plugin>
|
||||||
<include>
|
</plugins>
|
||||||
cz.moneta.test.sandbox.demo.HarnessDemoTest.java</include>
|
</build>
|
||||||
</includes>
|
<repositories>
|
||||||
<trimStackTrace>false</trimStackTrace>
|
|
||||||
<useFile>false</useFile>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
<repositories>
|
|
||||||
<repository>
|
<repository>
|
||||||
<snapshots>
|
<snapshots>
|
||||||
<enabled>false</enabled>
|
<enabled>false</enabled>
|
||||||
@ -173,129 +148,122 @@
|
|||||||
<name>MVN Repository</name>
|
<name>MVN Repository</name>
|
||||||
<url>https://mvnrepository.com/artifact/</url>
|
<url>https://mvnrepository.com/artifact/</url>
|
||||||
</repository>
|
</repository>
|
||||||
</repositories>
|
</repositories>
|
||||||
<pluginRepositories>
|
<pluginRepositories>
|
||||||
<pluginRepository>
|
<pluginRepository>
|
||||||
<releases>
|
<releases>
|
||||||
<enabled>true</enabled>
|
<enabled>true</enabled>
|
||||||
</releases>
|
</releases>
|
||||||
<snapshots>
|
<snapshots>
|
||||||
<enabled>false</enabled>
|
<enabled>false</enabled>
|
||||||
</snapshots>
|
</snapshots>
|
||||||
<id>central</id>
|
<id>central</id>
|
||||||
<name>plugins-release</name>
|
<name>plugins-release</name>
|
||||||
<url>
|
<url>https://artifactory-aws.ux.mbid.cz/artifactory/plugins-release</url>
|
||||||
https://artifactory-aws.ux.mbid.cz/artifactory/plugins-release</url>
|
</pluginRepository>
|
||||||
</pluginRepository>
|
<pluginRepository>
|
||||||
<pluginRepository>
|
<releases>
|
||||||
<releases>
|
<enabled>false</enabled>
|
||||||
<enabled>false</enabled>
|
</releases>
|
||||||
</releases>
|
<snapshots>
|
||||||
<snapshots>
|
<enabled>true</enabled>
|
||||||
<enabled>true</enabled>
|
</snapshots>
|
||||||
</snapshots>
|
<id>snapshots</id>
|
||||||
<id>snapshots</id>
|
<name>plugins-snapshot</name>
|
||||||
<name>plugins-snapshot</name>
|
<url>https://artifactory-aws.ux.mbid.cz/artifactory/plugins-snapshot</url>
|
||||||
<url>
|
</pluginRepository>
|
||||||
https://artifactory-aws.ux.mbid.cz/artifactory/plugins-snapshot</url>
|
</pluginRepositories>
|
||||||
</pluginRepository>
|
</profile>
|
||||||
</pluginRepositories>
|
<profile>
|
||||||
</profile>
|
<id>build.package</id>
|
||||||
<profile>
|
<activation>
|
||||||
<id>build.package</id>
|
<property>
|
||||||
<activation>
|
<name>build.package</name>
|
||||||
<property>
|
<value>true</value>
|
||||||
<name>build.package</name>
|
</property>
|
||||||
<value>true</value>
|
</activation>
|
||||||
</property>
|
<build>
|
||||||
</activation>
|
<plugins>
|
||||||
<build>
|
<plugin>
|
||||||
<plugins>
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
<plugin>
|
<version>3.13.0</version>
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
<configuration>
|
||||||
<version>3.13.0</version>
|
<source>17</source>
|
||||||
<configuration>
|
<target>17</target>
|
||||||
<source>17</source>
|
</configuration>
|
||||||
<target>17</target>
|
</plugin>
|
||||||
</configuration>
|
<plugin>
|
||||||
</plugin>
|
<artifactId>maven-assembly-plugin</artifactId>
|
||||||
<plugin>
|
<version>3.6.0</version>
|
||||||
<artifactId>maven-assembly-plugin</artifactId>
|
<executions>
|
||||||
<version>3.6.0</version>
|
<execution>
|
||||||
<executions>
|
<phase>package</phase>
|
||||||
<execution>
|
<goals>
|
||||||
<phase>package</phase>
|
<goal>single</goal>
|
||||||
<goals>
|
</goals>
|
||||||
<goal>single</goal>
|
<configuration>
|
||||||
</goals>
|
<archive>
|
||||||
<configuration>
|
<manifest>
|
||||||
<archive>
|
<mainClass>cz.moneta.test.testrunner.TestRunner</mainClass>
|
||||||
<manifest>
|
</manifest>
|
||||||
<mainClass>
|
</archive>
|
||||||
cz.moneta.test.testrunner.TestRunner</mainClass>
|
<descriptors>
|
||||||
</manifest>
|
<descriptor>src/assembly/assembly.xml</descriptor>
|
||||||
</archive>
|
</descriptors>
|
||||||
<descriptors>
|
</configuration>
|
||||||
<descriptor>src/assembly/assembly.xml</descriptor>
|
</execution>
|
||||||
</descriptors>
|
</executions>
|
||||||
</configuration>
|
</plugin>
|
||||||
</execution>
|
</plugins>
|
||||||
</executions>
|
</build>
|
||||||
</plugin>
|
<repositories>
|
||||||
</plugins>
|
<repository>
|
||||||
</build>
|
<releases>
|
||||||
<repositories>
|
<enabled>true</enabled>
|
||||||
<repository>
|
</releases>
|
||||||
<releases>
|
<snapshots>
|
||||||
<enabled>true</enabled>
|
<enabled>false</enabled>
|
||||||
</releases>
|
</snapshots>
|
||||||
<snapshots>
|
<id>central</id>
|
||||||
<enabled>false</enabled>
|
<name>libs-release</name>
|
||||||
</snapshots>
|
<url>https://artifactory-aws.ux.mbid.cz/artifactory/libs-release</url>
|
||||||
<id>central</id>
|
</repository>
|
||||||
<name>libs-release</name>
|
<repository>
|
||||||
<url>
|
<releases>
|
||||||
https://artifactory-aws.ux.mbid.cz/artifactory/libs-release</url>
|
<enabled>false</enabled>
|
||||||
</repository>
|
</releases>
|
||||||
<repository>
|
<snapshots>
|
||||||
<releases>
|
<enabled>true</enabled>
|
||||||
<enabled>false</enabled>
|
</snapshots>
|
||||||
</releases>
|
<id>snapshots</id>
|
||||||
<snapshots>
|
<name>libs-snapshot</name>
|
||||||
<enabled>true</enabled>
|
<url>https://artifactory-aws.ux.mbid.cz/artifactory/libs-snapshot</url>
|
||||||
</snapshots>
|
</repository>
|
||||||
<id>snapshots</id>
|
</repositories>
|
||||||
<name>libs-snapshot</name>
|
<pluginRepositories>
|
||||||
<url>
|
<pluginRepository>
|
||||||
https://artifactory-aws.ux.mbid.cz/artifactory/libs-snapshot</url>
|
<releases>
|
||||||
</repository>
|
<enabled>true</enabled>
|
||||||
</repositories>
|
</releases>
|
||||||
<pluginRepositories>
|
<snapshots>
|
||||||
<pluginRepository>
|
<enabled>false</enabled>
|
||||||
<releases>
|
</snapshots>
|
||||||
<enabled>true</enabled>
|
<id>central</id>
|
||||||
</releases>
|
<name>plugins-release</name>
|
||||||
<snapshots>
|
<url>https://artifactory-aws.ux.mbid.cz/artifactory/plugins-release</url>
|
||||||
<enabled>false</enabled>
|
</pluginRepository>
|
||||||
</snapshots>
|
<pluginRepository>
|
||||||
<id>central</id>
|
<releases>
|
||||||
<name>plugins-release</name>
|
<enabled>false</enabled>
|
||||||
<url>
|
</releases>
|
||||||
https://artifactory-aws.ux.mbid.cz/artifactory/plugins-release</url>
|
<snapshots>
|
||||||
</pluginRepository>
|
<enabled>true</enabled>
|
||||||
<pluginRepository>
|
</snapshots>
|
||||||
<releases>
|
<id>snapshots</id>
|
||||||
<enabled>false</enabled>
|
<name>plugins-snapshot</name>
|
||||||
</releases>
|
<url>https://artifactory-aws.ux.mbid.cz/artifactory/plugins-snapshot</url>
|
||||||
<snapshots>
|
</pluginRepository>
|
||||||
<enabled>true</enabled>
|
</pluginRepositories>
|
||||||
</snapshots>
|
</profile>
|
||||||
<id>snapshots</id>
|
</profiles>
|
||||||
<name>plugins-snapshot</name>
|
|
||||||
<url>
|
|
||||||
https://artifactory-aws.ux.mbid.cz/artifactory/plugins-snapshot</url>
|
|
||||||
</pluginRepository>
|
|
||||||
</pluginRepositories>
|
|
||||||
</profile>
|
|
||||||
</profiles>
|
|
||||||
</project>
|
</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.broadcom.Broadcom;
|
||||||
import cz.moneta.test.dsl.brokerportal.BrokerPortal;
|
import cz.moneta.test.dsl.brokerportal.BrokerPortal;
|
||||||
import cz.moneta.test.dsl.caapi.CaApiBuilder;
|
import cz.moneta.test.dsl.caapi.CaApiBuilder;
|
||||||
import cz.moneta.test.dsl.cagw.CaGwBuilder;
|
import cz.moneta.test.dsl.cagw.CaGw;
|
||||||
import cz.moneta.test.dsl.cashman.Cashman;
|
import cz.moneta.test.dsl.cashman.Cashman;
|
||||||
import cz.moneta.test.dsl.cebia.Cebia;
|
import cz.moneta.test.dsl.cebia.Cebia;
|
||||||
import cz.moneta.test.dsl.demo.Spirits;
|
import cz.moneta.test.dsl.demo.Spirits;
|
||||||
@ -23,9 +23,10 @@ import cz.moneta.test.dsl.greenscreen.GreenScreen;
|
|||||||
import cz.moneta.test.dsl.hypos.Hypos;
|
import cz.moneta.test.dsl.hypos.Hypos;
|
||||||
import cz.moneta.test.dsl.ib.Ib;
|
import cz.moneta.test.dsl.ib.Ib;
|
||||||
import cz.moneta.test.dsl.ilods.Ilods;
|
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.kasanova.Kasanova;
|
||||||
import cz.moneta.test.dsl.mobile.smartbanking.home.Sb;
|
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.monetaapiportal.MonetaApiPortal;
|
||||||
import cz.moneta.test.dsl.monetaportal.MonetaPortal;
|
import cz.moneta.test.dsl.monetaportal.MonetaPortal;
|
||||||
import cz.moneta.test.dsl.mwf.IHub;
|
import cz.moneta.test.dsl.mwf.IHub;
|
||||||
@ -111,8 +112,8 @@ public class Harness extends BaseStoreAccessor {
|
|||||||
return new CaApiBuilder(this);
|
return new CaApiBuilder(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CaGwBuilder withCaGw() {
|
public CaGw withCaGw() {
|
||||||
return new CaGwBuilder(this);
|
return new CaGw(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@ -283,8 +284,12 @@ public class Harness extends BaseStoreAccessor {
|
|||||||
return new Cashman(this);
|
return new Cashman(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Messaging withMessaging() {
|
public ImqFirstVision withImqFirstVision() {
|
||||||
return new Messaging(this);
|
return new ImqFirstVision(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Kafka withKafka() {
|
||||||
|
return new Kafka(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initGenerators() {
|
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_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 = "(//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_FINANCE_PRODUCT_CHOOSE_BUTTON_DEFAULT = "(//div/h3[contains(text(), '%s')])/..//following-sibling::div//button//span[contains(text(), 'Vybrat')][1]/..";
|
||||||
String VEHICLE_CAR_INSURANCE = "//span[contains(text(),'Povinné ručení')]/following-sibling::div/div[contains(text(),'Vyberte záznam ...')][1]";
|
String VEHICLE_CAR_INSURANCE = "//h4/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_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 = "(//DIV//p//span[contains(text(), 'Vyberte záznam ...')])[1]";
|
||||||
String VEHICLE_CAR_BENEFITS_OPTION_DEFAULT = "(//label//span[contains(text(), '%s')])[1]/..";
|
String VEHICLE_CAR_BENEFITS_OPTION_DEFAULT = "(//label//span[contains(text(), '%s')])[1]/..";
|
||||||
@ -75,6 +75,7 @@ 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_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_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_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_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_WEIGHT_AGRO = "//label[contains(.,'Hmotnost')]/following-sibling::div/div/input";
|
||||||
String VEHICLE_CAR_INSURANCE_VEHICLE_FUEL_AGRO = "//label[contains(.,'Palivo')]/following-sibling::div/button";
|
String VEHICLE_CAR_INSURANCE_VEHICLE_FUEL_AGRO = "//label[contains(.,'Palivo')]/following-sibling::div/button";
|
||||||
@ -84,7 +85,8 @@ 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_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_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_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='Tabs']/div[2]/div[3]/div/div[3]/div/div/button[contains(.,'Vybrat')]";
|
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 CREATE_APPLICATION = "//span[contains(text(), 'Vytvořit žádost')]/parent::button";
|
String CREATE_APPLICATION = "//span[contains(text(), 'Vytvořit žádost')]/parent::button";
|
||||||
String DIV_CLICK_BLOCKER = "//div[@class='_21KP5GbzHYNm19YwGmWy0r']";
|
String DIV_CLICK_BLOCKER = "//div[@class='_21KP5GbzHYNm19YwGmWy0r']";
|
||||||
String DIV_CLICK_BLOCKER_2 = "//div[@class='_3Lp0XIzdhx1ktbMqO92O8T']";
|
String DIV_CLICK_BLOCKER_2 = "//div[@class='_3Lp0XIzdhx1ktbMqO92O8T']";
|
||||||
@ -251,8 +253,8 @@ public interface NewCalculationPage extends SmartAutoFlow<NewCalculationPage>, S
|
|||||||
@TypeInto(value = VEHICLE_CAR_INSURANCE_CUSTOMER_NATIONALITY, clear = true)
|
@TypeInto(value = VEHICLE_CAR_INSURANCE_CUSTOMER_NATIONALITY, clear = true)
|
||||||
NewCalculationPage typeVehicleInsuranceCustomerNationality(String nationality);
|
NewCalculationPage typeVehicleInsuranceCustomerNationality(String nationality);
|
||||||
|
|
||||||
@Wait(VEHICLE_CAR_INSURANCE_CUSTOMER_NATIONALITY_FIELD)
|
@Wait(value = VEHICLE_CAR_INSURANCE_CUSTOMER_NATIONALITY_FIELD)
|
||||||
@Click(value = VEHICLE_CAR_INSURANCE_CUSTOMER_NATIONALITY_FIELD)
|
@Click(value = VEHICLE_CAR_INSURANCE_CUSTOMER_NATIONALITY_FIELD, jsClick = true)
|
||||||
NewCalculationPage clickVehicleInsuranceCustomerNationality();
|
NewCalculationPage clickVehicleInsuranceCustomerNationality();
|
||||||
|
|
||||||
@Wait(VEHICLE_CAR_INSURANCE_CUSTOMER_ADDRESS)
|
@Wait(VEHICLE_CAR_INSURANCE_CUSTOMER_ADDRESS)
|
||||||
@ -271,9 +273,12 @@ public interface NewCalculationPage extends SmartAutoFlow<NewCalculationPage>, S
|
|||||||
@TypeInto(value = VEHICLE_CAR_INSURANCE_VEHICLE_VOLUME_AGRO)
|
@TypeInto(value = VEHICLE_CAR_INSURANCE_VEHICLE_VOLUME_AGRO)
|
||||||
NewCalculationPage typeVehicleInsuranceVehicleVolume(String volume);
|
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)
|
@Wait(VEHICLE_CAR_INSURANCE_VEHICLE_TAB)
|
||||||
@TypeInto(value = VEHICLE_CAR_INSURANCE_VEHICLE_POWER_AGRO)
|
@TypeInto(value = VEHICLE_CAR_INSURANCE_VEHICLE_POWER_AGRO, andWait = @Wait(explicitWaitSeconds = 2))
|
||||||
NewCalculationPage typeVehicleInsuranceVehiclePower(String power);
|
NewCalculationPage typeVehicleInsuranceVehiclePower(String power);
|
||||||
|
|
||||||
@Wait(VEHICLE_CAR_INSURANCE_VEHICLE_TAB)
|
@Wait(VEHICLE_CAR_INSURANCE_VEHICLE_TAB)
|
||||||
@ -345,6 +350,10 @@ public interface NewCalculationPage extends SmartAutoFlow<NewCalculationPage>, S
|
|||||||
@Click(value = VEHICLE_CAR_INSURANCE_INSURANCE_CHOOSE,jsClick = true)
|
@Click(value = VEHICLE_CAR_INSURANCE_INSURANCE_CHOOSE,jsClick = true)
|
||||||
NewCalculationPage clickVehicleInsuranceInsuranceChoose();
|
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 = {DIV_CLICK_BLOCKER, DIV_CLICK_BLOCKER_2}, until = Until.GONE, waitSecondsForElement = 30)
|
||||||
@Wait(value = VEHICLE_CAR_ACCIDENT_INSURANCE, waitSecondsForElement = 30)
|
@Wait(value = VEHICLE_CAR_ACCIDENT_INSURANCE, waitSecondsForElement = 30)
|
||||||
@Click(value = VEHICLE_CAR_ACCIDENT_INSURANCE, andWait = @Wait(value = CommonElements.MODAL, 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 ACCOUNT_NUMBER = "//input[@name='sign.bankContactC2C.accountNumber']";
|
||||||
String BANK_CODE = "//span[contains(text(), 'Kód banky')]/../../..//input";
|
String BANK_CODE = "//span[contains(text(), 'Kód banky')]/../../..//input";
|
||||||
String BUTTON = "//button[contains(text(), '%s')]";
|
String BUTTON = "//button[contains(text(), '%s')]";
|
||||||
String APPROVAL_MESSAGE = "//span[contains(text(), 'Zpráva pro schvalovatele')]/../../../textarea";
|
String APPROVAL_MESSAGE = "//h6[contains(text(), 'Zpráva pro schvalovatele')]/../../../div/div/span/textarea";
|
||||||
String VEHICLE_PAGE = "//span[contains(text(), 'Předmět')]";
|
String VEHICLE_PAGE = "//span[contains(text(), 'Předmět')]";
|
||||||
String DUPLICATE_APPLICATION = "//span[contains(text(), 'Duplikovat')]";
|
String DUPLICATE_APPLICATION = "//span[contains(text(), 'Duplikovat')]";
|
||||||
String CALCULATION_NAME = "//input[@name='calculationName']";
|
String CALCULATION_NAME = "//input[@name='calculationName']";
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import static cz.moneta.test.dsl.auto.smartauto.QueuePage.*;
|
|||||||
waitSecondsForElement = 40)
|
waitSecondsForElement = 40)
|
||||||
public interface QueuePage extends SmartAutoFlow<QueuePage>, StoreAccessor, CalendarAccessor {
|
public interface QueuePage extends SmartAutoFlow<QueuePage>, StoreAccessor, CalendarAccessor {
|
||||||
|
|
||||||
String HEADING_SEND_APPLICATIONS = "//span[contains(text(), 'Odeslané žádosti')]";
|
String HEADING_SEND_APPLICATIONS = "//h1[contains(text(), 'Odeslané žádosti')]";
|
||||||
String FIRST_APPLICATION = "//div/div/div/button/i";
|
String FIRST_APPLICATION = "//div/div/div/button/i";
|
||||||
String DATE_FROM = "//label[contains(., 'Datum od')]/following-sibling::div/button";
|
String DATE_FROM = "//label[contains(., 'Datum od')]/following-sibling::div/button";
|
||||||
String DATE_TO = "//label[contains(., 'Datum do')]/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 {
|
public interface SavedCalculationsPage extends SmartAutoFlow<SavedCalculationsPage>, StoreAccessor, CalendarAccessor {
|
||||||
|
|
||||||
String HEADING_SAVED_CALCULATIONS = "//h1/span[contains(text(), 'Kalkulace')]";
|
String HEADING_SAVED_CALCULATIONS = "//h1[contains(text(), 'Kalkulace')]";
|
||||||
String FIRST_APPLICATION_DIV = "//h2/../div[1]/div/";
|
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/div[1]";
|
String CALCULATION_NAME = FIRST_APPLICATION_DIV + "/div[2]/div[contains(text(), 'TestDuplikace')]";
|
||||||
String CALCULATION_STATUS = FIRST_APPLICATION_DIV + "/span/span[contains(text(), 'Rozpracovaná žádost')]";
|
String CALCULATION_STATUS = FIRST_APPLICATION_DIV + "//span[contains(text(), 'Rozpracovaná žádost')]";
|
||||||
String CALCULATION_DATE = FIRST_APPLICATION_DIV + "div/div[5]";
|
String CALCULATION_DATE = FIRST_APPLICATION_DIV + "/div[3]/div[2]";
|
||||||
String DATE_FROM = "//label[contains(., 'Datum od')]/following-sibling::div/button";
|
String DATE_FROM = "//label[contains(., 'Datum od')]/following-sibling::div/button";
|
||||||
String DATE_TO = "//label[contains(., 'Datum do')]/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']]";
|
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 SEATS = "//input[@name='vehicle.specificCar.sittingPlaceNr']";
|
||||||
String FUEL = "(//span[contains(text(), 'Palivo')])[2]/../..//following-sibling::div//input[@value='%s']";
|
String FUEL = "(//span[contains(text(), 'Palivo')])[2]/../..//following-sibling::div//input[@value='%s']";
|
||||||
String MILEAGE = "//input[@name='vehicle.specificCar.travel']";
|
String MILEAGE = "//input[@name='vehicle.specificCar.travel']";
|
||||||
String IMPORT_VEHICLE = "//span[contains(text(), 'Vozidlo z dovozu')]/../../../div/div/span/button/span[contains(text(), '%s')]";
|
String IMPORT_VEHICLE = "//span[contains(text(), 'Vozidlo z dovozu')]/../../../div/div/div/span/button//span[contains(text(), '%s')]/..";
|
||||||
String DEALER_IS_OWNER = "//span[contains(text(), 'Dealer je majitelem vozidla')]/../../..//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 DESCRIPTION = "//textarea[@name='vehicle.specificCar.description']";
|
||||||
String OTHERS_PAGE = "//span[contains(text(), 'Ostatní')]";
|
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))
|
@TypeInto(value = DESCRIPTION, andWait = @Wait(value = LOADER_DIV, until = Until.GONE))
|
||||||
VehiclePage typeDescription(String description);
|
VehiclePage typeDescription(String description);
|
||||||
|
|
||||||
@Click(IMPORT_VEHICLE)
|
@Click(value = IMPORT_VEHICLE, jsClick = true)
|
||||||
VehiclePage clickImportVehicle(ImportVehicle importVehicle);
|
VehiclePage clickImportVehicle(ImportVehicle importVehicle);
|
||||||
|
|
||||||
@Click(DEALER_IS_OWNER)
|
@Click(value = DEALER_IS_OWNER,jsClick = true)
|
||||||
VehiclePage clickDealerIsOwner(DealerIsOwner dealerIsOwner);
|
VehiclePage clickDealerIsOwner(DealerIsOwner dealerIsOwner);
|
||||||
|
|
||||||
@Click(value = OTHERS_PAGE, jsClick = true)
|
@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 FROM_DATE_TEXT = "//span[contains(text(), 'Od data')]";
|
||||||
String TO_DATE_TEXT = "//span[contains(text(), 'Do data')]";
|
String TO_DATE_TEXT = "//span[contains(text(), 'Do data')]";
|
||||||
String COMMISSIONS_CREDIT_NOTES_TEXT = "//span[contains(text(), 'Provize/Dobropis')]";
|
String COMMISSIONS_CREDIT_NOTES_TEXT = "//span[contains(text(), 'Provize/Dobropis')]";
|
||||||
String FILTER_TITLE = "//span[contains(text(), 'Filtr')]";
|
String FILTER_TITLE = "//h6[contains(text(), 'Filtr')]";
|
||||||
String BILLING_COMMISSIONS_TITLE = "//span[contains(text(), 'Provize k fakturaci')]";
|
String BILLING_COMMISSIONS_TITLE = "//h6[contains(text(), 'Fakturace')]";
|
||||||
String RESET_BUTTON = "//span[contains(text(), 'Resetovat')]";
|
String RESET_BUTTON = "//span[contains(text(), 'Resetovat')]";
|
||||||
String SEARCH_BUTTON = "//span[contains(text(), 'Vyhledat')]";
|
String SEARCH_BUTTON = "//span[contains(text(), 'Vyhledat')]";
|
||||||
String INVOICE_BUTTON = "//span[contains(text(), 'Vyfakturovat')]";
|
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 TOTAL_AVAILABLE_LIMIT_TEXT = "//span[contains(text(), 'Volný limit')]";
|
||||||
String FOR_PAYMENT_TEXT = "//span[contains(text(), 'K úhradě')]";
|
String FOR_PAYMENT_TEXT = "//span[contains(text(), 'K úhradě')]";
|
||||||
String OVERDUE_PAYMENT_TEXT = "//span[contains(text(), 'Z toho po splatnosti')]";
|
String OVERDUE_PAYMENT_TEXT = "//span[contains(text(), 'Z toho po splatnosti')]";
|
||||||
String PRODUCT_LIST_TITLE = "//span[contains(text(), 'Seznam produktů')]";
|
String PRODUCT_LIST_TITLE = "//h6[contains(text(), 'Produkty')]";
|
||||||
String ALL_CONTRACT_BUTTON = "//span[contains(text(), 'Všechny zakázky')]";
|
String ALL_CONTRACT_BUTTON = "//span[contains(text(), 'Všechny zakázky')]";
|
||||||
|
|
||||||
@CheckElementsPresent(value = {
|
@CheckElementsPresent(value = {
|
||||||
|
|||||||
@ -25,6 +25,7 @@ public class CalculationData {
|
|||||||
private Subsidy subsidy;
|
private Subsidy subsidy;
|
||||||
private ItemType itemType;
|
private ItemType itemType;
|
||||||
private int firstPayment;
|
private int firstPayment;
|
||||||
|
private String numberOfSeats;
|
||||||
|
|
||||||
public CalculationData setCustomer(Customer customer) {
|
public CalculationData setCustomer(Customer customer) {
|
||||||
this.customer = customer;
|
this.customer = customer;
|
||||||
@ -131,4 +132,8 @@ public class CalculationData {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public CalculationData setNumberOfSeats(String numberOfSeats) {
|
||||||
|
this.numberOfSeats = numberOfSeats;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
33
tests/src/main/java/cz/moneta/test/dsl/cagw/CaGw.java
Normal file
33
tests/src/main/java/cz/moneta/test/dsl/cagw/CaGw.java
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
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,34 +1,20 @@
|
|||||||
package cz.moneta.test.dsl.cagw;
|
package cz.moneta.test.dsl.cagw;
|
||||||
|
|
||||||
import cz.moneta.test.dsl.Harness;
|
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.endpoints.cagw.CaGwEndpoint;
|
||||||
import cz.moneta.test.harness.support.rest.Builders;
|
import cz.moneta.test.harness.support.rest.Builders;
|
||||||
import cz.moneta.test.harness.support.rest.RestRequest;
|
import cz.moneta.test.harness.support.rest.RestRequest;
|
||||||
|
|
||||||
public class CaGwBuilder {
|
public class CaGwBuilder {
|
||||||
|
|
||||||
private final Harness harness;
|
protected final Harness harness;
|
||||||
|
|
||||||
public CaGwBuilder(Harness harness) {
|
protected CaGwBuilder(Harness harness) {
|
||||||
this.harness = harness;
|
this.harness = harness;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ApiBuilder api() {
|
protected <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
||||||
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);
|
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
}
|
||||||
@ -1,19 +1,16 @@
|
|||||||
package cz.moneta.test.dsl.cagw.api;
|
package cz.moneta.test.dsl.cagw.api;
|
||||||
|
|
||||||
import cz.moneta.test.dsl.Harness;
|
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.cbl.CblBuilder;
|
||||||
import cz.moneta.test.dsl.cagw.api.v2.V2Builder;
|
import cz.moneta.test.dsl.cagw.api.v2.V2Builder;
|
||||||
import cz.moneta.test.dsl.cagw.api.v4.V4Builder;
|
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.dsl.cagw.api.v1.V1Builder;
|
||||||
import cz.moneta.test.harness.support.rest.Builders;
|
|
||||||
import cz.moneta.test.harness.support.rest.RestRequest;
|
|
||||||
|
|
||||||
public class ApiBuilder {
|
public class ApiBuilder extends CaGwBuilder {
|
||||||
private final Harness harness;
|
|
||||||
|
|
||||||
public ApiBuilder(Harness harness) {
|
public ApiBuilder(Harness harness) {
|
||||||
this.harness = harness;
|
super(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CblBuilder cbl() {
|
public CblBuilder cbl() {
|
||||||
@ -32,7 +29,4 @@ public class ApiBuilder {
|
|||||||
return new V1Builder(harness);
|
return new V1Builder(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
|
||||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -1,23 +1,17 @@
|
|||||||
package cz.moneta.test.dsl.cagw.api.cbl;
|
package cz.moneta.test.dsl.cagw.api.cbl;
|
||||||
|
|
||||||
import cz.moneta.test.dsl.Harness;
|
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.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 {
|
public class CblBuilder extends CaGwBuilder {
|
||||||
private final Harness harness;
|
|
||||||
|
|
||||||
public CblBuilder(Harness harness) {
|
public CblBuilder(Harness harness) {
|
||||||
this.harness = harness;
|
super(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PsdBuilder psd() {
|
public PsdBuilder psd() {
|
||||||
return new PsdBuilder(harness);
|
return new PsdBuilder(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
|
||||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,17 +1,14 @@
|
|||||||
package cz.moneta.test.dsl.cagw.api.cbl.psd;
|
package cz.moneta.test.dsl.cagw.api.cbl.psd;
|
||||||
|
|
||||||
import cz.moneta.test.dsl.Harness;
|
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.aisp.AispBuilder;
|
||||||
import cz.moneta.test.dsl.cagw.api.cbl.psd.pisp.PispBuilder;
|
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 {
|
public class PsdBuilder extends CaGwBuilder {
|
||||||
private final Harness harness;
|
|
||||||
|
|
||||||
public PsdBuilder(Harness harness) {
|
public PsdBuilder(Harness harness) {
|
||||||
this.harness = harness;
|
super(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PispBuilder pisp() {
|
public PispBuilder pisp() {
|
||||||
@ -22,7 +19,4 @@ public class PsdBuilder {
|
|||||||
return new AispBuilder(harness);
|
return new AispBuilder(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
|
||||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -1,23 +1,17 @@
|
|||||||
package cz.moneta.test.dsl.cagw.api.cbl.psd.aisp;
|
package cz.moneta.test.dsl.cagw.api.cbl.psd.aisp;
|
||||||
|
|
||||||
import cz.moneta.test.dsl.Harness;
|
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.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 {
|
public class AispBuilder extends CaGwBuilder {
|
||||||
private final Harness harness;
|
|
||||||
|
|
||||||
public AispBuilder(Harness harness) {
|
public AispBuilder(Harness harness) {
|
||||||
this.harness = harness;
|
super(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MyBuilder my() {
|
public MyBuilder my() {
|
||||||
return new MyBuilder(harness);
|
return new MyBuilder(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
|
||||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -1,19 +1,16 @@
|
|||||||
package cz.moneta.test.dsl.cagw.api.cbl.psd.aisp.my;
|
package cz.moneta.test.dsl.cagw.api.cbl.psd.aisp.my;
|
||||||
|
|
||||||
import cz.moneta.test.dsl.Harness;
|
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.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.AccountsBuilder;
|
||||||
import cz.moneta.test.dsl.cagw.api.cbl.psd.aisp.my.accounts.AccountsWithParams;
|
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 {
|
public class MyBuilder extends CaGwBuilder {
|
||||||
private final Harness harness;
|
|
||||||
|
|
||||||
public MyBuilder(Harness harness) {
|
public MyBuilder(Harness harness) {
|
||||||
this.harness = harness;
|
super(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AccountsBuilder accounts() {
|
public AccountsBuilder accounts() {
|
||||||
@ -28,7 +25,4 @@ public class MyBuilder {
|
|||||||
return getBuilder(AccountsWithParams.class);
|
return getBuilder(AccountsWithParams.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
|
||||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -1,23 +1,17 @@
|
|||||||
package cz.moneta.test.dsl.cagw.api.cbl.psd.aisp.my.accounts;
|
package cz.moneta.test.dsl.cagw.api.cbl.psd.aisp.my.accounts;
|
||||||
|
|
||||||
import cz.moneta.test.dsl.Harness;
|
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.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 {
|
public class AccountsBuilder extends CaGwBuilder {
|
||||||
private final Harness harness;
|
|
||||||
|
|
||||||
public AccountsBuilder(Harness harness) {
|
public AccountsBuilder(Harness harness) {
|
||||||
this.harness = harness;
|
super(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IdBuilder id() {
|
public IdBuilder id() {
|
||||||
return new IdBuilder(harness);
|
return new IdBuilder(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
|
||||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -1,18 +1,15 @@
|
|||||||
package cz.moneta.test.dsl.cagw.api.cbl.psd.aisp.my.accounts.id;
|
package cz.moneta.test.dsl.cagw.api.cbl.psd.aisp.my.accounts.id;
|
||||||
|
|
||||||
import cz.moneta.test.dsl.Harness;
|
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.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.Transactions;
|
||||||
import cz.moneta.test.dsl.cagw.api.cbl.psd.aisp.my.accounts.id.transactions.TransactionsWithParams;
|
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 {
|
public class IdBuilder extends CaGwBuilder {
|
||||||
private final Harness harness;
|
|
||||||
|
|
||||||
public IdBuilder(Harness harness) {
|
public IdBuilder(Harness harness) {
|
||||||
this.harness = harness;
|
super(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Balance prepareBalanceRequest() {
|
public Balance prepareBalanceRequest() {
|
||||||
@ -26,8 +23,5 @@ public class IdBuilder {
|
|||||||
public TransactionsWithParams prepareTransactionsWithParamsRequest() {
|
public TransactionsWithParams prepareTransactionsWithParamsRequest() {
|
||||||
return getBuilder(TransactionsWithParams.class);
|
return getBuilder(TransactionsWithParams.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
|
||||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -1,23 +1,17 @@
|
|||||||
package cz.moneta.test.dsl.cagw.api.cbl.psd.pisp;
|
package cz.moneta.test.dsl.cagw.api.cbl.psd.pisp;
|
||||||
|
|
||||||
import cz.moneta.test.dsl.Harness;
|
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.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 {
|
public class PispBuilder extends CaGwBuilder {
|
||||||
private final Harness harness;
|
|
||||||
|
|
||||||
public PispBuilder(Harness harness) {
|
public PispBuilder(Harness harness) {
|
||||||
this.harness = harness;
|
super(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MyBuilder my() {
|
public MyBuilder my() {
|
||||||
return new MyBuilder(harness);
|
return new MyBuilder(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
|
||||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -1,17 +1,14 @@
|
|||||||
package cz.moneta.test.dsl.cagw.api.cbl.psd.pisp.my;
|
package cz.moneta.test.dsl.cagw.api.cbl.psd.pisp.my;
|
||||||
|
|
||||||
import cz.moneta.test.dsl.Harness;
|
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.Payment;
|
||||||
import cz.moneta.test.dsl.cagw.api.cbl.psd.pisp.my.payments.PaymentsBuilder;
|
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 {
|
public class MyBuilder extends CaGwBuilder {
|
||||||
private final Harness harness;
|
|
||||||
|
|
||||||
public MyBuilder(Harness harness) {
|
public MyBuilder(Harness harness) {
|
||||||
this.harness = harness;
|
super(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PaymentsBuilder payments() {
|
public PaymentsBuilder payments() {
|
||||||
@ -22,7 +19,4 @@ public class MyBuilder {
|
|||||||
return getBuilder(Payment.class);
|
return getBuilder(Payment.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
|
||||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -1,23 +1,17 @@
|
|||||||
package cz.moneta.test.dsl.cagw.api.cbl.psd.pisp.my.payments;
|
package cz.moneta.test.dsl.cagw.api.cbl.psd.pisp.my.payments;
|
||||||
|
|
||||||
import cz.moneta.test.dsl.Harness;
|
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.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 {
|
public class PaymentsBuilder extends CaGwBuilder {
|
||||||
private final Harness harness;
|
|
||||||
|
|
||||||
public PaymentsBuilder(Harness harness) {
|
public PaymentsBuilder(Harness harness) {
|
||||||
this.harness = harness;
|
super(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PaymentIdBuilder paymentId() {
|
public PaymentIdBuilder paymentId() {
|
||||||
return new PaymentIdBuilder(harness);
|
return new PaymentIdBuilder(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
|
||||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -1,23 +1,17 @@
|
|||||||
package cz.moneta.test.dsl.cagw.api.cbl.psd.pisp.my.payments.paymentid;
|
package cz.moneta.test.dsl.cagw.api.cbl.psd.pisp.my.payments.paymentid;
|
||||||
|
|
||||||
import cz.moneta.test.dsl.Harness;
|
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.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 {
|
public class PaymentIdBuilder extends CaGwBuilder {
|
||||||
private final Harness harness;
|
|
||||||
|
|
||||||
public PaymentIdBuilder(Harness harness) {
|
public PaymentIdBuilder(Harness harness) {
|
||||||
this.harness = harness;
|
super(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Status prepareStatusRequest() {
|
public Status prepareStatusRequest() {
|
||||||
return getBuilder(Status.class);
|
return getBuilder(Status.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
|
||||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -1,23 +1,17 @@
|
|||||||
package cz.moneta.test.dsl.cagw.api.v1;
|
package cz.moneta.test.dsl.cagw.api.v1;
|
||||||
|
|
||||||
import cz.moneta.test.dsl.Harness;
|
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.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 {
|
public class V1Builder extends CaGwBuilder {
|
||||||
private final Harness harness;
|
|
||||||
|
|
||||||
public V1Builder(Harness harness) {
|
public V1Builder(Harness harness) {
|
||||||
this.harness = harness;
|
super(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ExternalBuilder external() {
|
public ExternalBuilder external() {
|
||||||
return new ExternalBuilder(harness);
|
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,14 @@
|
|||||||
package cz.moneta.test.dsl.cagw.api.v1.external;
|
package cz.moneta.test.dsl.cagw.api.v1.external;
|
||||||
|
|
||||||
import cz.moneta.test.dsl.Harness;
|
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.product.ProductBuilder;
|
||||||
import cz.moneta.test.dsl.cagw.api.v1.external.service.ServiceBuilder;
|
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 {
|
public class ExternalBuilder extends CaGwBuilder {
|
||||||
private final Harness harness;
|
|
||||||
|
|
||||||
public ExternalBuilder(Harness harness) {
|
public ExternalBuilder(Harness harness) {
|
||||||
this.harness = harness;
|
super(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ProductBuilder product() {
|
public ProductBuilder product() {
|
||||||
@ -22,7 +19,4 @@ public class ExternalBuilder {
|
|||||||
return new ServiceBuilder(harness);
|
return new ServiceBuilder(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
|
||||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,23 +1,17 @@
|
|||||||
package cz.moneta.test.dsl.cagw.api.v1.external.product;
|
package cz.moneta.test.dsl.cagw.api.v1.external.product;
|
||||||
|
|
||||||
import cz.moneta.test.dsl.Harness;
|
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.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) {
|
public ProductBuilder(Harness harness) {
|
||||||
this.harness = harness;
|
super(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CloanBuilder cbl() {
|
public CloanBuilder cbl() {
|
||||||
return new CloanBuilder(harness);
|
return new CloanBuilder(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
|
||||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,23 +1,17 @@
|
|||||||
package cz.moneta.test.dsl.cagw.api.v1.external.product.cloan;
|
package cz.moneta.test.dsl.cagw.api.v1.external.product.cloan;
|
||||||
|
|
||||||
import cz.moneta.test.dsl.Harness;
|
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.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 {
|
public class CloanBuilder extends CaGwBuilder {
|
||||||
private final Harness harness;
|
|
||||||
|
|
||||||
public CloanBuilder(Harness harness) {
|
public CloanBuilder(Harness harness) {
|
||||||
this.harness = harness;
|
super(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ApplicationBuilder application() {
|
public ApplicationBuilder application() {
|
||||||
return new ApplicationBuilder(harness);
|
return new ApplicationBuilder(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
|
||||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,22 +1,16 @@
|
|||||||
package cz.moneta.test.dsl.cagw.api.v1.external.product.cloan.application;
|
package cz.moneta.test.dsl.cagw.api.v1.external.product.cloan.application;
|
||||||
|
|
||||||
import cz.moneta.test.dsl.Harness;
|
import cz.moneta.test.dsl.Harness;
|
||||||
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
|
import cz.moneta.test.dsl.cagw.CaGwBuilder;
|
||||||
import cz.moneta.test.harness.support.rest.Builders;
|
|
||||||
import cz.moneta.test.harness.support.rest.RestRequest;
|
|
||||||
|
|
||||||
public class ApplicationBuilder {
|
public class ApplicationBuilder extends CaGwBuilder {
|
||||||
private final Harness harness;
|
|
||||||
|
|
||||||
public ApplicationBuilder(Harness harness) {
|
public ApplicationBuilder(Harness harness) {
|
||||||
this.harness = harness;
|
super(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Application prepareApplicationRequest() {
|
public Application prepareApplicationRequest() {
|
||||||
return getBuilder(Application.class);
|
return getBuilder(Application.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
|
||||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,23 +1,17 @@
|
|||||||
package cz.moneta.test.dsl.cagw.api.v1.external.service;
|
package cz.moneta.test.dsl.cagw.api.v1.external.service;
|
||||||
|
|
||||||
import cz.moneta.test.dsl.Harness;
|
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.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 {
|
public class ServiceBuilder extends CaGwBuilder {
|
||||||
private final Harness harness;
|
|
||||||
|
|
||||||
public ServiceBuilder(Harness harness) {
|
public ServiceBuilder(Harness harness) {
|
||||||
this.harness = harness;
|
super(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
public VoicebotBuilder voicebot() {
|
public VoicebotBuilder voicebot() {
|
||||||
return new VoicebotBuilder(harness);
|
return new VoicebotBuilder(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
|
||||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,23 +1,17 @@
|
|||||||
package cz.moneta.test.dsl.cagw.api.v1.external.service.voicebot;
|
package cz.moneta.test.dsl.cagw.api.v1.external.service.voicebot;
|
||||||
|
|
||||||
import cz.moneta.test.dsl.Harness;
|
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.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 {
|
public class VoicebotBuilder extends CaGwBuilder {
|
||||||
private final Harness harness;
|
|
||||||
|
|
||||||
public VoicebotBuilder(Harness harness) {
|
public VoicebotBuilder(Harness harness) {
|
||||||
this.harness = harness;
|
super(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
public VoicePasswordDetail prepareVoicePasswordDetailApi() {
|
public VoicePasswordDetail prepareVoicePasswordDetail() {
|
||||||
return getBuilder(VoicePasswordDetail.class);
|
return getBuilder(VoicePasswordDetail.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
|
||||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,23 +1,17 @@
|
|||||||
package cz.moneta.test.dsl.cagw.api.v2;
|
package cz.moneta.test.dsl.cagw.api.v2;
|
||||||
|
|
||||||
import cz.moneta.test.dsl.Harness;
|
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.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 {
|
public class V2Builder extends CaGwBuilder {
|
||||||
private final Harness harness;
|
|
||||||
|
|
||||||
public V2Builder(Harness harness) {
|
public V2Builder(Harness harness) {
|
||||||
this.harness = harness;
|
super(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ExternalBuilder external() {
|
public ExternalBuilder external() {
|
||||||
return new ExternalBuilder(harness);
|
return new ExternalBuilder(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
|
||||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -1,23 +1,17 @@
|
|||||||
package cz.moneta.test.dsl.cagw.api.v2.external;
|
package cz.moneta.test.dsl.cagw.api.v2.external;
|
||||||
|
|
||||||
import cz.moneta.test.dsl.Harness;
|
import cz.moneta.test.dsl.Harness;
|
||||||
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
|
import cz.moneta.test.dsl.cagw.CaGwBuilder;
|
||||||
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;
|
import cz.moneta.test.dsl.cagw.api.v2.external.client.ClientBuilder;
|
||||||
|
|
||||||
public class ExternalBuilder {
|
public class ExternalBuilder extends CaGwBuilder {
|
||||||
private final Harness harness;
|
|
||||||
|
|
||||||
public ExternalBuilder(Harness harness) {
|
public ExternalBuilder(Harness harness) {
|
||||||
this.harness = harness;
|
super(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ClientBuilder client() {
|
public ClientBuilder client() {
|
||||||
return new ClientBuilder(harness);
|
return new ClientBuilder(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
|
||||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -1,23 +1,17 @@
|
|||||||
package cz.moneta.test.dsl.cagw.api.v2.external.client;
|
package cz.moneta.test.dsl.cagw.api.v2.external.client;
|
||||||
|
|
||||||
import cz.moneta.test.dsl.Harness;
|
import cz.moneta.test.dsl.Harness;
|
||||||
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
|
import cz.moneta.test.dsl.cagw.CaGwBuilder;
|
||||||
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;
|
import cz.moneta.test.dsl.cagw.api.v2.external.client.kyc.KycBuilder;
|
||||||
|
|
||||||
public class ClientBuilder {
|
public class ClientBuilder extends CaGwBuilder {
|
||||||
private final Harness harness;
|
|
||||||
|
|
||||||
public ClientBuilder(Harness harness) {
|
public ClientBuilder(Harness harness) {
|
||||||
this.harness = harness;
|
super(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
public KycBuilder kyc() {
|
public KycBuilder kyc() {
|
||||||
return new KycBuilder(harness);
|
return new KycBuilder(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
|
||||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -1,23 +1,17 @@
|
|||||||
package cz.moneta.test.dsl.cagw.api.v2.external.client.kyc;
|
package cz.moneta.test.dsl.cagw.api.v2.external.client.kyc;
|
||||||
|
|
||||||
import cz.moneta.test.dsl.Harness;
|
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.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 {
|
public class KycBuilder extends CaGwBuilder {
|
||||||
private final Harness harness;
|
|
||||||
|
|
||||||
public KycBuilder(Harness harness) {
|
public KycBuilder(Harness harness) {
|
||||||
this.harness = harness;
|
super(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
public BusinessCardBuilder bussinesCard() {
|
public BusinessCardBuilder bussinesCard() {
|
||||||
return new BusinessCardBuilder(harness);
|
return new BusinessCardBuilder(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
|
||||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -1,22 +1,16 @@
|
|||||||
package cz.moneta.test.dsl.cagw.api.v2.external.client.kyc.businesscard;
|
package cz.moneta.test.dsl.cagw.api.v2.external.client.kyc.businesscard;
|
||||||
|
|
||||||
import cz.moneta.test.dsl.Harness;
|
import cz.moneta.test.dsl.Harness;
|
||||||
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
|
import cz.moneta.test.dsl.cagw.CaGwBuilder;
|
||||||
import cz.moneta.test.harness.support.rest.Builders;
|
|
||||||
import cz.moneta.test.harness.support.rest.RestRequest;
|
|
||||||
|
|
||||||
public class BusinessCardBuilder {
|
public class BusinessCardBuilder extends CaGwBuilder {
|
||||||
private final Harness harness;
|
|
||||||
|
|
||||||
public BusinessCardBuilder(Harness harness) {
|
public BusinessCardBuilder(Harness harness) {
|
||||||
this.harness = harness;
|
super(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
public BusinessCard prepareBusinessCardRequest() {
|
public BusinessCard prepareBusinessCardRequest() {
|
||||||
return getBuilder(BusinessCard.class);
|
return getBuilder(BusinessCard.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
|
||||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -1,23 +1,17 @@
|
|||||||
package cz.moneta.test.dsl.cagw.api.v4;
|
package cz.moneta.test.dsl.cagw.api.v4;
|
||||||
|
|
||||||
import cz.moneta.test.dsl.Harness;
|
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.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 {
|
public class V4Builder extends CaGwBuilder {
|
||||||
private final Harness harness;
|
|
||||||
|
|
||||||
public V4Builder(Harness harness) {
|
public V4Builder(Harness harness) {
|
||||||
this.harness = harness;
|
super(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TokenBuilder token() {
|
public TokenBuilder token() {
|
||||||
return new TokenBuilder(harness);
|
return new TokenBuilder(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
|
||||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -1,23 +1,16 @@
|
|||||||
package cz.moneta.test.dsl.cagw.api.v4.token;
|
package cz.moneta.test.dsl.cagw.api.v4.token;
|
||||||
|
|
||||||
import cz.moneta.test.dsl.Harness;
|
import cz.moneta.test.dsl.Harness;
|
||||||
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
|
import cz.moneta.test.dsl.cagw.CaGwBuilder;
|
||||||
import cz.moneta.test.harness.support.rest.Builders;
|
|
||||||
import cz.moneta.test.harness.support.rest.RestRequest;
|
|
||||||
|
|
||||||
public class TokenBuilder {
|
public class TokenBuilder extends CaGwBuilder {
|
||||||
|
|
||||||
private final Harness harness;
|
|
||||||
|
|
||||||
public TokenBuilder(Harness harness) {
|
public TokenBuilder(Harness harness) {
|
||||||
this.harness = harness;
|
super(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ApiV4Token prepareTokenRequest() {
|
public ApiV4Token prepareTokenRequest() {
|
||||||
return getBuilder(ApiV4Token.class);
|
return getBuilder(ApiV4Token.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
|
||||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -1,23 +1,17 @@
|
|||||||
package cz.moneta.test.dsl.cagw.auth;
|
package cz.moneta.test.dsl.cagw.auth;
|
||||||
|
|
||||||
import cz.moneta.test.dsl.Harness;
|
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.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 {
|
public class AuthBuilder extends CaGwBuilder {
|
||||||
private final Harness harness;
|
|
||||||
|
|
||||||
public AuthBuilder(Harness harness) {
|
public AuthBuilder(Harness harness) {
|
||||||
this.harness = harness;
|
super(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
public OauthBuilder oauth() {
|
public OauthBuilder oauth() {
|
||||||
return new OauthBuilder(harness);
|
return new OauthBuilder(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
|
||||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -1,23 +1,17 @@
|
|||||||
package cz.moneta.test.dsl.cagw.auth.oauth;
|
package cz.moneta.test.dsl.cagw.auth.oauth;
|
||||||
|
|
||||||
import cz.moneta.test.dsl.Harness;
|
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.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 {
|
public class OauthBuilder extends CaGwBuilder {
|
||||||
private final Harness harness;
|
|
||||||
|
|
||||||
public OauthBuilder(Harness harness) {
|
public OauthBuilder(Harness harness) {
|
||||||
this.harness = harness;
|
super(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
public V2Builder v2() {
|
public V2Builder v2() {
|
||||||
return new V2Builder(harness);
|
return new V2Builder(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
|
||||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -1,23 +1,17 @@
|
|||||||
package cz.moneta.test.dsl.cagw.auth.oauth.v2;
|
package cz.moneta.test.dsl.cagw.auth.oauth.v2;
|
||||||
|
|
||||||
import cz.moneta.test.dsl.Harness;
|
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.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 {
|
public class V2Builder extends CaGwBuilder {
|
||||||
private final Harness harness;
|
|
||||||
|
|
||||||
public V2Builder(Harness harness) {
|
public V2Builder(Harness harness) {
|
||||||
this.harness = harness;
|
super(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TokenBuilder token() {
|
public TokenBuilder token() {
|
||||||
return new TokenBuilder(harness);
|
return new TokenBuilder(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
|
||||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -1,24 +1,17 @@
|
|||||||
package cz.moneta.test.dsl.cagw.auth.oauth.v2.token;
|
package cz.moneta.test.dsl.cagw.auth.oauth.v2.token;
|
||||||
|
|
||||||
import cz.moneta.test.dsl.Harness;
|
import cz.moneta.test.dsl.Harness;
|
||||||
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
|
import cz.moneta.test.dsl.cagw.CaGwBuilder;
|
||||||
import cz.moneta.test.harness.support.rest.Builders;
|
|
||||||
import cz.moneta.test.harness.support.rest.RestRequest;
|
|
||||||
|
|
||||||
|
|
||||||
public class TokenBuilder {
|
public class TokenBuilder extends CaGwBuilder {
|
||||||
|
|
||||||
private final Harness harness;
|
|
||||||
|
|
||||||
public TokenBuilder(Harness harness) {
|
public TokenBuilder(Harness harness) {
|
||||||
this.harness = harness;
|
super(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
public OauthToken prepareOauth2V2TokenRequest() {
|
public OauthToken prepareOauth2V2TokenRequest() {
|
||||||
return getBuilder(OauthToken.class);
|
return getBuilder(OauthToken.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
|
||||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -1,23 +1,17 @@
|
|||||||
package cz.moneta.test.dsl.cagw.oauth2;
|
package cz.moneta.test.dsl.cagw.oauth2;
|
||||||
|
|
||||||
import cz.moneta.test.dsl.Harness;
|
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.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 {
|
public class Oauth2Builder extends CaGwBuilder {
|
||||||
private final Harness harness;
|
|
||||||
|
|
||||||
public Oauth2Builder(Harness harness) {
|
public Oauth2Builder(Harness harness) {
|
||||||
this.harness = harness;
|
super(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TokenBuilder token() {
|
public TokenBuilder token() {
|
||||||
return new TokenBuilder(harness);
|
return new TokenBuilder(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
|
||||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -1,24 +1,18 @@
|
|||||||
package cz.moneta.test.dsl.cagw.oauth2.token;
|
package cz.moneta.test.dsl.cagw.oauth2.token;
|
||||||
|
|
||||||
import cz.moneta.test.dsl.Harness;
|
import cz.moneta.test.dsl.Harness;
|
||||||
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
|
import cz.moneta.test.dsl.cagw.CaGwBuilder;
|
||||||
import cz.moneta.test.harness.support.rest.Builders;
|
|
||||||
import cz.moneta.test.harness.support.rest.RestRequest;
|
|
||||||
|
|
||||||
|
|
||||||
public class TokenBuilder {
|
public class TokenBuilder extends CaGwBuilder {
|
||||||
|
|
||||||
private final Harness harness;
|
|
||||||
|
|
||||||
public TokenBuilder(Harness harness) {
|
public TokenBuilder(Harness harness) {
|
||||||
this.harness = harness;
|
super(harness);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public Oauth2Token prepareOauth2TokenRequest() {
|
public Oauth2Token prepareOauth2TokenRequest() {
|
||||||
return getBuilder(Oauth2Token.class);
|
return getBuilder(Oauth2Token.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
|
|
||||||
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
package cz.moneta.test.dsl.exevido.components;
|
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.IncomingMessagesPage;
|
||||||
import cz.moneta.test.dsl.exevido.pages.OutgoingMessagesPage;
|
import cz.moneta.test.dsl.exevido.pages.OutgoingMessagesPage;
|
||||||
import cz.moneta.test.harness.support.web.Click;
|
import cz.moneta.test.harness.support.web.Click;
|
||||||
@ -9,6 +10,7 @@ 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.ExevidoPanels.LOADER_DIV;
|
||||||
import static cz.moneta.test.dsl.exevido.components.LeftMenu.PAGE_SA_SMART_MENU;
|
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(PAGE_SA_SMART_MENU)
|
||||||
@Wait(value = LOADER_DIV, until = Until.GONE)
|
@Wait(value = LOADER_DIV, until = Until.GONE)
|
||||||
@ -22,6 +24,7 @@ public interface LeftMenu<T> {
|
|||||||
String CATEGORY_INHERITANCE_MMB_XPATH = "//li[@id='Dědictví - MMB']";
|
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_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_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 FOR_PROCESSING_D_REQUEST_MMB_XPATH = CATEGORY_INHERITANCE_MMB_XPATH + "//a[contains(., 'D - Žádost [M] - MMB')]";
|
||||||
String REFRESH_COUNTERS_BUTTON = "refresh_counters";
|
String REFRESH_COUNTERS_BUTTON = "refresh_counters";
|
||||||
|
|
||||||
@ -37,7 +40,7 @@ public interface LeftMenu<T> {
|
|||||||
@Click(SUBCATEGORY_ALL_OUTGOING_MESSAGES_A)
|
@Click(SUBCATEGORY_ALL_OUTGOING_MESSAGES_A)
|
||||||
OutgoingMessagesPage clickAllOutgoingMessages();
|
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))
|
@Click(value = SUBCATEGORY_WITHOUT_FOLDER_XPATH, by = Lookup.XPATH, andWait = @Wait(value = LOADER_DIV, until = Until.GONE))
|
||||||
IncomingMessagesPage clickInheritanceMmbWithoutFolder();
|
IncomingMessagesPage clickInheritanceMmbWithoutFolder();
|
||||||
|
|
||||||
@ -45,6 +48,10 @@ 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))
|
@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();
|
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))
|
@Click(value = FOR_PROCESSING_D_REQUEST_MMB_XPATH, by = Lookup.XPATH, andWait = @Wait(value = LOADER_DIV, until = Until.GONE))
|
||||||
IncomingMessagesPage clickRequestMmb();
|
IncomingMessagesPage clickRequestMmb();
|
||||||
|
|
||||||
|
|||||||
@ -36,6 +36,11 @@ public interface DetailIncomingMessagePage extends ExevidoWebFlow<DetailIncoming
|
|||||||
String DOWNLOAD_ZFO = "//button[normalize-space()='Stáhnout ZFO']";
|
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 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 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";
|
String MESSAGE_ID_SPAN = "message_id";
|
||||||
|
|
||||||
@Click(ASSIGN_BUTTON)
|
@Click(ASSIGN_BUTTON)
|
||||||
@ -119,6 +124,25 @@ public interface DetailIncomingMessagePage extends ExevidoWebFlow<DetailIncoming
|
|||||||
@CheckElementPresent(value = INTERNAL_REFERENCE_NUMBER, by = Lookup.XPATH, isStringDynamicXpath = true)
|
@CheckElementPresent(value = INTERNAL_REFERENCE_NUMBER, by = Lookup.XPATH, isStringDynamicXpath = true)
|
||||||
DetailIncomingMessagePage checkInternalReferenceNumber(String referenceNumber);
|
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)
|
@CheckElementContent(value = MESSAGE_ID_SPAN)
|
||||||
NewIncomingMessagePage checkMessageId(String id);
|
NewIncomingMessagePage checkMessageId(String id);
|
||||||
}
|
}
|
||||||
@ -49,6 +49,15 @@ public interface IncomingMessagesPage extends ExevidoWebFlow<IncomingMessagesPag
|
|||||||
return null;
|
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)
|
@TypeInto(value = DATE_RANGE_ACCEPTANCE_TIME, clear = true)
|
||||||
@KeyPress(value = Key.ENTER, andWait = @Wait(value = ExevidoPanels.LOADER_DIV, until = Until.GONE))
|
@KeyPress(value = Key.ENTER, andWait = @Wait(value = ExevidoPanels.LOADER_DIV, until = Until.GONE))
|
||||||
IncomingMessagesPage searchByDateRange(String date);
|
IncomingMessagesPage searchByDateRange(String date);
|
||||||
|
|||||||
@ -42,8 +42,8 @@ public interface NewOutgoingMessagePage extends ExevidoWebFlow<NewOutgoingMessag
|
|||||||
String MESSAGE_IS_SENDING_DIV = "message_identification";
|
String MESSAGE_IS_SENDING_DIV = "message_identification";
|
||||||
String MESSAGE_SUCCESSFULLY_SEND_LOG_XPATH = "//span[contains(text(), 'byla odeslána')]";
|
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 MESSAGE_SEND_FAILURE_LOG_XPATH = "//span[contains(text(), 'selhání odeslání zprávy')]";
|
||||||
String ATTACHMENT_PATH = "regression/exevido/testExevido.txt";
|
String ATTACHMENT_PATH = "regression/exevido/web/testExevido.txt";
|
||||||
String SECOND_ATTACHMENT_PATH = "regression/exevido/testExevido2.txt";
|
String SECOND_ATTACHMENT_PATH = "regression/exevido/web/testExevido2.txt";
|
||||||
String MESSAGE_ID_SPAN = "message_id";
|
String MESSAGE_ID_SPAN = "message_id";
|
||||||
String MULTIPLE_RECIPIENTS_SPAN = "//label[@for='multiple_recipients']";
|
String MULTIPLE_RECIPIENTS_SPAN = "//label[@for='multiple_recipients']";
|
||||||
String ADD_RECIPIENT_TO_LIST_BUTTON = "add_to_list_button";
|
String ADD_RECIPIENT_TO_LIST_BUTTON = "add_to_list_button";
|
||||||
|
|||||||
@ -0,0 +1,25 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
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)
|
.realtyPriceCurrent(3500000)
|
||||||
.realtyType(RealtyType.FLAT.getValue())
|
.realtyType(RealtyType.FLAT.getValue())
|
||||||
.pledgeType(PledgeType.PROPERTY_HOUSING.getValue())
|
.pledgeType(PledgeType.PROPERTY_HOUSING.getValue())
|
||||||
.contractRelation(ContranctRelation.PLEDGE_WITH_CURRENT_REALTY.getValue())
|
.contractRelation(ContractRelation.PLEDGE_WITH_CURRENT_REALTY.getValue())
|
||||||
.collateralType(CollateralType.FLAT.getValue())
|
.collateralType(CollateralType.FLAT.getValue())
|
||||||
.appraiserCompany(AppraiserCompany.MONETA_SUPERVISION.getValue())
|
.appraiserCompany(AppraiserCompany.MONETA_SUPERVISION.getValue())
|
||||||
.build();
|
.build();
|
||||||
|
|||||||
@ -13,4 +13,4 @@ public enum AppraiserCompany {
|
|||||||
public String getValue() {
|
public String getValue() {
|
||||||
return appraiserCompany;
|
return appraiserCompany;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,6 +1,8 @@
|
|||||||
package cz.moneta.test.dsl.hypos.enums;
|
package cz.moneta.test.dsl.hypos.enums;
|
||||||
|
|
||||||
public enum CadasterRequestType {
|
import cz.moneta.test.harness.support.web.SelectByValue;
|
||||||
|
|
||||||
|
public enum CadasterRequestType implements SelectByValue {
|
||||||
PART_OWNER_LIST("Část. výpis LV"),
|
PART_OWNER_LIST("Část. výpis LV"),
|
||||||
CADASTER_MAP("Kat. mapa"),
|
CADASTER_MAP("Kat. mapa"),
|
||||||
OWNER_LIST("List vlastnictví"),
|
OWNER_LIST("List vlastnictví"),
|
||||||
@ -12,6 +14,7 @@ public enum CadasterRequestType {
|
|||||||
this.cadasterRequestType = cadasterRequestType;
|
this.cadasterRequestType = cadasterRequestType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public String getValue() {
|
public String getValue() {
|
||||||
return cadasterRequestType;
|
return cadasterRequestType;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,4 +12,4 @@ public enum CollateralCode {
|
|||||||
public String getValue() {
|
public String getValue() {
|
||||||
return collateralCode;
|
return collateralCode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,6 +1,8 @@
|
|||||||
package cz.moneta.test.dsl.hypos.enums;
|
package cz.moneta.test.dsl.hypos.enums;
|
||||||
|
|
||||||
public enum CollectableFee {
|
import cz.moneta.test.harness.support.web.SelectByValue;
|
||||||
|
|
||||||
|
public enum CollectableFee implements SelectByValue {
|
||||||
APPENDIX_CLIENT_INITIATIVE("DODATEK - Z PODNĚTU KLIENTA"),
|
APPENDIX_CLIENT_INITIATIVE("DODATEK - Z PODNĚTU KLIENTA"),
|
||||||
FEE_DOWNLOAD_LV_KM("Poplatek za stažení LV/KM"),
|
FEE_DOWNLOAD_LV_KM("Poplatek za stažení LV/KM"),
|
||||||
OTHER_BANK_INFORMATION("OSTATNÍ - BANKOVNÍ INFORMACE");
|
OTHER_BANK_INFORMATION("OSTATNÍ - BANKOVNÍ INFORMACE");
|
||||||
@ -11,6 +13,7 @@ public enum CollectableFee {
|
|||||||
this.collectableFee = collectableFee;
|
this.collectableFee = collectableFee;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public String getValue() {
|
public String getValue() {
|
||||||
return collectableFee;
|
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