Compare commits

..

No commits in common. "main" and "draft" have entirely different histories.
main ... draft

277 changed files with 2584 additions and 7832 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cz.moneta.demo</groupId>
<artifactId>messaging-connection-demo</artifactId>
<version>1.0.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<kafka.version>3.7.1</kafka.version>
<confluent.version>7.6.1</confluent.version>
<ibm.mq.version>9.4.2.0</ibm.mq.version>
<jakarta.jms.version>3.1.0</jakarta.jms.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>${kafka.version}</version>
</dependency>
<dependency>
<groupId>io.confluent</groupId>
<artifactId>kafka-avro-serializer</artifactId>
<version>${confluent.version}</version>
</dependency>
<dependency>
<groupId>com.ibm.mq</groupId>
<artifactId>com.ibm.mq.allclient</artifactId>
<version>${ibm.mq.version}</version>
</dependency>
<dependency>
<groupId>jakarta.jms</groupId>
<artifactId>jakarta.jms-api</artifactId>
<version>${jakarta.jms.version}</version>
</dependency>
</dependencies>
<repositories>
<repository>
<id>confluent</id>
<name>Confluent Maven Repository</name>
<url>https://packages.confluent.io/maven/</url>
</repository>
</repositories>
</project>

View File

@ -0,0 +1,69 @@
package cz.moneta.demo;
import com.ibm.msg.client.jms.JmsConnectionFactory;
import com.ibm.msg.client.jms.JmsFactoryFactory;
import com.ibm.msg.client.wmq.WMQConstants;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.serialization.StringSerializer;
import javax.jms.JMSContext;
import java.util.Properties;
public class MessagingConnectionApp {
public static KafkaProducer<String, String> createKafkaConnection(String bootstrapServers,
String apiKey,
String apiSecret) {
Properties kafkaProps = new Properties();
kafkaProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
kafkaProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
kafkaProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
kafkaProps.put("security.protocol", "SASL_SSL");
kafkaProps.put("sasl.mechanism", "PLAIN");
kafkaProps.put("sasl.jaas.config",
String.format("org.apache.kafka.common.security.plain.PlainLoginModule required username=\"%s\" password=\"%s\";",
apiKey, apiSecret));
return new KafkaProducer<>(kafkaProps);
}
public static JMSContext createMqConnection(String host,
int port,
String channel,
String queueManager,
String user,
String password) throws Exception {
JmsFactoryFactory factoryFactory = JmsFactoryFactory.getInstance(WMQConstants.WMQ_PROVIDER);
JmsConnectionFactory connectionFactory = factoryFactory.createConnectionFactory();
connectionFactory.setStringProperty(WMQConstants.WMQ_HOST_NAME, host);
connectionFactory.setIntProperty(WMQConstants.WMQ_PORT, port);
connectionFactory.setStringProperty(WMQConstants.WMQ_CHANNEL, channel);
connectionFactory.setStringProperty(WMQConstants.WMQ_QUEUE_MANAGER, queueManager);
connectionFactory.setIntProperty(WMQConstants.WMQ_CONNECTION_MODE, WMQConstants.WMQ_CM_CLIENT);
connectionFactory.setStringProperty(WMQConstants.USERID, user);
connectionFactory.setStringProperty(WMQConstants.PASSWORD, password);
return connectionFactory.createContext(user, password, JMSContext.AUTO_ACKNOWLEDGE);
}
public static void main(String[] args) throws Exception {
String kafkaBootstrap = System.getProperty("kafka.bootstrap", "localhost:9092");
String kafkaApiKey = System.getProperty("kafka.apiKey", "api-key");
String kafkaApiSecret = System.getProperty("kafka.apiSecret", "api-secret");
String mqHost = System.getProperty("mq.host", "localhost");
int mqPort = Integer.parseInt(System.getProperty("mq.port", "1414"));
String mqChannel = System.getProperty("mq.channel", "DEV.APP.SVRCONN");
String mqQueueManager = System.getProperty("mq.queueManager", "QM1");
String mqUser = System.getProperty("mq.user", "app");
String mqPassword = System.getProperty("mq.password", "pass");
try (KafkaProducer<String, String> kafkaProducer = createKafkaConnection(kafkaBootstrap, kafkaApiKey, kafkaApiSecret);
JMSContext mqContext = createMqConnection(mqHost, mqPort, mqChannel, mqQueueManager, mqUser, mqPassword)) {
System.out.println("Kafka connection created: " + (kafkaProducer != null));
System.out.println("IBM MQ connection created: " + (mqContext != null));
}
}
}

View File

@ -29,11 +29,10 @@
<commons-beanutils.version>1.9.3</commons-beanutils.version>
<commons-configuration.version>1.6</commons-configuration.version>
<cxf.version>4.0.3</cxf.version>
<kafka.version>3.7.1</kafka.version>
<confluent.version>7.6.1</confluent.version>
<ibm.mq.version>9.4.5.0</ibm.mq.version>
<javax.jms.version>2.0.1</javax.jms.version>
<kafka.clients.version>3.7.0</kafka.clients.version>
<confluent.version>7.6.0</confluent.version>
<assertj.version>3.24.2</assertj.version>
<jakarta.jms.version>3.1.0</jakarta.jms.version>
</properties>
<dependencies>
@ -271,6 +270,48 @@
<version>${appium-java-client.version}</version>
</dependency>
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>${kafka.version}</version>
</dependency>
<dependency>
<groupId>io.confluent</groupId>
<artifactId>kafka-avro-serializer</artifactId>
<version>${confluent.version}</version>
</dependency>
<dependency>
<groupId>io.confluent</groupId>
<artifactId>kafka-schema-registry-client</artifactId>
<version>${confluent.version}</version>
</dependency>
<dependency>
<groupId>org.apache.avro</groupId>
<artifactId>avro</artifactId>
<version>1.11.3</version>
</dependency>
<dependency>
<groupId>com.ibm.mq</groupId>
<artifactId>com.ibm.mq.allclient</artifactId>
<version>${ibm.mq.version}</version>
</dependency>
<dependency>
<groupId>javax.jms</groupId>
<artifactId>javax.jms-api</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>jakarta.jms</groupId>
<artifactId>jakarta.jms-api</artifactId>
<version>${jakarta.jms.version}</version>
</dependency>
<!-- Used for Web Services connector -->
<dependency>
<groupId>org.apache.cxf</groupId>
@ -290,44 +331,6 @@
<version>${cxf.version}</version>
</dependency>
<!-- Used in IbmMqConnector -->
<dependency>
<groupId>com.ibm.mq</groupId>
<artifactId>com.ibm.mq.allclient</artifactId>
<version>${ibm.mq.version}</version>
</dependency>
<dependency>
<groupId>javax.jms</groupId>
<artifactId>javax.jms-api</artifactId>
<version>${javax.jms.version}</version>
</dependency>
<!-- Kafka dependencies -->
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>${kafka.clients.version}</version>
</dependency>
<dependency>
<groupId>io.confluent</groupId>
<artifactId>kafka-avro-serializer</artifactId>
<version>${confluent.version}</version>
</dependency>
<dependency>
<groupId>org.apache.avro</groupId>
<artifactId>avro</artifactId>
<version>1.11.3</version>
</dependency>
<!-- AssertJ for advanced assertions -->
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>${assertj.version}</version>
</dependency>
<!-- Used in Wso2ConnectorServlet -->
<dependency>
<groupId>javax.servlet</groupId>
@ -401,6 +404,23 @@
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.9.0</version>
<executions>
<execution>
<phase>install</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/lib</outputDirectory>
<includeScope>runtime</includeScope>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>

View File

@ -1,502 +1,264 @@
package cz.moneta.test.harness.connectors.messaging;
import java.io.FileInputStream;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import javax.jms.BytesMessage;
import javax.jms.JMSConsumer;
import javax.jms.JMSContext;
import javax.jms.JMSException;
import javax.jms.JMSRuntimeException;
import javax.jms.JMSProducer;
import javax.jms.Message;
import javax.jms.Queue;
import javax.jms.QueueBrowser;
import javax.jms.TextMessage;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.ibm.mq.jms.MQConnectionFactory;
import com.ibm.msg.client.wmq.WMQConstants;
import cz.moneta.test.harness.connectors.Connector;
import cz.moneta.test.harness.messaging.MessageContentType;
import cz.moneta.test.harness.messaging.MqMessageFormat;
import cz.moneta.test.harness.messaging.ReceivedMessage;
import cz.moneta.test.harness.messaging.exception.MessagingConnectionException;
import cz.moneta.test.harness.messaging.exception.MessagingDestinationException;
import cz.moneta.test.harness.messaging.exception.MessagingTimeoutException;
import cz.moneta.test.harness.support.messaging.ImqRequest;
import cz.moneta.test.harness.exception.MessagingTimeoutException;
import cz.moneta.test.harness.messaging.model.MessageContentType;
import cz.moneta.test.harness.messaging.model.MqMessageFormat;
import cz.moneta.test.harness.messaging.model.ReceivedMessage;
/**
* IBM MQ connector using JMS client with Jakarta JMS API. Supports
* multi-instance Queue Manager, SSL/TLS, and multiple message formats.
* <p>
* Supported formats: - JSON: JMS TextMessage with plain JSON string (default) -
* XML: JMS TextMessage with XML string - UTF-8 (CCSID 1208): JMS BytesMessage
* with UTF-8 encoding - EBCDIC (CCSID 870): JMS BytesMessage with EBCDIC
* IBM-870 encoding
*/
public class IbmMqConnector implements Connector {
private static final Logger LOG = LogManager.getLogger(IbmMqConnector.class);
private static final Charset EBCDIC_870 = Charset.forName("IBM870");
private static final Charset UTF_8 = StandardCharsets.UTF_8;
private final MQConnectionFactory connectionFactory;
private final String user;
private final String password;
private final Object contextLock = new Object();
private volatile JMSContext jmsContext;
private static final Charset EBCDIC_870 = Charset.forName("IBM870");
private static final Charset UTF_8 = StandardCharsets.UTF_8;
public IbmMqConnector(String host,
int port,
String channel,
String queueManager,
String user,
String password,
String keystorePath,
String keystorePassword,
String cipherSuite) {
this.user = user;
this.password = password;
try {
if (keystorePath != null && !keystorePath.isBlank()) {
System.setProperty("javax.net.ssl.keyStore", keystorePath);
System.setProperty("javax.net.ssl.trustStore", keystorePath);
if (keystorePassword != null) {
System.setProperty("javax.net.ssl.keyStorePassword", keystorePassword);
System.setProperty("javax.net.ssl.trustStorePassword", keystorePassword);
}
}
private static final long DEFAULT_POLL_INTERVAL_MS = 100;
private static final long DEFAULT_MAX_POLL_INTERVAL_MS = 1000;
connectionFactory = new MQConnectionFactory();
connectionFactory.setHostName(host);
connectionFactory.setPort(port);
connectionFactory.setQueueManager(queueManager);
connectionFactory.setChannel(channel);
connectionFactory.setTransportType(WMQConstants.WMQ_CM_CLIENT);
if (user != null && !user.isBlank()) {
connectionFactory.setStringProperty(WMQConstants.USERID, user);
}
if (password != null && !password.isBlank()) {
connectionFactory.setStringProperty(WMQConstants.PASSWORD, password);
}
private static final String TLS_VERSION = "TLSv1.2";
if (cipherSuite != null && !cipherSuite.isBlank()) {
connectionFactory.setSSLCipherSuite(cipherSuite);
}
} catch (Exception e) {
throw new IllegalStateException("Failed to initialize IBM MQ connection factory", e);
}
}
private final MQConnectionFactory connectionFactory;
private JMSContext jmsContext;
private final String queueManager;
private final String user;
private final String password;
public void send(String queueName,
String payload,
MqMessageFormat format,
Map<String, String> properties) {
switch (Objects.requireNonNull(format, "format")) {
case JSON, XML -> sendTextMessage(queueName, payload, properties);
case EBCDIC_870 -> sendBytesMessage(queueName, payload, EBCDIC_870, 870, properties);
case UTF8_1208 -> sendBytesMessage(queueName, payload, UTF_8, 1208, properties);
}
}
/**
* Constructor with multi-instance Queue Manager support.
*
* @param connectionNameList Connection name list in format
* "host1(port1),host2(port2)"
* @param channel MQ channel name
* @param queueManager Queue Manager name
* @param user Username for authentication
* @param password Password for authentication
* @param keystorePath Path to SSL keystore (can be null for non-SSL)
* @param keystorePassword Password for SSL keystore
* @param sslCipherSuite SSL cipher suite to use (e.g.,
* "TLS_RSA_WITH_AES_256_CBC_SHA256")
*/
public IbmMqConnector(String connectionNameList, String channel, String queueManager, String user, String password,
String keystorePath, String keystorePassword, String sslCipherSuite) {
this.queueManager = queueManager;
this.user = user;
this.password = password;
public ReceivedMessage receive(String queueName,
String messageSelector,
MqMessageFormat expectedFormat,
Duration timeout) {
JMSContext context = getContext();
Queue queue = context.createQueue("queue:///" + queueName);
try (JMSConsumer consumer = messageSelector == null || messageSelector.isBlank()
? context.createConsumer(queue)
: context.createConsumer(queue, messageSelector)) {
Message message = consumer.receive(Optional.ofNullable(timeout).orElse(Duration.ofSeconds(30)).toMillis());
if (message == null) {
throw new MessagingTimeoutException("Timeout waiting for IBM MQ message from queue: " + queueName);
}
return toReceivedMessage(message, queueName, expectedFormat);
}
}
try {
connectionFactory = new MQConnectionFactory();
connectionFactory.setConnectionNameList(connectionNameList);
connectionFactory.setQueueManager(queueManager);
connectionFactory.setChannel(channel);
connectionFactory.setTransportType(WMQConstants.WMQ_CM_CLIENT);
if (user != null && !user.isBlank()) {
connectionFactory.setStringProperty(WMQConstants.USERID, user);
}
if (password != null && !password.isBlank()) {
connectionFactory.setStringProperty(WMQConstants.PASSWORD, password);
}
public List<ReceivedMessage> browse(String queueName,
Predicate<ReceivedMessage> filter,
MqMessageFormat expectedFormat,
Duration timeout) {
long timeoutMillis = Optional.ofNullable(timeout).orElse(Duration.ofSeconds(30)).toMillis();
long deadline = System.currentTimeMillis() + timeoutMillis;
long backoff = 100;
JMSContext context = getContext();
Queue queue = context.createQueue("queue:///" + queueName);
if (keystorePath != null && !keystorePath.isBlank() && keystorePassword != null
&& !keystorePassword.isBlank()) {
connectionFactory.setSSLSocketFactory(getSslSocketFactory(keystorePath, keystorePassword));
}
while (System.currentTimeMillis() < deadline) {
List<ReceivedMessage> matched = new ArrayList<>();
try (QueueBrowser browser = context.createBrowser(queue)) {
Enumeration<?> messages = browser.getEnumeration();
while (messages.hasMoreElements()) {
Message message = (Message) messages.nextElement();
ReceivedMessage receivedMessage = toReceivedMessage(message, queueName, expectedFormat);
if (filter == null || filter.test(receivedMessage)) {
matched.add(receivedMessage);
}
}
} catch (JMSException e) {
throw new IllegalStateException("Failed to browse IBM MQ queue: " + queueName, e);
}
if (sslCipherSuite != null && !sslCipherSuite.isBlank()) {
connectionFactory.setSSLCipherSuite(sslCipherSuite);
}
if (!matched.isEmpty()) {
return matched;
}
// Initialize JMS context
connect();
try {
TimeUnit.MILLISECONDS.sleep(backoff);
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
break;
}
backoff = Math.min(backoff * 2, 1000);
}
throw new MessagingTimeoutException("Timeout waiting for IBM MQ message from queue: " + queueName);
}
} catch (Exception e) {
throw new MessagingConnectionException("Failed to create IBM MQ connection to " + queueManager, e);
}
}
@Override
public void close() {
JMSContext context = jmsContext;
if (context != null) {
context.close();
}
}
/**
* Connect to IBM MQ.
*/
private void connect() {
try {
this.jmsContext = connectionFactory.createContext(user, password, JMSContext.AUTO_ACKNOWLEDGE);
this.jmsContext.start();
LOG.info("Connected to IBM MQ: {}", queueManager);
} catch (Exception e) {
throw new MessagingConnectionException(
"Failed to connect to IBM MQ: " + queueManager + " - " + e.getMessage(), e);
}
}
private void sendTextMessage(String queueName, String payload, Map<String, String> properties) {
JMSContext context = getContext();
JMSProducer producer = context.createProducer();
TextMessage message = context.createTextMessage(payload);
applyProperties(message, properties);
producer.send(context.createQueue("queue:///" + queueName), message);
}
/**
* Send a JSON or XML message as TextMessage.
*/
private void sendTextMessage(String queueName, String payload, Map<String, String> properties) {
javax.jms.Queue queue = getQueue(queueName);
private void sendBytesMessage(String queueName,
String payload,
Charset charset,
int ccsid,
Map<String, String> properties) {
try {
JMSContext context = getContext();
JMSProducer producer = context.createProducer();
BytesMessage message = context.createBytesMessage();
message.writeBytes(Optional.ofNullable(payload).orElse("").getBytes(charset));
message.setIntProperty(WMQConstants.JMS_IBM_CHARACTER_SET, ccsid);
applyProperties(message, properties);
producer.send(context.createQueue("queue:///" + queueName), message);
} catch (JMSException e) {
throw new IllegalStateException("Failed to send bytes message to IBM MQ queue: " + queueName, e);
}
}
TextMessage message = jmsContext.createTextMessage(payload);
private void applyProperties(Message message, Map<String, String> properties) {
Optional.ofNullable(properties).orElseGet(Collections::emptyMap)
.forEach((key, value) -> {
try {
message.setStringProperty(key, String.valueOf(value));
} catch (JMSException e) {
throw new IllegalStateException("Failed to set JMS property: " + key, e);
}
});
}
// Set JMS properties
if (properties != null) {
for (Map.Entry<String, String> entry : properties.entrySet()) {
try {
if (entry.getKey().equals(ImqRequest.PROP_JMS_CORRELATION_ID)) {
message.setJMSCorrelationID(entry.getValue());
continue;
} else if (entry.getKey().equals(ImqRequest.PROP_JMS_TYPE)) {
message.setJMSType(entry.getValue());
continue;
} else if (entry.getKey().equals(ImqRequest.PROP_JMS_MESSAGE_ID)) {
message.setJMSMessageID(entry.getValue());
continue;
}
message.setStringProperty(entry.getKey(), entry.getValue());
} catch (JMSException e) {
LOG.warn("Failed to set property: {}", entry.getKey(), e);
}
}
}
private ReceivedMessage toReceivedMessage(Message message, String queueName, MqMessageFormat format) {
try {
Map<String, String> headers = new LinkedHashMap<>();
Enumeration<?> names = message.getPropertyNames();
while (names.hasMoreElements()) {
String name = String.valueOf(names.nextElement());
headers.put(name, String.valueOf(message.getObjectProperty(name)));
}
try {
jmsContext.createProducer().send(queue, message);
} catch (RuntimeException e) {
throw new MessagingDestinationException("Failed to send message to queue: " + queueName, e);
}
LOG.debug("Sent JSON/XML message to queue: {}", queueName);
}
String body = decodeMessage(message, format);
MessageContentType contentType = resolveContentType(message, format);
return new ReceivedMessage(body, contentType, headers, message.getJMSTimestamp(), queueName);
} catch (JMSException e) {
throw new IllegalStateException("Failed to decode IBM MQ message", e);
}
}
/**
* Send a message as BytesMessage with specific encoding and CCSID.
*/
private void sendBytesMessage(String queueName, String payload, Charset charset, int ccsid,
Map<String, String> properties) {
javax.jms.Queue queue = getQueue(queueName);
private MessageContentType resolveContentType(Message message, MqMessageFormat expectedFormat) {
if (message instanceof TextMessage) {
return expectedFormat == MqMessageFormat.XML ? MessageContentType.XML : MessageContentType.JSON;
}
if (expectedFormat == MqMessageFormat.XML) {
return MessageContentType.XML;
}
if (expectedFormat == MqMessageFormat.JSON) {
return MessageContentType.JSON;
}
return MessageContentType.RAW_TEXT;
}
BytesMessage message = jmsContext.createBytesMessage();
private String decodeMessage(Message jmsMessage, MqMessageFormat format) {
try {
if (jmsMessage instanceof TextMessage textMessage) {
return textMessage.getText();
}
// Convert payload to bytes using specified charset
byte[] bytes = payload.getBytes(charset);
try {
message.writeBytes(bytes);
message.setIntProperty("CCSID", ccsid);
} catch (JMSException e) {
throw new MessagingDestinationException("Failed to create bytes message", e);
}
if (jmsMessage instanceof BytesMessage bytesMessage) {
byte[] data = new byte[(int) bytesMessage.getBodyLength()];
bytesMessage.readBytes(data);
Charset charset = switch (format) {
case EBCDIC_870 -> EBCDIC_870;
case UTF8_1208, JSON, XML -> UTF_8;
};
return new String(data, charset);
}
return "";
} catch (Exception e) {
throw new IllegalStateException("Failed to decode JMS message", e);
}
}
// Set JMS properties
if (properties != null) {
for (Map.Entry<String, String> entry : properties.entrySet()) {
try {
if (entry.getKey().equals(ImqRequest.PROP_JMS_CORRELATION_ID)) {
message.setJMSCorrelationID(entry.getValue());
continue;
} else if (entry.getKey().equals(ImqRequest.PROP_JMS_TYPE)) {
message.setJMSType(entry.getValue());
continue;
} else if (entry.getKey().equals(ImqRequest.PROP_JMS_MESSAGE_ID)) {
message.setJMSMessageID(entry.getValue());
continue;
}
message.setStringProperty(entry.getKey(), entry.getValue());
} catch (JMSException e) {
LOG.warn("Failed to set property: {}", entry.getKey(), e);
}
}
}
try {
jmsContext.createProducer().send(queue, message);
} catch (RuntimeException e) {
throw new MessagingDestinationException("Failed to send message to queue: " + queueName, e);
}
LOG.debug("Sent {} message to queue: {}", charset, queueName);
}
/**
* Send a message to a queue with specified format.
*
* @param queueName Queue name
* @param payload Message payload
* @param format Message format (JSON, XML, EBCDIC_870, UTF8_1208)
* @param properties JMS properties to set
*/
public void send(String queueName, String payload, MqMessageFormat format, Map<String, String> properties) {
switch (format) {
case JSON, XML -> sendTextMessage(queueName, payload, properties);
case EBCDIC_870 -> sendBytesMessage(queueName, payload, EBCDIC_870, 870, properties);
case UTF8_1208 -> sendBytesMessage(queueName, payload, UTF_8, 1208, properties);
}
}
/**
* Receive a message from a queue with timeout.
*
* @param queueName Queue name
* @param messageSelector JMS message selector (optional)
* @param format Expected message format
* @param timeout Timeout duration
* @return Received message
*/
public ReceivedMessage receive(String queueName, String messageSelector, MqMessageFormat format,
java.time.Duration timeout) {
long timeoutMs = timeout.toMillis();
javax.jms.Queue queue = getQueue(queueName);
JMSConsumer consumer = (messageSelector == null || messageSelector.isBlank() ? jmsContext.createConsumer(queue)
: jmsContext.createConsumer(queue, messageSelector));
AtomicBoolean messageFound = new AtomicBoolean(false);
ReceivedMessage received = null;
long pollInterval = DEFAULT_POLL_INTERVAL_MS;
long remainingTime = timeoutMs;
try {
while (remainingTime > 0 && !messageFound.get()) {
Message message = consumer.receive(remainingTime);
if (message != null) {
received = decodeMessage(message, queueName, format);
messageFound.set(true);
} else {
// Exponential backoff
pollInterval = Math.min(pollInterval * 2, DEFAULT_MAX_POLL_INTERVAL_MS);
remainingTime -= pollInterval;
}
}
if (received == null) {
throw new MessagingTimeoutException("No message matching filter found on queue '" + queueName
+ "' within " + timeout.toMillis() + "ms");
}
return received;
} catch (MessagingTimeoutException e) {
throw e;
} catch (Exception e) {
throw new MessagingDestinationException("Failed to receive message from queue: " + queueName, e);
} finally {
try {
consumer.close();
} catch (JMSRuntimeException e) {
LOG.warn("Failed to close consumer", e);
}
}
}
/**
* Browse a queue (non-destructive read).
*
* @param queueName Queue name
* @param messageSelector JMS message selector (optional)
* @param format Expected message format
* @param maxMessages Maximum number of messages to browse
* @return List of received messages
*/
public List<ReceivedMessage> browse(String queueName, String messageSelector, MqMessageFormat format,
int maxMessages) {
List<ReceivedMessage> messages = new ArrayList<>();
javax.jms.Queue queue = getQueue(queueName);
try (javax.jms.QueueBrowser browser = (messageSelector == null || messageSelector.isBlank())
? jmsContext.createBrowser(queue)
: jmsContext.createBrowser(queue, messageSelector)) {
Enumeration<?> enumeration = browser.getEnumeration();
int count = 0;
while (enumeration.hasMoreElements() && count < maxMessages) {
Message message = (Message) enumeration.nextElement();
if (message != null) {
ReceivedMessage received = decodeMessage(message, queueName, format);
messages.add(received);
count++;
}
}
return messages;
} catch (JMSException e) {
throw new MessagingDestinationException("Failed to browse queue: " + queueName, e);
}
}
/**
* Decode a JMS message to ReceivedMessage.
*/
private ReceivedMessage decodeMessage(Message jmsMessage, String queueName, MqMessageFormat format) {
long timestamp;
try {
timestamp = jmsMessage.getJMSTimestamp();
} catch (JMSException e) {
timestamp = System.currentTimeMillis();
}
if (timestamp == 0) {
timestamp = System.currentTimeMillis();
}
String body;
MessageContentType contentType;
Map<String, String> headers = new HashMap<>();
// Extract JMS properties as headers
extractJmsProperties(jmsMessage, headers);
if (jmsMessage instanceof TextMessage textMessage) {
try {
body = textMessage.getText();
} catch (JMSException e) {
throw new RuntimeException("Failed to read text message body", e);
}
contentType = switch (format) {
case XML -> MessageContentType.XML;
default -> MessageContentType.JSON;
};
} else if (jmsMessage instanceof BytesMessage bytesMessage) {
int ccsid;
try {
ccsid = bytesMessage.getIntProperty("CCSID");
} catch (JMSException e) {
ccsid = 1208; // default UTF-8
}
body = decodeBytesMessage(bytesMessage, ccsid);
contentType = MessageContentType.RAW_TEXT;
} else {
try {
throw new IllegalArgumentException("Unsupported message type: " + jmsMessage.getJMSType());
} catch (JMSException e) {
throw new IllegalArgumentException("Unsupported message type", e);
}
}
return new ReceivedMessage(body, contentType, headers, timestamp, queueName, null);
}
/**
* Decode BytesMessage body based on CCSID.
*/
private String decodeBytesMessage(BytesMessage bytesMessage, int ccsid) {
try {
long bodyLength;
try {
bodyLength = bytesMessage.getBodyLength();
} catch (JMSException e) {
throw new RuntimeException("Failed to get message body length", e);
}
byte[] data = new byte[(int) bodyLength];
bytesMessage.readBytes(data);
Charset charset = switch (ccsid) {
case 870 -> EBCDIC_870;
case 1208 -> UTF_8;
default -> UTF_8;
};
return new String(data, charset);
} catch (JMSException e) {
throw new RuntimeException("Failed to read BytesMessage body", e);
}
}
/**
* Extract JMS properties as headers.
*/
@SuppressWarnings("unchecked")
private void extractJmsProperties(Message message, Map<String, String> headers) {
try {
// Common JMS headers
headers.put("JMSMessageID", message.getJMSMessageID());
try {
headers.put("JMSType", message.getJMSType() != null ? message.getJMSType() : "");
} catch (JMSException e) {
headers.put("JMSType", "");
}
try {
headers.put("JMSDestination",
message.getJMSDestination() != null ? message.getJMSDestination().toString() : "");
} catch (JMSException e) {
headers.put("JMSDestination", "");
}
try {
headers.put("JMSDeliveryMode", String.valueOf(message.getJMSDeliveryMode()));
} catch (JMSException e) {
headers.put("JMSDeliveryMode", "");
}
try {
headers.put("JMSPriority", String.valueOf(message.getJMSPriority()));
} catch (JMSException e) {
headers.put("JMSPriority", "");
}
try {
headers.put("JMSTimestamp", String.valueOf(message.getJMSTimestamp()));
} catch (JMSException e) {
headers.put("JMSTimestamp", "");
}
// Extract custom properties
Enumeration<String> propertyNames = (Enumeration<String>) message.getPropertyNames();
while (propertyNames.hasMoreElements()) {
String propName = propertyNames.nextElement();
Object propValue = message.getObjectProperty(propName);
if (propValue != null) {
headers.put(propName, propValue.toString());
}
}
} catch (JMSException e) {
LOG.warn("Failed to extract JMS properties", e);
}
}
/**
* Get Queue object from queue name.
*/
private javax.jms.Queue getQueue(String queueName) {
return jmsContext.createQueue(queueName);
}
@Override
public void close() {
if (jmsContext != null) {
try {
jmsContext.close();
LOG.info("Closed connection to IBM MQ: {}", queueManager);
} catch (Exception e) {
LOG.error("Failed to close IBM MQ connection", e);
}
}
}
private SSLSocketFactory getSslSocketFactory(String keystorePath, String keystorePassword) throws Exception {
// --- keystore ---
KeyStore keyStore = KeyStore.getInstance("JKS");
InputStream ksStream = IbmMqConnector.class.getClassLoader().getResourceAsStream(keystorePath);
if (ksStream == null) {
throw new IllegalStateException("Keystore not found: " + keystorePath);
}
keyStore.load(ksStream, keystorePassword.toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, keystorePassword.toCharArray());
// --- truststore ---
KeyStore trustStore = KeyStore.getInstance("JKS");
InputStream tsStream = IbmMqConnector.class.getClassLoader().getResourceAsStream(keystorePath);
if (tsStream == null) {
throw new IllegalStateException("Truststore not found: " + keystorePath);
}
trustStore.load(tsStream, keystorePassword.toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(trustStore);
// --- SSL context ---
SSLContext sslContext = SSLContext.getInstance(TLS_VERSION);
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
return sslContext.getSocketFactory();
}
private JMSContext getContext() {
JMSContext current = jmsContext;
if (current == null) {
synchronized (contextLock) {
current = jmsContext;
if (current == null) {
jmsContext = current = connectionFactory.createContext(user, password, JMSContext.AUTO_ACKNOWLEDGE);
}
}
}
return current;
}
}

View File

@ -1,115 +0,0 @@
package cz.moneta.test.harness.connectors.messaging;
import com.google.gson.*;
import org.apache.avro.Schema;
import org.apache.avro.generic.GenericData;
import org.apache.avro.generic.GenericDatumReader;
import org.apache.avro.generic.GenericDatumWriter;
import org.apache.avro.generic.GenericRecord;
import org.apache.avro.io.DatumReader;
import org.apache.avro.io.DatumWriter;
import org.apache.avro.io.Decoder;
import org.apache.avro.io.JsonDecoder;
import java.util.ArrayList;
import java.util.List;
public class JsonToAvroConverter {
protected static GenericRecord processJson(String json, Schema schema) throws IllegalArgumentException, JsonSchemaException {
GenericRecord result = (GenericRecord) jsonElementToAvro(JsonParser.parseString(json), schema);
return result;
}
private static Object jsonElementToAvro(JsonElement element, Schema schema) throws JsonSchemaException {
boolean schemaIsNullable = isNullable(schema);
if (schemaIsNullable) {
schema = typeFromNullable(schema);
}
if (element == null || element.isJsonNull()) {
if (!schemaIsNullable) {
throw new JsonSchemaException("The element is not nullable in Avro schema.");
}
return null;
} else if (element.isJsonObject()) {
if (schema.getType() != Schema.Type.RECORD) {
throw new JsonSchemaException(
String.format("The element `%s` doesn't match Avro type RECORD", element));
}
return jsonObjectToAvro(element.getAsJsonObject(), schema);
} else if (element.isJsonArray()) {
if (schema.getType() != Schema.Type.ARRAY) {
throw new JsonSchemaException(
String.format("The element `%s` doesn't match Avro type ARRAY", element));
}
JsonArray jsonArray = element.getAsJsonArray();
List<Object> avroArray = new ArrayList<>(jsonArray.size());
for (JsonElement e : element.getAsJsonArray()) {
avroArray.add(jsonElementToAvro(e, schema.getElementType()));
}
return avroArray;
} else if (element.isJsonPrimitive()) {
return jsonPrimitiveToAvro(element.getAsJsonPrimitive(), schema);
} else {
throw new JsonSchemaException(
String.format(
"The Json element `%s` is of an unknown class %s", element, element.getClass()));
}
}
private static GenericRecord jsonObjectToAvro(JsonObject jsonObject, Schema schema) throws JsonSchemaException {
GenericRecord avroRecord = new GenericData.Record(schema);
for (Schema.Field field : schema.getFields()) {
avroRecord.put(field.name(), jsonElementToAvro(jsonObject.get(field.name()), field.schema()));
}
return avroRecord;
}
private static boolean isNullable(Schema type) {
return type.getType() == Schema.Type.NULL
|| type.getType() == Schema.Type.UNION
&& type.getTypes().stream().anyMatch(JsonToAvroConverter::isNullable);
}
private static Schema typeFromNullable(Schema type) {
if (type.getType() == Schema.Type.UNION) {
return typeFromNullable(
type.getTypes().stream()
.filter(t -> t.getType() != Schema.Type.NULL)
.findFirst()
.orElseThrow(
() ->
new IllegalStateException(
String.format("Type `%s` should have a non null subtype", type))));
}
return type;
}
private static Object jsonPrimitiveToAvro(JsonPrimitive primitive, Schema schema){
switch (schema.getType()) {
case NULL:
return null;
case STRING:
return primitive.getAsString();
case BOOLEAN:
return primitive.getAsBoolean();
case INT:
return primitive.getAsInt();
case LONG:
return primitive.getAsLong();
case FLOAT:
return primitive.getAsFloat();
case DOUBLE:
return primitive.getAsDouble();
default:
return primitive.getAsString();
}
}
private static class JsonSchemaException extends Exception {
JsonSchemaException(String message) {
super(message);
}
}
}

View File

@ -1,16 +1,18 @@
package cz.moneta.test.harness.connectors.messaging;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ExecutionException;
import java.util.function.Predicate;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import cz.moneta.test.harness.connectors.Connector;
import cz.moneta.test.harness.exception.MessagingTimeoutException;
import cz.moneta.test.harness.messaging.model.MessageContentType;
import cz.moneta.test.harness.messaging.model.ReceivedMessage;
import io.confluent.kafka.schemaregistry.client.CachedSchemaRegistryClient;
import io.confluent.kafka.serializers.AbstractKafkaSchemaSerDeConfig;
import io.confluent.kafka.serializers.KafkaAvroDeserializer;
import io.confluent.kafka.serializers.KafkaAvroDeserializerConfig;
import io.confluent.kafka.serializers.KafkaAvroSerializer;
import org.apache.avro.Schema;
import org.apache.avro.generic.GenericData;
import org.apache.avro.generic.GenericRecord;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
@ -20,329 +22,332 @@ import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.common.header.Headers;
import org.apache.kafka.common.header.Header;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.apache.kafka.common.serialization.StringSerializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import cz.moneta.test.harness.messaging.exception.MessagingConnectionException;
import cz.moneta.test.harness.messaging.exception.MessagingDestinationException;
import cz.moneta.test.harness.messaging.exception.MessagingSchemaException;
import cz.moneta.test.harness.messaging.exception.MessagingTimeoutException;
import cz.moneta.test.harness.support.messaging.kafka.MessageContentType;
import cz.moneta.test.harness.support.messaging.kafka.ReceivedMessage;
import io.confluent.kafka.schemaregistry.client.CachedSchemaRegistryClient;
import io.confluent.kafka.serializers.KafkaAvroDeserializer;
import io.confluent.kafka.serializers.KafkaAvroSerializer;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Properties;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
/**
* Kafka connector for sending and receiving messages.
* Supports Avro serialization with Confluent Schema Registry.
* <p>
* Uses manual partition assignment (no consumer group) for test isolation.
* Each receive operation creates a new consumer to avoid offset sharing.
*/
public class KafkaConnector implements cz.moneta.test.harness.connectors.Connector {
public class KafkaConnector implements Connector {
private static final Logger LOG = LoggerFactory.getLogger(KafkaConnector.class);
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private final Properties producerConfig;
private final Properties consumerConfig;
private final String schemaRegistryUrl;
private final Properties producerProps = new Properties();
private final Properties consumerProps = new Properties();
private final CachedSchemaRegistryClient schemaRegistryClient;
private KafkaProducer<String, GenericRecord> producer;
private volatile KafkaProducer<String, GenericRecord> producer;
private final Object producerLock = new Object();
/**
* Creates a new KafkaConnector.
*
* @param bootstrapServers Kafka bootstrap servers
* @param apiKey Kafka API key
* @param apiSecret Kafka API secret
* @param schemaRegistryUrl Schema Registry URL
* @param schemaRegistryApiKey Schema Registry API key
* @param schemaRegistryApiSecret Schema Registry API secret
*/
public KafkaConnector(String bootstrapServers,
String apiKey,
String apiSecret,
String schemaRegistryUrl,
String schemaRegistryApiKey,
String schemaRegistryApiSecret) {
this.schemaRegistryUrl = schemaRegistryUrl;
this.schemaRegistryClient = new CachedSchemaRegistryClient(
Collections.singletonList(schemaRegistryUrl), 100, new HashMap<>());
String jaasConfig = String.format("org.apache.kafka.common.security.plain.PlainLoginModule required username=\"%s\" password=\"%s\";",
apiKey,
apiSecret);
String schemaRegistryAuth = schemaRegistryApiKey + ":" + schemaRegistryApiSecret;
this.producerConfig = createProducerConfig(bootstrapServers, apiKey, apiSecret);
this.consumerConfig = createConsumerConfig(bootstrapServers, schemaRegistryApiKey, schemaRegistryApiSecret);
producerProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
producerProps.put("security.protocol", "SASL_SSL");
producerProps.put("sasl.mechanism", "PLAIN");
producerProps.put("sasl.jaas.config", jaasConfig);
producerProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
producerProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, KafkaAvroSerializer.class.getName());
producerProps.put(AbstractKafkaSchemaSerDeConfig.SCHEMA_REGISTRY_URL_CONFIG, schemaRegistryUrl);
producerProps.put(AbstractKafkaSchemaSerDeConfig.BASIC_AUTH_CREDENTIALS_SOURCE, "USER_INFO");
producerProps.put(AbstractKafkaSchemaSerDeConfig.USER_INFO_CONFIG, schemaRegistryAuth);
producerProps.put("auto.register.schemas", "false");
consumerProps.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
consumerProps.put("security.protocol", "SASL_SSL");
consumerProps.put("sasl.mechanism", "PLAIN");
consumerProps.put("sasl.jaas.config", jaasConfig);
consumerProps.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
consumerProps.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, KafkaAvroDeserializer.class.getName());
consumerProps.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "latest");
consumerProps.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false");
consumerProps.put(KafkaAvroDeserializerConfig.SPECIFIC_AVRO_READER_CONFIG, "false");
consumerProps.put(AbstractKafkaSchemaSerDeConfig.SCHEMA_REGISTRY_URL_CONFIG, schemaRegistryUrl);
consumerProps.put(AbstractKafkaSchemaSerDeConfig.BASIC_AUTH_CREDENTIALS_SOURCE, "USER_INFO");
consumerProps.put(AbstractKafkaSchemaSerDeConfig.USER_INFO_CONFIG, schemaRegistryAuth);
this.schemaRegistryClient = new CachedSchemaRegistryClient(schemaRegistryUrl, 128);
}
/**
* Creates producer configuration.
*/
private Properties createProducerConfig(String bootstrapServers, String apiKey, String apiSecret) {
Properties config = new Properties();
config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
config.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
config.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, KafkaAvroSerializer.class);
config.put("schema.registry.url", schemaRegistryUrl);
config.put(ProducerConfig.ACKS_CONFIG, "all");
config.put(ProducerConfig.LINGER_MS_CONFIG, 1);
config.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384);
// SASL/PLAIN authentication
// config.put("security.protocol", "SASL_SSL");
// config.put("sasl.mechanism", "PLAIN");
// config.put("sasl.jaas.config",
// "org.apache.kafka.common.security.plain.PlainLoginModule required " +
// "username=\"" + apiKey + "\" password=\"" + apiSecret + "\";");
// SSL configuration
// config.put("ssl.endpoint.identification.algorithm", "https");
return config;
}
/**
* Creates consumer configuration.
*/
private Properties createConsumerConfig(String bootstrapServers, String apiKey, String apiSecret) {
Properties config = new Properties();
config.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
config.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
config.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, KafkaAvroDeserializer.class);
config.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "none");
config.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
// SASL/PLAIN authentication
config.put("security.protocol", "SASL_SSL");
config.put("sasl.mechanism", "PLAIN");
config.put("sasl.jaas.config",
"org.apache.kafka.common.security.plain.PlainLoginModule required " +
"username=\"" + apiKey + "\" password=\"" + apiSecret + "\";");
// Schema Registry for deserialization
config.put("schema.registry.url", schemaRegistryUrl);
config.put("specific.avro.reader", false);
// SSL configuration
config.put("ssl.endpoint.identification.algorithm", "https");
return config;
}
/**
* Sends a message to a Kafka topic.
*/
public void send(String topic, String key, String jsonPayload, Map<String, String> headers) {
Objects.requireNonNull(topic, "topic");
Schema schema = getSchemaForTopic(topic);
GenericRecord record = jsonToAvro(jsonPayload, schema);
ProducerRecord<String, GenericRecord> producerRecord = new ProducerRecord<>(topic, key, record);
Optional.ofNullable(headers).orElseGet(HashMap::new)
.forEach((headerKey, headerValue) -> producerRecord.headers()
.add(headerKey, String.valueOf(headerValue).getBytes(StandardCharsets.UTF_8)));
try {
org.apache.avro.Schema schema = getSchemaForTopic(topic);
GenericRecord record = jsonToAvro(jsonPayload, schema);
ProducerRecord<String, GenericRecord> producerRecord =
new ProducerRecord<>(topic, key, record);
// Add headers
if (headers != null) {
Headers kafkaHeaders = producerRecord.headers();
headers.forEach((k, v) ->
kafkaHeaders.add(k, v.getBytes(StandardCharsets.UTF_8)));
}
// Send and wait for confirmation
getProducer().send(producerRecord, (metadata, exception) -> {
if (exception != null) {
LOG.error("Failed to send message", exception);
} else {
LOG.debug("Message sent to topic {} partition {} offset {}",
metadata.topic(), metadata.partition(), metadata.offset());
}
}).get(10, java.util.concurrent.TimeUnit.SECONDS);
} catch (ExecutionException e) {
throw new MessagingConnectionException(
"Failed to send message to topic " + topic, e.getCause());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new MessagingConnectionException(
"Interrupted while sending message to topic " + topic, e);
} catch (MessagingSchemaException e) {
throw e;
getProducer().send(producerRecord).get(30, TimeUnit.SECONDS);
} catch (Exception e) {
throw new MessagingSchemaException(
"Failed to convert JSON to Avro for topic " + topic, e);
throw new IllegalStateException("Failed to send Kafka message to topic: " + topic, e);
}
}
/**
* Receives a message from a Kafka topic matching the filter.
*/
public List<ReceivedMessage> receive(String topic,
Predicate<ReceivedMessage> filter,
Duration timeout) {
KafkaConsumer<String, GenericRecord> consumer = null;
try {
consumer = new KafkaConsumer<>(consumerConfig);
long timeoutMillis = Optional.ofNullable(timeout).orElse(Duration.ofSeconds(30)).toMillis();
long deadline = System.currentTimeMillis() + timeoutMillis;
long backoff = 100;
// Get partitions for the topic
List<TopicPartition> partitions = getPartitionsForTopic(consumer, topic);
if (partitions.isEmpty()) {
throw new MessagingDestinationException(
"Topic '" + topic + "' does not exist or has no partitions");
}
// Assign partitions and seek to end
try (KafkaConsumer<String, Object> consumer = createConsumer()) {
List<TopicPartition> partitions = consumer.partitionsFor(topic).stream()
.map(info -> new TopicPartition(topic, info.partition()))
.toList();
consumer.assign(partitions);
// consumer.seekToBeginning(partitions);
consumer.seekToBeginning(partitions);
consumer.seekToEnd(partitions);
// Poll loop with exponential backoff
long startTime = System.currentTimeMillis();
Duration pollInterval = Duration.ofMillis(100);
Duration maxPollInterval = Duration.ofSeconds(1);
while (Duration.ofMillis(System.currentTimeMillis() - startTime).compareTo(timeout) < 0) {
ConsumerRecords<String, GenericRecord> records = consumer.poll(pollInterval);
for (ConsumerRecord<String, GenericRecord> record : records) {
ReceivedMessage message = convertToReceivedMessage(record, topic);
if (filter.test(message)) {
LOG.debug("Found matching message on topic {} partition {} offset {}",
record.topic(), record.partition(), record.offset());
return Collections.singletonList(message);
while (System.currentTimeMillis() < deadline) {
ConsumerRecords<String, Object> records = consumer.poll(Duration.ofMillis(100));
if (!records.isEmpty()) {
for (ConsumerRecord<String, Object> record : records) {
ReceivedMessage message = toReceivedMessage(record);
if (filter == null || filter.test(message)) {
return List.of(message);
}
}
backoff = 100;
continue;
}
// Exponential backoff
pollInterval = Duration.ofMillis(
Math.min(pollInterval.toMillis() * 2, maxPollInterval.toMillis()));
}
throw new MessagingTimeoutException(
"No message matching filter found on topic '" + topic + "' within " + timeout);
} finally {
if (consumer != null) {
consumer.close();
TimeUnit.MILLISECONDS.sleep(backoff);
backoff = Math.min(backoff * 2, 1000);
}
} catch (MessagingTimeoutException e) {
throw e;
} catch (Exception e) {
throw new IllegalStateException("Failed to receive Kafka message from topic: " + topic, e);
}
throw new MessagingTimeoutException("Timeout waiting for Kafka message from topic: " + topic);
}
/**
* Gets partitions for a topic.
*/
private List<TopicPartition> getPartitionsForTopic(KafkaConsumer<?, ?> consumer, String topic) {
List<TopicPartition> partitions = new ArrayList<>();
List<org.apache.kafka.common.PartitionInfo> partitionInfos = consumer.partitionsFor(topic);
if (partitionInfos != null) {
for (org.apache.kafka.common.PartitionInfo partitionInfo : partitionInfos) {
partitions.add(new TopicPartition(topic, partitionInfo.partition()));
}
}
return partitions;
}
/**
* Saves current offsets for a topic.
*/
public Map<TopicPartition, Long> saveOffsets(String topic) {
return new HashMap<>();
try (KafkaConsumer<String, Object> consumer = createConsumer()) {
List<TopicPartition> partitions = consumer.partitionsFor(topic).stream()
.map(info -> new TopicPartition(topic, info.partition()))
.toList();
consumer.assign(partitions);
consumer.seekToEnd(partitions);
Map<TopicPartition, Long> offsets = new HashMap<>();
partitions.forEach(partition -> offsets.put(partition, consumer.position(partition)));
return offsets;
}
}
/**
* Closes the connector and releases resources.
*/
@Override
public void close() {
if (producer != null) {
producer.close();
KafkaProducer<String, GenericRecord> current = producer;
if (current != null) {
current.close(Duration.ofSeconds(5));
}
}
/**
* Gets or creates the producer (singleton, thread-safe).
*/
private KafkaProducer<String, GenericRecord> getProducer() {
if (producer == null) {
synchronized (this) {
if (producer == null) {
producer = new KafkaProducer<>(producerConfig);
KafkaProducer<String, GenericRecord> current = producer;
if (current == null) {
synchronized (producerLock) {
current = producer;
if (current == null) {
producer = current = new KafkaProducer<>(producerProps);
}
}
}
return producer;
return current;
}
/**
* Retrieves schema from Schema Registry based on topic name.
*/
private org.apache.avro.Schema getSchemaForTopic(String topic) {
private KafkaConsumer<String, Object> createConsumer() {
Properties properties = new Properties();
properties.putAll(consumerProps);
properties.put(ConsumerConfig.GROUP_ID_CONFIG, "harness-" + UUID.randomUUID());
return new KafkaConsumer<>(properties);
}
private ReceivedMessage toReceivedMessage(ConsumerRecord<String, Object> record) {
String body = convertValueToJson(record.value());
Map<String, String> headers = new LinkedHashMap<>();
for (Header header : record.headers()) {
headers.put(header.key(), new String(header.value(), StandardCharsets.UTF_8));
}
return new ReceivedMessage(body,
MessageContentType.JSON,
headers,
record.timestamp(),
record.topic());
}
private String convertValueToJson(Object value) {
try {
if (value instanceof GenericRecord genericRecord) {
return avroToJson(genericRecord);
}
if (value instanceof CharSequence) {
return value.toString();
}
return OBJECT_MAPPER.writeValueAsString(value);
} catch (Exception e) {
throw new IllegalStateException("Failed to convert Kafka payload to JSON", e);
}
}
private Schema getSchemaForTopic(String topic) {
String subject = topic + "-value";
try {
io.confluent.kafka.schemaregistry.client.SchemaMetadata metadata =
schemaRegistryClient.getLatestSchemaMetadata(subject);
return new org.apache.avro.Schema.Parser().parse(metadata.getSchema());
// Get all versions and use the latest one
java.util.List<Integer> versions = schemaRegistryClient.getAllVersions(subject);
int latestVersion = versions.get(versions.size() - 1);
io.confluent.kafka.schemaregistry.client.rest.entities.Schema confluentSchema =
schemaRegistryClient.getByVersion(subject, latestVersion, false);
String schemaString = confluentSchema.getSchema();
return new Schema.Parser().parse(schemaString);
} catch (Exception e) {
if (e.getMessage() != null && e.getMessage().contains("404")) {
throw new MessagingSchemaException(
"Schema not found for subject '" + subject + "' in Schema Registry. " +
"Make sure the topic exists and schema is registered.");
}
throw new MessagingSchemaException(
"Failed to retrieve schema for topic '" + topic + "'", e);
throw new IllegalStateException("Failed to get schema for subject: " + subject, e);
}
}
/**
* Converts JSON string to Avro GenericRecord.
*/
private GenericRecord jsonToAvro(String json, org.apache.avro.Schema schema) {
try {
GenericRecord genericRecord = JsonToAvroConverter.processJson(json, schema);
return genericRecord;
} catch (Exception e) {
throw new MessagingSchemaException("Failed to convert JSON to Avro: " + e.getMessage(), e);
}
}
/**
* Converts Kafka ConsumerRecord to ReceivedMessage.
*/
private ReceivedMessage convertToReceivedMessage(ConsumerRecord<String, GenericRecord> record, String topic) {
try {
String jsonBody = avroToJson(record.value());
Map<String, String> headers = new HashMap<>();
Headers kafkaHeaders = record.headers();
for (org.apache.kafka.common.header.Header header : kafkaHeaders) {
if (header.value() != null) {
headers.put(header.key(), new String(header.value(), StandardCharsets.UTF_8));
}
}
return ReceivedMessage.builder()
.body(jsonBody)
.contentType(MessageContentType.JSON)
.headers(headers)
.timestamp(record.timestamp())
.source(topic)
.key(record.key())
.build();
} catch (Exception e) {
LOG.error("Failed to convert Avro record to ReceivedMessage", e);
throw new RuntimeException("Failed to convert message", e);
}
}
/**
* Converts Avro GenericRecord to JSON string.
*/
private String avroToJson(GenericRecord record) {
try {
return record.toString();
return OBJECT_MAPPER.writeValueAsString(convertAvroObject(record));
} catch (Exception e) {
throw new RuntimeException("Failed to convert Avro to JSON: " + e.getMessage(), e);
throw new IllegalStateException("Failed to convert Avro record to JSON", e);
}
}
private GenericRecord jsonToAvro(String jsonPayload, Schema schema) {
try {
JsonNode root = OBJECT_MAPPER.readTree(jsonPayload);
Object converted = convertJsonNode(root, schema);
return (GenericRecord) converted;
} catch (Exception e) {
throw new IllegalStateException("Failed to convert JSON payload to Avro", e);
}
}
private Object convertJsonNode(JsonNode node, Schema schema) {
return switch (schema.getType()) {
case RECORD -> {
GenericData.Record record = new GenericData.Record(schema);
schema.getFields().forEach(field -> record.put(field.name(),
convertJsonNode(node.path(field.name()), field.schema())));
yield record;
}
case ARRAY -> {
List<Object> values = new ArrayList<>();
node.forEach(item -> values.add(convertJsonNode(item, schema.getElementType())));
yield values;
}
case MAP -> {
Map<String, Object> map = new HashMap<>();
node.fields().forEachRemaining(entry -> map.put(entry.getKey(),
convertJsonNode(entry.getValue(), schema.getValueType())));
yield map;
}
case UNION -> resolveUnion(node, schema);
case ENUM -> new GenericData.EnumSymbol(schema, node.asText());
case FIXED -> {
byte[] fixedBytes = toBytes(node);
yield new GenericData.Fixed(schema, fixedBytes);
}
case STRING -> node.isNull() ? null : node.asText();
case INT -> node.isNull() ? null : node.asInt();
case LONG -> node.isNull() ? null : node.asLong();
case FLOAT -> node.isNull() ? null : (float) node.asDouble();
case DOUBLE -> node.isNull() ? null : node.asDouble();
case BOOLEAN -> node.isNull() ? null : node.asBoolean();
case BYTES -> ByteBuffer.wrap(toBytes(node));
case NULL -> null;
};
}
private Object resolveUnion(JsonNode node, Schema unionSchema) {
if (node == null || node.isNull()) {
return null;
}
IllegalStateException lastException = null;
for (Schema candidate : unionSchema.getTypes()) {
if (candidate.getType() == Schema.Type.NULL) {
continue;
}
try {
Object value = convertJsonNode(node, candidate);
if (GenericData.get().validate(candidate, value)) {
return value;
}
} catch (Exception e) {
lastException = new IllegalStateException("Failed to resolve union type", e);
}
}
if (lastException != null) {
throw lastException;
}
throw new IllegalStateException("Cannot resolve union for node: " + node);
}
private byte[] toBytes(JsonNode node) {
if (node.isBinary()) {
try {
return node.binaryValue();
} catch (Exception ignored) {
// fallback to textual representation
}
}
String text = node.asText();
try {
return Base64.getDecoder().decode(text);
} catch (Exception ignored) {
return text.getBytes(StandardCharsets.UTF_8);
}
}
private Object convertAvroObject(Object value) {
if (value == null) {
return null;
}
if (value instanceof GenericRecord record) {
Map<String, Object> jsonObject = new LinkedHashMap<>();
record.getSchema().getFields().forEach(field -> jsonObject.put(field.name(), convertAvroObject(record.get(field.name()))));
return jsonObject;
}
if (value instanceof GenericData.Array<?> array) {
List<Object> values = new ArrayList<>(array.size());
array.forEach(item -> values.add(convertAvroObject(item)));
return values;
}
if (value instanceof Map<?, ?> map) {
Map<String, Object> values = new LinkedHashMap<>();
map.forEach((key, item) -> values.put(String.valueOf(key), convertAvroObject(item)));
return values;
}
if (value instanceof ByteBuffer byteBuffer) {
ByteBuffer duplicate = byteBuffer.duplicate();
byte[] bytes = new byte[duplicate.remaining()];
duplicate.get(bytes);
return Base64.getEncoder().encodeToString(bytes);
}
return value;
}
}

View File

@ -55,5 +55,4 @@ public class CaGwEndpoint implements RestEndpoint {
public StoreAccessor getStore() {
return store;
}
}

View File

@ -1,199 +0,0 @@
package cz.moneta.test.harness.endpoints.imq;
import java.time.Duration;
import java.util.List;
import java.util.Optional;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import cz.moneta.test.harness.connectors.VaultConnector;
import cz.moneta.test.harness.connectors.messaging.IbmMqConnector;
import cz.moneta.test.harness.constants.HarnessConfigConstants;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.Endpoint;
import cz.moneta.test.harness.messaging.MqMessageFormat;
import cz.moneta.test.harness.messaging.ReceivedMessage;
import cz.moneta.test.harness.support.auth.Credentials;
/**
* IBM MQ First Vision endpoint. Provides high-level access to IBM MQ queues
* with configuration from StoreAccessor.
* <p>
* Credentials are loaded from HashiCorp Vault.
*/
public class ImqFirstVisionEndpoint implements Endpoint {
private static final Logger LOG = LogManager.getLogger(ImqFirstVisionEndpoint.class);
private final IbmMqConnector connector;
private final StoreAccessor store;
private String username, password, keystorePassword;
// Configuration keys
private static final String CONNECTION_NAME_LIST_KEY = "endpoints.imq-first-vision.connection-name-list";
private static final String CHANNEL_KEY = "endpoints.imq-first-vision.channel";
private static final String QUEUE_MANAGER_KEY = "endpoints.imq-first-vision.queue-manager";
private static final String SSL_CIPHER_SUITE_KEY = "endpoints.imq-first-vision.ssl-cipher-suite";
private static final String VAULT_PATH_KEY = "vault.imq-first-vision.secrets.path";
private static final String VAULT_KEYSTORE_PASSWORD_KEY = "keystorePassword";
private static final String KEYSTORE_PATH = "keystores/imq-keystore.jks";
/**
* Constructor that reads configuration from StoreAccessor.
*/
public ImqFirstVisionEndpoint(StoreAccessor store) {
this.store = store;
// Read configuration
String connectionNameList = getConfig(CONNECTION_NAME_LIST_KEY);
String channel = getConfig(CHANNEL_KEY);
String queueManager = getConfig(QUEUE_MANAGER_KEY);
String sslCipherSuite = getConfig(SSL_CIPHER_SUITE_KEY);
loadCredentialsFromVault();
try {
this.connector = new IbmMqConnector(connectionNameList, channel, queueManager, username, password,
KEYSTORE_PATH, keystorePassword, sslCipherSuite);
LOG.info("Initialized IBM MQ First Vision endpoint for queue manager: {}", queueManager);
} catch (Exception e) {
throw new IllegalStateException("Failed to initialize IBM MQ endpoint", e);
}
}
/**
* Get a configuration value from StoreAccessor.
*/
private String getConfig(String key) {
return Optional.ofNullable(store.getConfig(key))
.orElseThrow(() -> new IllegalStateException("You need to configure " + key));
}
/**
* Load credentials from HashiCorp Vault.
*/
private void loadCredentialsFromVault() {
try {
// Get vault URL from configuration
String vaultPath = getConfig(VAULT_PATH_KEY);
String vaultUrl = getConfig(HarnessConfigConstants.VAULT_URL_CONFIG);
String vaultUser = getConfig(HarnessConfigConstants.VAULT_USERNAME_CONFIG);
String vaultPassword = getConfig(HarnessConfigConstants.VAULT_PASSWORD_CONFIG);
VaultConnector vaultConnector = new VaultConnector(vaultUrl, vaultUser, vaultPassword);
Optional<Credentials> credentials = vaultConnector.getUsernameAndPassword(vaultPath);
if (credentials.isPresent()) {
this.username = credentials.get().getUsername();
this.password = credentials.get().getPassword();
this.keystorePassword = vaultConnector.getValue(vaultPath, VAULT_KEYSTORE_PASSWORD_KEY)
.map(Object::toString).orElse(null);
LOG.info("Successfully loaded credentials from Vault for path: {}", vaultPath);
} else {
throw new IllegalStateException("Credentials not found in Vault at path: " + vaultPath);
}
} catch (Exception e) {
throw new IllegalStateException("Failed to load credentials from Vault", e);
}
}
/**
* Send a message to a queue.
*
* @param queueName Physical queue name or logical name (from
* ImqFirstVisionQueue)
* @param payload Message payload
* @param format Message format
* @param properties JMS properties
*/
public void send(String queueName, String payload, MqMessageFormat format,
java.util.Map<String, String> properties) {
connector.send(queueName, payload, format, properties);
}
/**
* Send a message to a queue using logical queue name.
*/
public void send(ImqFirstVisionQueue queue, String payload, MqMessageFormat format,
java.util.Map<String, String> properties) {
String physicalQueueName = resolveQueue(queue);
connector.send(physicalQueueName, payload, format, properties);
}
/**
* Receive a message from a queue.
*
* @param queueName Physical queue name or logical name
* @param messageSelector JMS message selector (optional)
* @param format Expected message format
* @param timeout Timeout duration
* @return Received message
*/
public ReceivedMessage receive(String queueName, String messageSelector, MqMessageFormat format, Duration timeout) {
return connector.receive(queueName, messageSelector, format, timeout);
}
/**
* Receive a message from a queue using logical queue name.
*/
public ReceivedMessage receive(ImqFirstVisionQueue queue, String messageSelector, MqMessageFormat format,
Duration timeout) {
String physicalQueueName = resolveQueue(queue);
return connector.receive(physicalQueueName, messageSelector, format, timeout);
}
/**
* Browse a queue (non-destructive read).
*
* @param queueName Physical queue name or logical name
* @param messageSelector JMS message selector (optional)
* @param format Expected message format
* @param maxMessages Maximum number of messages
* @return List of received messages
*/
public List<ReceivedMessage> browse(String queueName, String messageSelector, MqMessageFormat format,
int maxMessages) {
return connector.browse(queueName, messageSelector, format, maxMessages);
}
/**
* Browse a queue using logical queue name.
*/
public List<ReceivedMessage> browse(ImqFirstVisionQueue queue, String messageSelector, MqMessageFormat format,
int maxMessages) {
String physicalQueueName = resolveQueue(queue);
return connector.browse(physicalQueueName, messageSelector, format, maxMessages);
}
/**
* Resolve logical queue name to physical queue name.
*
* @param logicalName Logical queue name or ImqFirstVisionQueue enum
* @return Physical queue name
*/
public String resolveQueue(String logicalName) {
String configKey = "endpoints.imq-first-vision." + logicalName + ".queue";
return Optional.ofNullable(store.getConfig(configKey)).orElseThrow(
() -> new IllegalStateException("Queue '" + logicalName + "' is not configured in " + configKey));
}
/**
* Resolve ImqFirstVisionQueue enum to physical queue name.
*/
public String resolveQueue(ImqFirstVisionQueue queue) {
return resolveQueue(queue.getConfigKey());
}
@Override
public void close() {
if (connector != null) {
connector.close();
}
}
}

View File

@ -1,51 +0,0 @@
package cz.moneta.test.harness.endpoints.imq;
/**
* Logical queue names for IBM MQ First Vision.
* Physical queue names are resolved from configuration.
*/
public enum ImqFirstVisionQueue {
/**
* Payment notifications queue.
*/
PAYMENT_NOTIFICATIONS("payment-notifications"),
/**
* Payment request queue.
*/
PAYMENT_REQUEST("payment-request"),
/**
* MF (Money Flow) requests queue.
*/
MF_REQUESTS("mf-requests"),
/**
* MF (Money Flow) responses queue.
*/
MF_RESPONSES("mf-responses"),
/**
* MF (Money Flow) EBCDIC queue.
*/
MF_EBCDIC("mf-ebcdic"),
/**
* MF (Money Flow) UTF-8 queue.
*/
MF_UTF8("mf-utf8");
private final String configKey;
ImqFirstVisionQueue(String configKey) {
this.configKey = configKey;
}
/**
* Get the configuration key for this queue.
* Used to resolve physical queue name from configuration.
*/
public String getConfigKey() {
return configKey;
}
}

View File

@ -1,199 +0,0 @@
package cz.moneta.test.harness.endpoints.kafka;
import cz.moneta.test.harness.connectors.messaging.KafkaConnector;
import cz.moneta.test.harness.context.BaseStoreAccessor;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.Endpoint;
import cz.moneta.test.harness.messaging.exception.MessagingConnectionException;
import cz.moneta.test.harness.support.messaging.kafka.ReceivedMessage;
import com.bettercloud.vault.Vault;
import com.bettercloud.vault.VaultConfig;
import com.bettercloud.vault.VaultException;
import com.bettercloud.vault.response.LogicalResponse;
import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
* Kafka endpoint that provides high-level API for Kafka messaging.
* <p>
* Reads configuration from StoreAccessor and credentials from HashiCorp Vault.
* Implements singleton pattern per test class (managed by Harness).
* </p>
*/
public class KafkaEndpoint implements Endpoint {
private final KafkaConnector connector;
private final StoreAccessor store;
/**
* Creates a new KafkaEndpoint.
* <p>
* Configuration is read from StoreAccessor:
* - endpoints.kafka.bootstrap-servers
* - endpoints.kafka.schema-registry-url
* <p>
* Credentials are retrieved from Vault:
* - vault.kafka.secrets.path -> apiKey, apiSecret
* </p>
*/
public KafkaEndpoint(StoreAccessor store) {
this.store = store;
// Read configuration
String bootstrapServers = store.getConfig("endpoints.kafka.bootstrap-servers");
if (bootstrapServers == null) {
throw new IllegalStateException("You need to configure " + bootstrapServers + " to work with Kafka");
}
String schemaRegistryUrl = store.getConfig("endpoints.kafka.schema-registry-url");
if (schemaRegistryUrl == null) {
throw new IllegalStateException("You need to configure " + schemaRegistryUrl + " url to work with Kafka");
}
// Retrieve credentials from Vault
String vaultPath = store.getConfig("vault.kafka.secrets.path");
if (vaultPath == null) {
throw new IllegalStateException(
"You need to configure vault.kafka.secrets.path");
}
//
String apiKey = getVaultValue(vaultPath, "apiKey");
String apiSecret = getVaultValue(vaultPath, "apiSecret");
String schemaRegistryApiKey = getVaultValue(vaultPath, "schemaRegistryApiKey");
String schemaRegistryApiSecret = getVaultValue(vaultPath, "schemaRegistryApiSecret");
// Create connector
this.connector = new KafkaConnector(
bootstrapServers,
apiKey,
apiSecret,
schemaRegistryUrl,
schemaRegistryApiKey,
schemaRegistryApiSecret
);
}
/**
* Sends a message to a Kafka topic.
*
* @param topic Kafka topic name
* @param key Message key
* @param jsonPayload Message body as JSON string
* @param headers Kafka headers (traceparent, requestID, activityID, etc.)
*/
public void send(String topic, String key, String jsonPayload, Map<String, String> headers) {
connector.send(topic, key, jsonPayload, headers);
}
/**
* Receives a message from a Kafka topic matching the filter.
*
* @param topic Kafka topic name
* @param filter Predicate to filter messages
* @param timeout Maximum time to wait for a message
* @return First matching ReceivedMessage
*/
public List<ReceivedMessage> receive(String topic,
java.util.function.Predicate<ReceivedMessage> filter,
Duration timeout) {
return connector.receive(topic, filter, timeout);
}
/**
* Receives a message with default timeout (30 seconds).
*/
public List<ReceivedMessage> receive(String topic,
java.util.function.Predicate<ReceivedMessage> filter) {
return receive(topic, filter, Duration.ofSeconds(30));
}
/**
* Receives the first available message from a topic.
*
* @param topic Kafka topic name
* @param timeout Maximum time to wait
* @return First message
*/
public List<ReceivedMessage> receive(String topic, Duration timeout) {
return receive(topic, msg -> true, timeout);
}
/**
* Closes the endpoint and releases resources.
*/
@Override
public void close() {
if (connector != null) {
connector.close();
}
}
/**
* Checks if Kafka is accessible.
*/
@Override
public boolean canAccess() {
try {
// Try to get topic metadata - if this succeeds, Kafka is accessible
// Note: We don't actually make a network call here, just verify config
String bootstrapServers = store.getConfig("endpoints.kafka.bootstrap-servers");
return bootstrapServers != null && !bootstrapServers.isEmpty();
} catch (Exception e) {
return false;
}
}
/**
* Gets the underlying connector (for advanced use cases).
*/
public KafkaConnector getConnector() {
return connector;
}
/**
* Gets the store accessor.
*/
public StoreAccessor getStore() {
return store;
}
/**
* Retrieves a value from Vault.
*/
private String getVaultValue(String path, String key) {
try {
VaultConfig vaultConfig = new VaultConfig()
.address(store.getConfig("vault.address", "http://localhost:8200"))
.token(store.getConfig("vault.token"))
.build();
Vault vault = new Vault(vaultConfig, 2);
LogicalResponse response = vault.logical().read(path);
if (response == null) {
throw new MessagingConnectionException(
"Failed to read from Vault path: " + path);
}
Map<String, String> data = response.getData();
if (data == null) {
throw new MessagingConnectionException(
"No data found in Vault path: " + path);
}
String value = data.get(key);
if (value == null) {
throw new MessagingConnectionException(
"Credential '" + key + "' not found in Vault path: " + path);
}
return value;
} catch (VaultException e) {
throw new MessagingConnectionException(
"Failed to retrieve credential '" + key + "' from Vault at " + path, e);
}
}
}

View File

@ -0,0 +1,125 @@
package cz.moneta.test.harness.endpoints.messaging;
import cz.moneta.test.harness.connectors.VaultConnector;
import cz.moneta.test.harness.connectors.messaging.IbmMqConnector;
import cz.moneta.test.harness.constants.HarnessConfigConstants;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.Endpoint;
import cz.moneta.test.harness.messaging.model.MqMessageFormat;
import cz.moneta.test.harness.messaging.model.ReceivedMessage;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Predicate;
public class IbmMqEndpoint implements Endpoint {
private static final String CONFIG_HOST = "endpoints.imq-first-vision.host";
private static final String CONFIG_PORT = "endpoints.imq-first-vision.port";
private static final String CONFIG_CHANNEL = "endpoints.imq-first-vision.channel";
private static final String CONFIG_QUEUE_MANAGER = "endpoints.imq-first-vision.queue-manager";
private static final String CONFIG_KEYSTORE_PATH = "endpoints.imq-first-vision.keystore.path";
private static final String CONFIG_KEYSTORE_PASSWORD = "endpoints.imq-first-vision.keystore.password";
private static final String CONFIG_VAULT_PATH = "vault.path.messaging.ibmmq";
private static final String CONFIG_USERNAME = "endpoints.imq-first-vision.username";
private static final String CONFIG_PASSWORD = "endpoints.imq-first-vision.password";
private static final String CONFIG_SSL_CYPHER_SUITES = "endpoints.imq-first-vision.ssl-cipher-suite";
private final StoreAccessor store;
private volatile IbmMqConnector connector;
private final Object connectorLock = new Object();
public IbmMqEndpoint(StoreAccessor store) {
this.store = store;
}
public void send(String queueName,
String payload,
MqMessageFormat format,
Map<String, String> properties) {
getConnector().send(queueName, payload, format, properties);
}
public ReceivedMessage receive(String queueName,
String messageSelector,
MqMessageFormat expectedFormat,
Duration timeout) {
return getConnector().receive(queueName, messageSelector, expectedFormat, timeout);
}
public List<ReceivedMessage> browse(String queueName,
Predicate<ReceivedMessage> filter,
MqMessageFormat expectedFormat,
Duration timeout) {
return getConnector().browse(queueName, filter, expectedFormat, timeout);
}
@Override
public void close() {
IbmMqConnector current = connector;
if (current != null) {
current.close();
}
}
private IbmMqConnector getConnector() {
IbmMqConnector current = connector;
if (current == null) {
synchronized (connectorLock) {
current = connector;
if (current == null) {
current = createConnector();
connector = current;
}
}
}
return current;
}
private IbmMqConnector createConnector() {
String host = requireConfig(CONFIG_HOST);
int port = Integer.parseInt(requireConfig(CONFIG_PORT));
String channel = requireConfig(CONFIG_CHANNEL);
String queueManager = requireConfig(CONFIG_QUEUE_MANAGER);
String username = resolveSecret(CONFIG_USERNAME, "username");
String password = resolveSecret(CONFIG_PASSWORD, "password");
String keystorePath = store.getConfig(CONFIG_KEYSTORE_PATH);
String keystorePassword = store.getConfig(CONFIG_KEYSTORE_PASSWORD);
String sslCipherSuites = store.getConfig(CONFIG_SSL_CYPHER_SUITES);
return new IbmMqConnector(host, port, channel, queueManager, username, password, keystorePath, keystorePassword, sslCipherSuites);
}
private String resolveSecret(String localConfigKey, String vaultKey) {
return Optional.ofNullable(store.getConfig(localConfigKey))
.or(() -> readFromVault(vaultKey))
.orElseThrow(() -> new IllegalStateException("Missing messaging secret: " + localConfigKey));
}
private Optional<String> readFromVault(String key) {
String path = store.getConfig(CONFIG_VAULT_PATH);
if (path == null || path.isBlank()) {
return Optional.empty();
}
String basePath = store.getConfig("vault.path.base");
String resolvedPath = path.startsWith("/") || basePath == null || basePath.isBlank()
? path
: basePath + "/" + path;
return createVaultConnector().flatMap(vault -> vault.getValue(resolvedPath, key));
}
private Optional<VaultConnector> createVaultConnector() {
return Optional.ofNullable(store.getConfig(HarnessConfigConstants.VAULT_URL_CONFIG))
.flatMap(url -> Optional.ofNullable(store.getConfig(HarnessConfigConstants.VAULT_USERNAME_CONFIG))
.flatMap(username -> Optional.ofNullable(store.getConfig(HarnessConfigConstants.VAULT_PASSWORD_CONFIG))
.map(password -> new VaultConnector(url, username, password))));
}
private String requireConfig(String key) {
return Optional.ofNullable(store.getConfig(key))
.orElseThrow(() -> new IllegalStateException("Missing required config: " + key));
}
}

View File

@ -0,0 +1,119 @@
package cz.moneta.test.harness.endpoints.messaging;
import cz.moneta.test.harness.connectors.VaultConnector;
import cz.moneta.test.harness.connectors.messaging.KafkaConnector;
import cz.moneta.test.harness.constants.HarnessConfigConstants;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.Endpoint;
import cz.moneta.test.harness.messaging.model.ReceivedMessage;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Predicate;
public class KafkaEndpoint implements Endpoint {
private static final String CONFIG_BOOTSTRAP_SERVERS = "messaging.kafka.bootstrap-servers";
private static final String CONFIG_SCHEMA_REGISTRY_URL = "messaging.kafka.schema-registry-url";
private static final String CONFIG_VAULT_PATH = "vault.path.messaging.kafka";
private static final String CONFIG_API_KEY = "messaging.kafka.api-key";
private static final String CONFIG_API_SECRET = "messaging.kafka.api-secret";
private static final String CONFIG_SCHEMA_REGISTRY_API_KEY = "messaging.kafka.schema-registry-api-key";
private static final String CONFIG_SCHEMA_REGISTRY_API_SECRET = "messaging.kafka.schema-registry-api-secret";
private static final String VAULT_API_KEY = "apiKey";
private static final String VAULT_API_SECRET = "apiSecret";
private static final String VAULT_SCHEMA_REGISTRY_API_KEY = "schemaRegistryApiKey";
private static final String VAULT_SCHEMA_REGISTRY_API_SECRET = "schemaRegistryApiSecret";
private final StoreAccessor store;
private volatile KafkaConnector connector;
private final Object connectorLock = new Object();
public KafkaEndpoint(StoreAccessor store) {
this.store = store;
}
public void send(String topic, String key, String payload, Map<String, String> headers) {
getConnector().send(topic, key, payload, headers);
}
public ReceivedMessage receive(String topic, Predicate<ReceivedMessage> filter, Duration timeout) {
List<ReceivedMessage> messages = getConnector().receive(topic, filter, timeout);
return messages.isEmpty() ? null : messages.get(0);
}
@Override
public void close() {
KafkaConnector current = connector;
if (current != null) {
current.close();
}
}
private KafkaConnector getConnector() {
KafkaConnector current = connector;
if (current == null) {
synchronized (connectorLock) {
current = connector;
if (current == null) {
current = createConnector();
connector = current;
}
}
}
return current;
}
private KafkaConnector createConnector() {
String bootstrapServers = requireConfig(CONFIG_BOOTSTRAP_SERVERS);
String schemaRegistryUrl = requireConfig(CONFIG_SCHEMA_REGISTRY_URL);
String apiKey = resolveSecret(CONFIG_API_KEY, VAULT_API_KEY);
String apiSecret = resolveSecret(CONFIG_API_SECRET, VAULT_API_SECRET);
String schemaRegistryApiKey = resolveSecret(CONFIG_SCHEMA_REGISTRY_API_KEY, VAULT_SCHEMA_REGISTRY_API_KEY);
String schemaRegistryApiSecret = resolveSecret(CONFIG_SCHEMA_REGISTRY_API_SECRET, VAULT_SCHEMA_REGISTRY_API_SECRET);
return new KafkaConnector(
bootstrapServers,
apiKey,
apiSecret,
schemaRegistryUrl,
schemaRegistryApiKey,
schemaRegistryApiSecret
);
}
private String resolveSecret(String localConfigKey, String vaultKey) {
return Optional.ofNullable(store.getConfig(localConfigKey))
.or(() -> readFromVault(vaultKey))
.orElseThrow(() -> new IllegalStateException("Missing messaging secret: " + localConfigKey));
}
private Optional<String> readFromVault(String key) {
String path = store.getConfig(CONFIG_VAULT_PATH);
if (path == null || path.isBlank()) {
return Optional.empty();
}
String basePath = store.getConfig("vault.path.base");
String resolvedPath = path.startsWith("/") || basePath == null || basePath.isBlank()
? path
: basePath + "/" + path;
return createVaultConnector().flatMap(vault -> vault.getValue(resolvedPath, key));
}
private Optional<VaultConnector> createVaultConnector() {
return Optional.ofNullable(store.getConfig(HarnessConfigConstants.VAULT_URL_CONFIG))
.flatMap(url -> Optional.ofNullable(store.getConfig(HarnessConfigConstants.VAULT_USERNAME_CONFIG))
.flatMap(username -> Optional.ofNullable(store.getConfig(HarnessConfigConstants.VAULT_PASSWORD_CONFIG))
.map(password -> new VaultConnector(url, username, password))));
}
private String requireConfig(String key) {
return Optional.ofNullable(store.getConfig(key))
.orElseThrow(() -> new IllegalStateException("Missing required config: " + key));
}
}

View File

@ -0,0 +1,130 @@
package cz.moneta.test.harness.endpoints.messaging;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.Endpoint;
import cz.moneta.test.harness.exception.MessagingTimeoutException;
import cz.moneta.test.harness.messaging.model.Destination;
import cz.moneta.test.harness.messaging.model.MqMessageFormat;
import cz.moneta.test.harness.messaging.model.Queue;
import cz.moneta.test.harness.messaging.model.ReceivedMessage;
import cz.moneta.test.harness.messaging.model.Topic;
import java.time.Duration;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Predicate;
public class MessagingEndpoint implements Endpoint {
private final StoreAccessor store;
private volatile KafkaEndpoint kafkaEndpoint;
private volatile IbmMqEndpoint ibmMqEndpoint;
private final Object endpointLock = new Object();
public MessagingEndpoint(StoreAccessor store) {
this.store = store;
}
public void send(String destinationName,
String key,
String payload,
MqMessageFormat formatOverride,
Map<String, String> headers) {
Destination destination = resolveDestination(destinationName);
if (destination instanceof Topic topic) {
getKafkaEndpoint().send(topic.getTopicName(), key, payload, headers);
return;
}
Queue queue = (Queue) destination;
MqMessageFormat format = Optional.ofNullable(formatOverride).orElse(queue.getFormat());
getIbmMqEndpoint().send(queue.getQueueName(), payload, format, headers);
}
public ReceivedMessage receive(String destinationName,
Predicate<ReceivedMessage> filter,
Duration timeout,
MqMessageFormat formatOverride) {
Destination destination = resolveDestination(destinationName);
if (destination instanceof Topic topic) {
return getKafkaEndpoint().receive(topic.getTopicName(), filter, timeout);
}
Queue queue = (Queue) destination;
MqMessageFormat format = Optional.ofNullable(formatOverride).orElse(queue.getFormat());
List<ReceivedMessage> messages = getIbmMqEndpoint().browse(queue.getQueueName(), filter, format, timeout);
if (messages.isEmpty()) {
throw new MessagingTimeoutException("No IBM MQ message found for destination: " + destinationName);
}
return messages.get(0);
}
public Destination resolveDestination(String destinationName) {
String prefix = "messaging.destination." + destinationName + ".";
String type = Optional.ofNullable(store.getConfig(prefix + "type"))
.map(v -> v.toLowerCase(Locale.ROOT))
.orElseThrow(() -> new IllegalStateException("Missing destination config: " + prefix + "type"));
return switch (type) {
case "kafka" -> {
String topic = requireConfig(prefix + "topic");
yield new Topic(destinationName, topic);
}
case "ibmmq" -> {
String queue = requireConfig(prefix + "queue");
String format = store.getConfig(prefix + "format", MqMessageFormat.JSON.name().toLowerCase(Locale.ROOT));
yield new Queue(destinationName, queue, MqMessageFormat.fromConfig(format, MqMessageFormat.JSON));
}
default -> throw new IllegalStateException("Unsupported destination type '" + type + "' for destination: " + destinationName);
};
}
@Override
public void close() {
KafkaEndpoint kafka = kafkaEndpoint;
if (kafka != null) {
kafka.close();
}
IbmMqEndpoint mq = ibmMqEndpoint;
if (mq != null) {
mq.close();
}
}
private KafkaEndpoint getKafkaEndpoint() {
KafkaEndpoint current = kafkaEndpoint;
if (current == null) {
synchronized (endpointLock) {
current = kafkaEndpoint;
if (current == null) {
current = new KafkaEndpoint(store);
kafkaEndpoint = current;
}
}
}
return current;
}
private IbmMqEndpoint getIbmMqEndpoint() {
IbmMqEndpoint current = ibmMqEndpoint;
if (current == null) {
synchronized (endpointLock) {
current = ibmMqEndpoint;
if (current == null) {
current = new IbmMqEndpoint(store);
ibmMqEndpoint = current;
}
}
}
return current;
}
private String requireConfig(String key) {
return Optional.ofNullable(store.getConfig(key))
.filter(v -> !Objects.equals(v.trim(), ""))
.orElseThrow(() -> new IllegalStateException("Missing required config: " + key));
}
}

View File

@ -1,51 +0,0 @@
package cz.moneta.test.harness.endpoints.payment_engine;
import cz.moneta.test.harness.connectors.rest.RestConnector;
import cz.moneta.test.harness.connectors.rest.SimpleRestConnector;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.RestEndpoint;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.GenericType;
import org.apache.commons.lang3.tuple.Pair;
import java.util.Map;
import java.util.Optional;
public class PaeMqEndpoint implements RestEndpoint {
private RestConnector restConnector;
private StoreAccessor store;
public PaeMqEndpoint(StoreAccessor store) {
this.store = store;
String endpointName = "endpoints.pae-mq.url";
this.restConnector = Optional.ofNullable(store.getConfig(endpointName))
.map(url -> new SimpleRestConnector(url, "PaeMqServerLogger"))
.orElseThrow(() -> new IllegalStateException("You need to configure " + endpointName + " to work with PAE IBM MQ server"));
}
@Override
public <T> Pair<Integer, T> get(String path, Map<String, Object> properties, Class<T> responseType, Map<String, Object> headers) {
return restConnector.get(path, properties, new GenericType<>(responseType), headers);
}
@Override
public <T> Pair<Integer, T> post(String path, Entity<?> request, Class<T> responseType, Map<String, Object> headers, boolean rawResponse) {
return restConnector.post(path, request, new GenericType<>(responseType), headers);
}
@Override
public <T> Pair<Integer, T> delete(String path, Map<String, Object> properties, Class<T> responseType, Map<String, Object> headers) {
return restConnector.delete(path, properties, new GenericType<>(responseType), headers);
}
@Override
public <T> Pair<Integer, T> patch(String path, Entity<?> request, Class<T> responseType, Map<String, Object> headers, boolean rawResponse) {
return restConnector.patch(path, request, new GenericType<>(responseType), headers);
}
@Override
public StoreAccessor getStore() {
return store;
}
}

View File

@ -0,0 +1,8 @@
package cz.moneta.test.harness.exception;
public class MessagingTimeoutException extends HarnessException {
public MessagingTimeoutException(String message) {
super(message);
}
}

View File

@ -1,83 +0,0 @@
package cz.moneta.test.harness.messaging;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.NullNode;
/**
* Wrapper for extracted JSON path values.
* Provides fluent methods for value extraction and conversion.
*/
public class JsonPathValue {
private final JsonNode node;
private final String rawValue;
public JsonPathValue(JsonNode node) {
this.node = node;
this.rawValue = node != null ? node.asText() : null;
}
public JsonPathValue(String rawValue) {
this.node = null;
this.rawValue = rawValue;
}
/**
* Get the value as a string.
*/
public String asText() {
if (node != null && !(node instanceof NullNode)) {
return node.asText();
}
return rawValue;
}
/**
* Get the value as an integer.
*/
public int asInt() {
if (node != null && !(node instanceof NullNode)) {
return node.asInt();
}
return Integer.parseInt(rawValue);
}
/**
* Get the value as a long.
*/
public long asLong() {
if (node != null && !(node instanceof NullNode)) {
return node.asLong();
}
return Long.parseLong(rawValue);
}
/**
* Get the value as a boolean.
*/
public boolean asBoolean() {
if (node != null && !(node instanceof NullNode)) {
return node.asBoolean();
}
return Boolean.parseBoolean(rawValue);
}
/**
* Check if the value is null or missing.
*/
public boolean isNull() {
return node == null || node instanceof NullNode || rawValue == null;
}
/**
* Get the underlying JsonNode.
*/
public JsonNode getNode() {
return node;
}
@Override
public String toString() {
return asText();
}
}

View File

@ -1,21 +0,0 @@
package cz.moneta.test.harness.messaging;
/**
* Content type of a received message.
*/
public enum MessageContentType {
/**
* JSON content - body is a JSON string.
*/
JSON,
/**
* XML content - body is an XML string.
*/
XML,
/**
* Raw text content - body is plain text (e.g., EBCDIC decoded, UTF-8).
*/
RAW_TEXT
}

View File

@ -1,111 +0,0 @@
package cz.moneta.test.harness.messaging;
import org.assertj.core.api.AbstractObjectAssert;
/**
* Response interface for received messages.
* Provides assertion methods for verifying message content.
* Shared interface for both Kafka and IBM MQ message responses.
*/
public interface MessageResponse {
/**
* Assert that a field in the message body has the expected value.
* For JSON: uses JSON path (dot/bracket notation).
* For XML: uses XPath expression.
*
* @param path JSON path or XPath expression
* @param value expected value as string
* @return this instance for fluent assertions
* @throws AssertionError if assertion fails
*/
MessageResponse andAssertFieldValue(String path, String value);
/**
* Assert that a field exists in the message body.
*
* @param path JSON path or XPath expression
* @return this instance for fluent assertions
* @throws AssertionError if assertion fails
*/
MessageResponse andAssertPresent(String path);
/**
* Assert that a field does not exist in the message body.
*
* @param path JSON path or XPath expression
* @return this instance for fluent assertions
* @throws AssertionError if assertion fails
*/
MessageResponse andAssertNotPresent(String path);
/**
* Assert that a header (Kafka header or JMS property) has the expected value.
*
* @param headerName name of the header/property
* @param value expected value
* @return this instance for fluent assertions
* @throws AssertionError if assertion fails
*/
MessageResponse andAssertHeaderValue(String headerName, String value);
/**
* Assert that the message body contains a substring.
* Primarily used for EBCDIC/UTF-8 raw text assertions.
*
* @param substring expected substring
* @return this instance for fluent assertions
* @throws AssertionError if assertion fails
*/
MessageResponse andAssertBodyContains(String substring);
/**
* Get AssertJ fluent assertion for complex object assertions.
*
* @return AssertJ AbstractObjectAssert for fluent assertions
*/
AbstractObjectAssert<?, ?> andAssertWithAssertJ();
/**
* Extract a value from the message body.
* For JSON: uses JSON path (dot/bracket notation).
* For XML: uses XPath expression.
*
* @param path JSON path or XPath expression
* @return JsonPathValue wrapper for the extracted value
*/
JsonPathValue extract(String path);
/**
* Deserialize the message body to a Java object.
* For JSON: uses Jackson ObjectMapper.
* For XML: uses JAXB or Jackson XmlMapper.
*
* @param type target type
* @param <T> target type
* @return deserialized object
*/
<T> T mapTo(Class<T> type);
/**
* Get the raw message body.
*
* @return message body as string
*/
String getBody();
/**
* Get a header value (Kafka header or JMS property).
*
* @param name header/property name
* @return header value or null if not present
*/
String getHeader(String name);
/**
* Get the underlying received message.
*
* @return ReceivedMessage instance
*/
ReceivedMessage getMessage();
}

View File

@ -1,31 +0,0 @@
package cz.moneta.test.harness.messaging;
/**
* Message format for IBM MQ.
* Defines how messages are encoded and transmitted.
*/
public enum MqMessageFormat {
/**
* JSON format - JMS TextMessage with plain JSON string.
* Default format for IBM MQ.
*/
JSON,
/**
* XML format - JMS TextMessage with XML string.
* XML is decoded and can be queried using XPath.
*/
XML,
/**
* EBCDIC format - JMS BytesMessage with EBCDIC IBM-870 encoding.
* Used for mainframe systems (Czech/Slovak characters).
*/
EBCDIC_870,
/**
* UTF-8 format - JMS BytesMessage with UTF-8 (CCSID 1208) encoding.
* Used for binary data with explicit UTF-8 encoding.
*/
UTF8_1208
}

View File

@ -1,386 +0,0 @@
package cz.moneta.test.harness.messaging;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.StringUtils;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Represents a received message from a messaging system.
* Body is always normalized to a String regardless of source and wire format.
* <p>
* For Kafka: Avro GenericRecord is automatically converted to JSON.
* For IBM MQ (JSON): JSON string from JMS TextMessage.
* For IBM MQ (XML): XML string from JMS TextMessage.
* For IBM MQ (EBCDIC): byte[] from JMS BytesMessage decoded from IBM-870.
*/
public class ReceivedMessage {
private static final ObjectMapper JSON_MAPPER = new ObjectMapper();
private static final Pattern JSON_PATH_PATTERN = Pattern.compile("^([\\w.]+)\\[([\\d]+)\\](.*)$");
private final String body;
private final MessageContentType contentType;
private final Map<String, String> headers;
private final long timestamp;
private final String source;
private final String key;
public ReceivedMessage(String body, MessageContentType contentType, Map<String, String> headers,
long timestamp, String source, String key) {
this.body = body;
this.contentType = contentType;
this.headers = headers != null ? Collections.unmodifiableMap(new HashMap<>(headers)) : new HashMap<>();
this.timestamp = timestamp;
this.source = source;
this.key = key;
}
/**
* Extract a JSON value using JSON path (dot/bracket notation).
* Supports paths like "items[0].sku" or "nested.field".
*
* @param path JSON path
* @return JsonNode for the extracted value
*/
public JsonNode extractJson(String path) {
if (body == null || StringUtils.isEmpty(path)) {
return null;
}
try {
JsonNode root = JSON_MAPPER.readTree(body);
return evaluateJsonPath(root, path);
} catch (Exception e) {
throw new RuntimeException("Failed to extract JSON path: " + path, e);
}
}
/**
* Extract a value using XPath (for XML messages).
*
* @param xpathExpression XPath expression
* @return extracted value as string
*/
public String extractXml(String xpathExpression) {
if (body == null || StringUtils.isEmpty(xpathExpression)) {
return null;
}
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
DocumentBuilder builder = factory.newDocumentBuilder();
javax.xml.parsers.DocumentBuilder finalBuilder = builder;
var document = finalBuilder.parse(new java.io.ByteArrayInputStream(body.getBytes()));
document.getDocumentElement().normalize();
XPath xpath = XPathFactory.newInstance().newXPath();
Object result = xpath.evaluate(xpathExpression, document, XPathConstants.NODE);
if (result instanceof org.w3c.dom.Node domNode) {
return domNode.getTextContent();
}
return null;
} catch (Exception e) {
throw new RuntimeException("Failed to evaluate XPath: " + xpathExpression, e);
}
}
/**
* Universal extract method - auto-detects content type and uses appropriate extraction.
*
* @param expression JSON path or XPath expression
* @return extracted value as string
*/
public String extract(String expression) {
return switch (contentType) {
case JSON -> extractJson(expression).asText();
case XML -> extractXml(expression);
case RAW_TEXT -> body;
};
}
/**
* Evaluate JSON path on a JSON node.
* Supports dot notation and bracket notation for arrays.
*/
private JsonNode evaluateJsonPath(JsonNode node, String path) {
if (StringUtils.isEmpty(path)) {
return node;
}
String[] parts = tokenizePath(path);
JsonNode current = node;
for (String part : parts) {
if (current == null) {
return null;
}
if (part.isEmpty()) {
continue;
}
if (part.contains("[")) {
// Array access
Matcher matcher = JSON_PATH_PATTERN.matcher(part);
if (matcher.matches()) {
String arrayName = matcher.group(1);
int index = Integer.parseInt(matcher.group(2));
String remaining = matcher.group(3);
if (current.isArray()) {
current = index < current.size() ? current.get(index) : null;
} else if (current.isObject()) {
JsonNode arrayNode = current.get(arrayName);
if (arrayNode != null && arrayNode.isArray()) {
current = index < arrayNode.size() ? arrayNode.get(index) : null;
} else {
current = null;
}
}
// Continue with remaining path
if (StringUtils.isNotBlank(remaining)) {
current = evaluateJsonPath(current, remaining);
}
} else {
current = current.get(part);
}
} else if (part.contains(".")) {
// Navigate through object properties
String[] segments = part.split("\\.");
for (String segment : segments) {
if (StringUtils.isEmpty(segment)) {
continue;
}
current = current.get(segment);
}
} else {
current = current.get(part);
}
}
return current;
}
/**
* Tokenize JSON path into segments.
*/
private String[] tokenizePath(String path) {
if (StringUtils.isEmpty(path)) {
return new String[0];
}
java.util.List<String> tokens = new java.util.ArrayList<>();
StringBuilder current = new StringBuilder();
boolean inBracket = false;
for (int i = 0; i < path.length(); i++) {
char c = path.charAt(i);
if (c == '[') {
inBracket = true;
current.append(c);
} else if (c == ']') {
inBracket = false;
current.append(c);
} else if (c == '.' && !inBracket) {
if (current.length() > 0) {
tokens.add(current.toString());
current.setLength(0);
}
} else {
current.append(c);
}
}
if (current.length() > 0) {
tokens.add(current.toString());
}
return tokens.toArray(new String[0]);
}
/**
* Get the message key (Kafka message key, null for IBM MQ).
*/
public String getKey() {
return key;
}
/**
* Get a header value (Kafka header or JMS property).
*/
public String getHeader(String name) {
return headers.get(name);
}
/**
* Get all headers.
*/
public Map<String, String> getHeaders() {
return headers;
}
/**
* Get the message body.
*/
public String getBody() {
return body;
}
/**
* Get the message timestamp.
*/
public long getTimestamp() {
return timestamp;
}
/**
* Get the content type.
*/
public MessageContentType getContentType() {
return contentType;
}
/**
* Get the source (topic or queue name).
*/
public String getSource() {
return source;
}
/**
* Deserialize the message body to a Java object.
*
* @param type target type
* @param <T> target type
* @return deserialized object
*/
public <T> T mapTo(Class<T> type) {
if (body == null) {
return null;
}
try {
if (contentType == MessageContentType.XML) {
// XML deserialization using JAXB
return mapXmlTo(type);
} else {
// JSON deserialization using Jackson
return JSON_MAPPER.readValue(body, type);
}
} catch (Exception e) {
throw new RuntimeException("Failed to deserialize message to " + type.getName(), e);
}
}
/**
* Deserialize the message body to a Java object for XML.
* Uses JAXB-like parsing - simplified for basic XML structures.
*/
private <T> T mapXmlTo(Class<T> type) {
try {
// For XML, parse to a simple map structure
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
javax.xml.parsers.DocumentBuilder finalBuilder = builder;
var document = finalBuilder.parse(new java.io.ByteArrayInputStream(body.getBytes()));
document.getDocumentElement().normalize();
Map<String, Object> xmlMap = xmlToMap(document.getDocumentElement());
return JSON_MAPPER.convertValue(xmlMap, type);
} catch (Exception e) {
throw new RuntimeException("Failed to deserialize XML message", e);
}
}
/**
* Convert XML element to Map.
*/
private Map<String, Object> xmlToMap(org.w3c.dom.Element element) {
Map<String, Object> result = new HashMap<>();
for (int i = 0; i < element.getAttributes().getLength(); i++) {
org.w3c.dom.NamedNodeMap attributes = element.getAttributes();
org.w3c.dom.Node attr = attributes.item(i);
result.put("@" + attr.getNodeName(), attr.getNodeValue());
}
// Add children
for (int i = 0; i < element.getChildNodes().getLength(); i++) {
org.w3c.dom.Node node = element.getChildNodes().item(i);
if (node.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE) {
org.w3c.dom.Element childElement = (org.w3c.dom.Element) node;
String tagName = childElement.getTagName();
if (childElement.getChildNodes().getLength() == 0) {
// Leaf element
result.put(tagName, childElement.getTextContent());
} else {
// Check if all children are elements (complex) or text (simple)
boolean hasElement = false;
for (int j = 0; j < childElement.getChildNodes().getLength(); j++) {
org.w3c.dom.Node childNode = childElement.getChildNodes().item(j);
if (childNode.getNodeType() == org.w3c.dom.Node.TEXT_NODE &&
StringUtils.isNotBlank(childNode.getTextContent())) {
} else if (childNode.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE) {
hasElement = true;
}
}
if (hasElement) {
Map<String, Object> childMap = xmlToMap(childElement);
if (result.containsKey(tagName)) {
// Convert to list if multiple elements with same name
java.util.List<Object> list = new java.util.ArrayList<>();
if (result.get(tagName) instanceof Map) {
list.add(result.get(tagName));
}
list.add(childMap);
result.put(tagName, list);
} else {
result.put(tagName, childMap);
}
} else {
result.put(tagName, childElement.getTextContent());
}
}
}
}
// If element has only text content and no attributes or children, return text
if (element.getChildNodes().getLength() == 0) {
Map<String, Object> textMap = new HashMap<>();
textMap.put("#text", element.getTextContent());
return textMap;
}
return result;
}
@Override
public String toString() {
return "ReceivedMessage{" +
"contentType=" + contentType +
", source='" + source + '\'' +
", key='" + key + '\'' +
", body='" + body + '\'' +
'}';
}
}

View File

@ -1,20 +0,0 @@
package cz.moneta.test.harness.messaging.exception;
/**
* Exception thrown when connection to messaging system fails.
* Includes authentication failures, network issues, etc.
*/
public class MessagingConnectionException extends MessagingException {
public MessagingConnectionException(String message) {
super(message);
}
public MessagingConnectionException(String message, Throwable cause) {
super(message, cause);
}
public MessagingConnectionException(Throwable cause) {
super(cause);
}
}

View File

@ -1,20 +0,0 @@
package cz.moneta.test.harness.messaging.exception;
/**
* Exception thrown when destination (topic/queue) does not exist or is inaccessible.
* Includes permission issues and unknown object errors.
*/
public class MessagingDestinationException extends MessagingException {
public MessagingDestinationException(String message) {
super(message);
}
public MessagingDestinationException(String message, Throwable cause) {
super(message, cause);
}
public MessagingDestinationException(Throwable cause) {
super(cause);
}
}

View File

@ -1,20 +0,0 @@
package cz.moneta.test.harness.messaging.exception;
/**
* Base exception for all messaging-related errors.
* Extends RuntimeException for unchecked behavior.
*/
public abstract class MessagingException extends RuntimeException {
protected MessagingException(String message) {
super(message);
}
protected MessagingException(String message, Throwable cause) {
super(message, cause);
}
protected MessagingException(Throwable cause) {
super(cause);
}
}

View File

@ -1,20 +0,0 @@
package cz.moneta.test.harness.messaging.exception;
/**
* Exception thrown when schema validation fails or schema is not found.
* Used for Avro schema mismatches or missing schema in Schema Registry.
*/
public class MessagingSchemaException extends MessagingException {
public MessagingSchemaException(String message) {
super(message);
}
public MessagingSchemaException(String message, Throwable cause) {
super(message, cause);
}
public MessagingSchemaException(Throwable cause) {
super(cause);
}
}

View File

@ -1,20 +0,0 @@
package cz.moneta.test.harness.messaging.exception;
/**
* Exception thrown when message receiving times out.
* No matching message found within the specified timeout period.
*/
public class MessagingTimeoutException extends MessagingException {
public MessagingTimeoutException(String message) {
super(message);
}
public MessagingTimeoutException(String message, Throwable cause) {
super(message, cause);
}
public MessagingTimeoutException(Throwable cause) {
super(cause);
}
}

View File

@ -0,0 +1,22 @@
package cz.moneta.test.harness.messaging.model;
import java.util.Objects;
public abstract class Destination {
private final String name;
private final String type;
protected Destination(String name, String type) {
this.name = Objects.requireNonNull(name, "name");
this.type = Objects.requireNonNull(type, "type");
}
public String getName() {
return name;
}
public String getType() {
return type;
}
}

View File

@ -0,0 +1,7 @@
package cz.moneta.test.harness.messaging.model;
public enum MessageContentType {
JSON,
XML,
RAW_TEXT
}

View File

@ -0,0 +1,18 @@
package cz.moneta.test.harness.messaging.model;
import java.util.Locale;
import java.util.Optional;
public enum MqMessageFormat {
JSON,
XML,
EBCDIC_870,
UTF8_1208;
public static MqMessageFormat fromConfig(String value, MqMessageFormat defaultValue) {
return Optional.ofNullable(value)
.map(v -> v.toUpperCase(Locale.ROOT).replace('-', '_'))
.map(MqMessageFormat::valueOf)
.orElse(defaultValue);
}
}

View File

@ -0,0 +1,23 @@
package cz.moneta.test.harness.messaging.model;
import java.util.Objects;
public class Queue extends Destination {
private final String queueName;
private final MqMessageFormat format;
public Queue(String name, String queueName, MqMessageFormat format) {
super(name, "ibmmq");
this.queueName = Objects.requireNonNull(queueName, "queueName");
this.format = Objects.requireNonNull(format, "format");
}
public String getQueueName() {
return queueName;
}
public MqMessageFormat getFormat() {
return format;
}
}

View File

@ -0,0 +1,167 @@
package cz.moneta.test.harness.messaging.model;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.MissingNode;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import org.apache.commons.lang3.StringUtils;
import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import java.io.StringReader;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
public class ReceivedMessage {
private static final Pattern ARRAY_NODE_PATTERN = Pattern.compile("(.*?)\\[([0-9]*?)\\]");
private static final ObjectMapper JSON_MAPPER = new ObjectMapper();
private static final XmlMapper XML_MAPPER = new XmlMapper();
private final String body;
private final MessageContentType contentType;
private final Map<String, String> headers;
private final long timestamp;
private final String source;
public ReceivedMessage(String body,
MessageContentType contentType,
Map<String, String> headers,
long timestamp,
String source) {
this.body = Optional.ofNullable(body).orElse("");
this.contentType = Objects.requireNonNull(contentType, "contentType");
this.headers = Collections.unmodifiableMap(new LinkedHashMap<>(Optional.ofNullable(headers).orElseGet(Collections::emptyMap)));
this.timestamp = timestamp;
this.source = Optional.ofNullable(source).orElse("");
}
public JsonNode extractJson(String path) {
JsonNode root = readJsonLikeNode();
return extractNode(path, root);
}
public String extractXml(String xpathExpression) {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
factory.setNamespaceAware(false);
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(new InputSource(new StringReader(body)));
XPath xpath = XPathFactory.newInstance().newXPath();
xpath.setNamespaceContext(new EmptyNamespaceContext());
return String.valueOf(xpath.evaluate(xpathExpression, document, XPathConstants.STRING));
} catch (Exception e) {
throw new IllegalStateException("Failed to extract xml value for expression: " + xpathExpression, e);
}
}
public String extract(String expression) {
return switch (contentType) {
case JSON -> extractJson(expression).asText();
case XML -> extractXml(expression);
case RAW_TEXT -> body;
};
}
public <T> T mapTo(Class<T> type) {
try {
return switch (contentType) {
case JSON -> JSON_MAPPER.readValue(body, type);
case XML -> XML_MAPPER.readValue(body, type);
case RAW_TEXT -> {
if (String.class.equals(type)) {
yield type.cast(body);
}
throw new IllegalStateException("RAW_TEXT can only be mapped to String");
}
};
} catch (Exception e) {
throw new IllegalStateException("Failed to map message body", e);
}
}
public String getBody() {
return body;
}
public MessageContentType getContentType() {
return contentType;
}
public Map<String, String> getHeaders() {
return headers;
}
public long getTimestamp() {
return timestamp;
}
public String getSource() {
return source;
}
private JsonNode readJsonLikeNode() {
try {
return switch (contentType) {
case JSON -> JSON_MAPPER.readTree(body);
case XML -> XML_MAPPER.readTree(body.getBytes());
case RAW_TEXT -> {
if (StringUtils.isBlank(body)) {
yield MissingNode.getInstance();
}
yield JSON_MAPPER.readTree(body);
}
};
} catch (Exception e) {
throw new IllegalStateException("Unable to parse message as JSON-like content", e);
}
}
private static JsonNode extractNode(String path, JsonNode rootNode) {
return Arrays.stream(path.split("\\."))
.filter(StringUtils::isNotEmpty)
.reduce(rootNode,
(r, p) -> {
Matcher matcher = ARRAY_NODE_PATTERN.matcher(p);
if (matcher.find()) {
return r.path(matcher.group(1)).path(Integer.parseInt(matcher.group(2)));
}
return r.path(p);
},
(j1, j2) -> j1);
}
private static final class EmptyNamespaceContext implements NamespaceContext {
@Override
public String getNamespaceURI(String prefix) {
return XMLConstants.NULL_NS_URI;
}
@Override
public String getPrefix(String namespaceURI) {
return "";
}
@Override
public Iterator<String> getPrefixes(String namespaceURI) {
return Collections.emptyIterator();
}
}
}

View File

@ -0,0 +1,17 @@
package cz.moneta.test.harness.messaging.model;
import java.util.Objects;
public class Topic extends Destination {
private final String topicName;
public Topic(String name, String topicName) {
super(name, "kafka");
this.topicName = Objects.requireNonNull(topicName, "topicName");
}
public String getTopicName() {
return topicName;
}
}

View File

@ -1,595 +0,0 @@
package cz.moneta.test.harness.support.messaging;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import cz.moneta.test.harness.endpoints.imq.ImqFirstVisionEndpoint;
import cz.moneta.test.harness.endpoints.imq.ImqFirstVisionQueue;
import cz.moneta.test.harness.exception.HarnessException;
import cz.moneta.test.harness.messaging.MessageContentType;
import cz.moneta.test.harness.messaging.MqMessageFormat;
import cz.moneta.test.harness.messaging.ReceivedMessage;
import cz.moneta.test.harness.messaging.exception.MessagingTimeoutException;
import cz.moneta.test.harness.support.util.FileReader;
import cz.moneta.test.harness.support.util.Template;
import org.apache.commons.lang3.StringUtils;
import java.time.Duration;
import java.util.*;
import java.util.function.Predicate;
/**
* Fluent builder for IBM MQ requests.
* <p>
* Usage:
* <pre>{@code
* ImqRequest.toQueue(endpoint, queue)
* .asJson()
* .withPayload("{\"field\": \"value\"}")
* .send();
*
* ImqRequest.fromQueue(endpoint, queue)
* .receiveWhere(msg -> msg.extract("field").equals("value"))
* .withTimeout(10, TimeUnit.SECONDS)
* .andAssertFieldValue("result", "OK");
* }</pre>
* </p>
*/
@SuppressWarnings("unchecked")
public final class ImqRequest {
private static final ObjectMapper JSON_MAPPER = new ObjectMapper();
private static final String PROP_LOGICAL_QUEUE = "LogicalQueue";
private static final String PROP_SELECTOR = "Selector";
public static final String PROP_JMS_CORRELATION_ID = "JMSCorrelationID";
public static final String PROP_JMS_MESSAGE_ID = "JMSMessageID";
public static final String PROP_JMS_TYPE = "JMSType";
private ImqRequest() {
}
/**
* Start building a message to send to a queue.
*/
public static QueuePhase toQueue(ImqFirstVisionEndpoint endpoint, ImqFirstVisionQueue queue) {
return new QueueBuilder(endpoint, queue, null);
}
/**
* Start building a message to receive from a queue.
*/
public static ReceivePhase fromQueue(ImqFirstVisionEndpoint endpoint, ImqFirstVisionQueue queue) {
return new QueueBuilder(endpoint, queue, null);
}
/**
* Start building a message to send to a queue by physical name.
*/
public static QueuePhase toQueue(ImqFirstVisionEndpoint endpoint, String queueName) {
return new QueueBuilder(endpoint, null, queueName);
}
/**
* Start building a message to receive from a queue by physical name.
*/
public static ReceivePhase fromQueue(ImqFirstVisionEndpoint endpoint, String queueName) {
return new QueueBuilder(endpoint, null, queueName);
}
/**
* Phase after specifying queue - can send or receive.
*/
public interface QueuePhase extends PayloadPhase, ReceivePhase {
}
/**
* Phase for building message payload.
*/
public interface PayloadPhase {
/**
* Set message format to JSON (default).
*/
QueuePhase asJson();
/**
* Set message format to XML.
*/
QueuePhase asXml();
/**
* Set message format to EBCDIC (IBM-870).
*/
QueuePhase asEbcdic();
/**
* Set message format to UTF-8 (CCSID 1208).
*/
QueuePhase asUtf8();
/**
* Set message payload as JSON string.
*/
PayloadPhase withPayload(String payload);
/**
* Load payload from resource file.
*/
PayloadPhase withPayloadFromFile(String path);
/**
* Render payload from template.
*/
PayloadPhase withPayloadFromTemplate(Template template);
/**
* Add field to JSON payload at root level.
*/
PayloadPhase addField(String fieldName, Object value);
/**
* Add field to JSON payload at specified path.
*/
PayloadPhase addField(String path, String fieldName, Object value);
/**
* Append value to JSON array at specified path.
*/
PayloadPhase appendToArray(String path, Object value);
/**
* Set JMS correlation ID.
*/
PayloadPhase withCorrelationId(String correlationId);
/**
* Set JMS message ID.
*/
PayloadPhase withMessageId(String messageId);
/**
* Set JMS type.
*/
PayloadPhase withJmsType(String jmsType);
/**
* Send the message.
*/
void send();
}
/**
* Phase for receiving messages.
*/
public interface ReceivePhase {
/**
* Set JMS message selector.
*/
ReceivePhase withSelector(String selector);
/**
* Start receiving message matching predicate.
*/
AwaitingPhase receiveWhere(Predicate<ReceivedMessage> filter);
/**
* Browse messages from queue (non-destructive).
*/
List<ReceivedMessage> browse(int maxMessages);
/**
* Browse messages from queue with selector (non-destructive).
*/
List<ReceivedMessage> browse(String selector, int maxMessages);
/**
* Set message format to JSON (default) when receiving.
*/
QueuePhase asJson();
/**
* Set message format to XML when receiving.
*/
QueuePhase asXml();
/**
* Set message format to EBCDIC (IBM-870) when receiving.
*/
QueuePhase asEbcdic();
/**
* Set message format to UTF-8 (CCSID 1208) when receiving.
*/
QueuePhase asUtf8();
}
/**
* Phase after specifying filter - await message with timeout.
*/
public interface AwaitingPhase {
/**
* Set timeout and get message response for assertions.
*/
MessageResponse withTimeout(long duration, java.util.concurrent.TimeUnit unit);
/**
* Set timeout duration and get message response for assertions.
*/
MessageResponse withTimeout(Duration timeout);
}
/**
* Response with fluent assertions.
*/
public interface MessageResponse extends cz.moneta.test.harness.support.messaging.MessageResponse {
}
/**
* Builder for queue operations.
*/
private static class QueueBuilder implements QueuePhase, AwaitingPhase {
private final ImqFirstVisionEndpoint endpoint;
private final ImqFirstVisionQueue logicalQueue;
private final String physicalQueue;
private String selector;
private MqMessageFormat format = MqMessageFormat.JSON;
private String payload;
private Map<String, Object> fields = new HashMap<>();
private List<Map.Entry<String, Object>> arrayAppends = new ArrayList<>();
private String correlationId;
private String messageId;
private String jmsType;
private Predicate<ReceivedMessage> filter;
private Duration timeout;
public QueueBuilder(ImqFirstVisionEndpoint endpoint, ImqFirstVisionQueue logicalQueue, String physicalQueue) {
this.endpoint = endpoint;
this.logicalQueue = logicalQueue;
this.physicalQueue = physicalQueue;
}
private String getQueueName() {
if (logicalQueue != null) {
return endpoint.resolveQueue(logicalQueue);
}
return physicalQueue;
}
@Override
public QueuePhase asJson() {
this.format = MqMessageFormat.JSON;
return this;
}
@Override
public QueuePhase asXml() {
this.format = MqMessageFormat.XML;
return this;
}
@Override
public QueuePhase asEbcdic() {
this.format = MqMessageFormat.EBCDIC_870;
return this;
}
@Override
public QueuePhase asUtf8() {
this.format = MqMessageFormat.UTF8_1208;
return this;
}
@Override
public PayloadPhase withPayload(String payload) {
this.payload = payload;
return this;
}
@Override
public PayloadPhase withPayloadFromFile(String path) {
this.payload = FileReader.readFileFromResources(path);
return this;
}
@Override
public PayloadPhase withPayloadFromTemplate(Template template) {
this.payload = template.render();
return this;
}
@Override
public PayloadPhase addField(String fieldName, Object value) {
return addField("", fieldName, value);
}
@Override
public PayloadPhase addField(String path, String fieldName, Object value) {
String key = StringUtils.isNotBlank(path) && StringUtils.isNotBlank(fieldName) ? path + "." + fieldName : fieldName;
this.fields.put(key, value);
return this;
}
@Override
public PayloadPhase appendToArray(String path, Object value) {
this.arrayAppends.add(Map.entry(path, value));
return this;
}
@Override
public PayloadPhase withCorrelationId(String correlationId) {
this.correlationId = correlationId;
return this;
}
@Override
public PayloadPhase withMessageId(String messageId) {
this.messageId = messageId;
return this;
}
@Override
public PayloadPhase withJmsType(String jmsType) {
this.jmsType = jmsType;
return this;
}
@Override
public void send() {
String finalPayload = buildPayload();
Map<String, String> properties = new HashMap<>();
if (logicalQueue != null) {
properties.put(PROP_LOGICAL_QUEUE, logicalQueue.name());
}
if (selector != null && !selector.isBlank()) {
properties.put(PROP_SELECTOR, selector);
}
if (correlationId != null) {
properties.put(PROP_JMS_CORRELATION_ID, correlationId);
}
if (messageId != null) {
properties.put(PROP_JMS_MESSAGE_ID, messageId);
}
if (jmsType != null) {
properties.put(PROP_JMS_TYPE, jmsType);
}
endpoint.send(getQueueName(), finalPayload, format, properties);
}
@Override
public ReceivePhase withSelector(String selector) {
this.selector = selector;
return this;
}
@Override
public AwaitingPhase receiveWhere(Predicate<ReceivedMessage> filter) {
this.filter = filter;
return this;
}
@Override
public List<ReceivedMessage> browse(int maxMessages) {
return browse(selector, maxMessages);
}
@Override
public List<ReceivedMessage> browse(String sel, int maxMessages) {
return endpoint.browse(getQueueName(), sel, format, maxMessages);
}
@Override
public MessageResponse withTimeout(long duration, java.util.concurrent.TimeUnit unit) {
return withTimeout(Duration.of(duration, java.time.temporal.ChronoUnit.MILLIS));
}
@Override
public MessageResponse withTimeout(Duration timeout) {
this.timeout = timeout;
if (filter == null) {
throw new IllegalStateException("Must specify receiveWhere filter before withTimeout");
}
ReceivedMessage message = receiveMessage();
return new ResponseImpl(message);
}
private ReceivedMessage receiveMessage() {
long startTime = System.currentTimeMillis();
long timeoutMs = timeout.toMillis();
while (System.currentTimeMillis() - startTime < timeoutMs) {
try {
ReceivedMessage message = endpoint.receive(getQueueName(), selector, format, Duration.ofSeconds(1));
if (filter.test(message)) {
return message;
}
} catch (MessagingTimeoutException e) {
// Continue polling
}
}
throw new MessagingTimeoutException(
"No message matching filter found on queue '" + getQueueName() +
"' within " + timeout.toMillis() + "ms");
}
private String buildPayload() {
if (payload == null) {
return "{}";
}
if (fields.isEmpty() && arrayAppends.isEmpty()) {
return payload;
}
try {
Map<String, Object> json = JSON_MAPPER.readValue(payload, Map.class);
for (Map.Entry<String, Object> entry : fields.entrySet()) {
setField(json, entry.getKey(), entry.getValue());
}
for (Map.Entry<String, Object> entry : arrayAppends) {
appendToArray(json, entry.getKey(), entry.getValue());
}
return JSON_MAPPER.writeValueAsString(json);
} catch (Exception e) {
throw new HarnessException("Failed to build payload", e);
}
}
private void setField(Map<String, Object> json, String path, Object value) {
if (StringUtils.isBlank(path)) {
json.put(path, value);
return;
}
String[] parts = path.split("\\.");
Map<String, Object> current = json;
for (int i = 0; i < parts.length - 1; i++) {
String part = parts[i];
Object next = current.get(part);
if (!(next instanceof Map)) {
next = new HashMap<String, Object>();
current.put(part, next);
}
current = (Map<String, Object>) next;
}
current.put(parts[parts.length - 1], value);
}
private void appendToArray(Map<String, Object> json, String path, Object value) {
String[] parts = path.split("\\.");
Map<String, Object> current = json;
for (int i = 0; i < parts.length - 1; i++) {
String part = parts[i];
Object next = current.get(part);
if (!(next instanceof Map)) {
next = new HashMap<String, Object>();
current.put(part, next);
}
current = (Map<String, Object>) next;
}
String arrayKey = parts[parts.length - 1];
Object arrayValue = current.get(arrayKey);
List<Map<String, Object>> array;
if (arrayValue instanceof List) {
array = (List<Map<String, Object>>) arrayValue;
} else {
array = new ArrayList<>();
current.put(arrayKey, array);
}
array.add((Map<String, Object>) value);
}
}
/**
* Response implementation with assertions.
*/
private static class ResponseImpl implements MessageResponse {
private final ReceivedMessage message;
public ResponseImpl(ReceivedMessage message) {
this.message = message;
}
@Override
public MessageResponse andAssertFieldValue(String path, String value) {
String actual = message.extract(path);
if (!Objects.equals(value, actual)) {
throw new AssertionError(String.format("Expected field '%s' to be '%s' but was '%s'", path, value, actual));
}
return this;
}
@Override
public MessageResponse andAssertPresent(String path) {
JsonPathValue extracted = new JsonPathValue(message.extract(path));
if (extracted.asText() == null && !isPresentInJson(path)) {
throw new AssertionError(String.format("Expected field '%s' to be present", path));
}
return this;
}
@Override
public MessageResponse andAssertNotPresent(String path) {
if (isPresentInJson(path)) {
throw new AssertionError(String.format("Expected field '%s' to be absent", path));
}
return this;
}
@Override
public MessageResponse andAssertHeaderValue(String headerName, String value) {
String actual = message.getHeader(headerName);
if (!Objects.equals(value, actual)) {
throw new AssertionError(String.format("Expected header '%s' to be '%s' but was '%s'", headerName, value, actual));
}
return this;
}
@Override
public MessageResponse andAssertBodyContains(String substring) {
if (!message.getBody().contains(substring)) {
throw new AssertionError(String.format("Body does not contain '%s'", substring));
}
return this;
}
@Override
public JsonPathValue extract(String path) {
return new JsonPathValue(message.extract(path));
}
@Override
public <T> T mapTo(Class<T> type) {
return message.mapTo(type);
}
@Override
public cz.moneta.test.harness.support.messaging.ReceivedMessage getMessage() {
return cz.moneta.test.harness.support.messaging.ReceivedMessage.fromMessagingReceivedMessage(message);
}
@Override
public String getBody() {
return message.getBody();
}
@Override
public String getHeader(String name) {
return message.getHeader(name);
}
private boolean isPresentInJson(String path) {
try {
String body = message.getBody();
if (message.getContentType() != MessageContentType.JSON) {
return false;
}
JsonNode node = JSON_MAPPER.readTree(body);
JsonNode target = extractNode(path, node);
return target != null && !target.isMissingNode();
} catch (Exception e) {
return false;
}
}
private JsonNode extractNode(String path, JsonNode node) {
return Arrays.stream(path.split("\\."))
.filter(StringUtils::isNotEmpty)
.reduce(node,
(n, p) -> n.isContainerNode() ? n.get(p) : n,
(n1, n2) -> n1);
}
}
}

View File

@ -1,65 +0,0 @@
package cz.moneta.test.harness.support.messaging;
/**
* Wrapper for extracted JSON path value.
*/
public class JsonPathValue {
private final String value;
public JsonPathValue(String value) {
this.value = value;
}
/**
* Returns the value as String.
*/
public String asText() {
return value;
}
/**
* Returns the value as Boolean.
*/
public Boolean asBoolean() {
if (value == null) {
return null;
}
return Boolean.parseBoolean(value);
}
/**
* Returns the value as Integer.
*/
public Integer asInteger() {
if (value == null) {
return null;
}
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Cannot parse '" + value + "' as Integer", e);
}
}
/**
* Returns the value as Long.
*/
public Long asLong() {
if (value == null) {
return null;
}
try {
return Long.parseLong(value);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Cannot parse '" + value + "' as Long", e);
}
}
/**
* Returns the underlying value.
*/
public String getValue() {
return value;
}
}

View File

@ -1,19 +0,0 @@
package cz.moneta.test.harness.support.messaging;
/**
* Content type of a received message.
*/
public enum MessageContentType {
/**
* JSON content - body is a JSON string, can use dot-path or bracket notation for extraction
*/
JSON,
/**
* XML content - body is an XML string, can use XPath for extraction
*/
XML,
/**
* Raw text content - body is plain text without structured format
*/
RAW_TEXT
}

View File

@ -1,90 +0,0 @@
package cz.moneta.test.harness.support.messaging;
/**
* Response from a messaging operation (send/receive).
* Provides fluent assertion API for received messages.
*/
public interface MessageResponse {
/**
* Asserts that a field has the expected value.
*
* @param path the field path (JSON dot-path for JSON, XPath for XML)
* @param value the expected value
* @return this for method chaining
*/
MessageResponse andAssertFieldValue(String path, String value);
/**
* Asserts that a field is present in the message.
*
* @param path the field path
* @return this for method chaining
*/
MessageResponse andAssertPresent(String path);
/**
* Asserts that a field is NOT present in the message.
*
* @param path the field path
* @return this for method chaining
*/
MessageResponse andAssertNotPresent(String path);
/**
* Asserts that a header has the expected value.
*
* @param headerName the header name
* @param value the expected value
* @return this for method chaining
*/
MessageResponse andAssertHeaderValue(String headerName, String value);
/**
* Asserts that the body contains the given substring.
* Useful for raw text or EBCDIC/UTF-8 encoded messages.
*
* @param substring the expected substring
* @return this for method chaining
*/
MessageResponse andAssertBodyContains(String substring);
/**
* Extracts a value from the message.
*
* @param path the path expression
* @return JsonPathValue for further assertion
*/
JsonPathValue extract(String path);
/**
* Deserializes the message body into a Java object.
*
* @param type the target class
* @param <T> the target type
* @return the deserialized object
*/
<T> T mapTo(Class<T> type);
/**
* Returns the received message.
*
* @return the message
*/
ReceivedMessage getMessage();
/**
* Returns the message body.
*
* @return the body
*/
String getBody();
/**
* Returns a header value.
*
* @param name the header name
* @return the header value or null
*/
String getHeader(String name);
}

View File

@ -0,0 +1,193 @@
package cz.moneta.test.harness.support.messaging;
import cz.moneta.test.harness.endpoints.messaging.MessagingEndpoint;
import cz.moneta.test.harness.messaging.model.MqMessageFormat;
import cz.moneta.test.harness.messaging.model.ReceivedMessage;
import cz.moneta.test.harness.support.util.FileReader;
import cz.moneta.test.harness.support.util.Template;
import org.junit.jupiter.api.Assertions;
import java.time.Duration;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
public final class MessagingRequest {
private static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(30);
private final MessagingEndpoint endpoint;
private String destinationName;
private String key;
private String payload;
private Duration timeout = DEFAULT_TIMEOUT;
private final Map<String, String> headers = new LinkedHashMap<>();
private MqMessageFormat formatOverride;
private Predicate<ReceivedMessage> receiveFilter;
private ReceivedMessage receivedMessage;
private boolean receivePending;
private Mode mode = Mode.UNSET;
private MessagingRequest(MessagingEndpoint endpoint) {
this.endpoint = endpoint;
}
public static MessagingRequest builder(MessagingEndpoint endpoint) {
return new MessagingRequest(endpoint);
}
public MessagingRequest to(String destinationName) {
this.destinationName = destinationName;
this.mode = Mode.SEND;
resetReceiveState();
return this;
}
public MessagingRequest from(String destinationName) {
this.destinationName = destinationName;
this.mode = Mode.RECEIVE;
resetReceiveState();
return this;
}
public MessagingRequest withKey(String key) {
this.key = key;
return this;
}
public MessagingRequest withPayload(String payload) {
this.payload = payload;
return this;
}
public MessagingRequest withPayloadFromTemplate(Template template) {
this.payload = template.render();
return this;
}
public MessagingRequest withPayloadFromFile(String path) {
this.payload = FileReader.readFileFromResources(path);
return this;
}
public MessagingRequest withHeader(String key, String value) {
this.headers.put(key, value);
return this;
}
public MessagingRequest withTraceparent(String value) {
return withHeader("traceparent", value);
}
public MessagingRequest withRequestID(String value) {
return withHeader("requestID", value);
}
public MessagingRequest withActivityID(String value) {
return withHeader("activityID", value);
}
public MessagingRequest withSourceCodebookId(String value) {
return withHeader("sourceCodebookId", value);
}
public MessagingRequest asJson() {
this.formatOverride = MqMessageFormat.JSON;
return this;
}
public MessagingRequest asXml() {
this.formatOverride = MqMessageFormat.XML;
return this;
}
public MessagingRequest asEbcdic() {
this.formatOverride = MqMessageFormat.EBCDIC_870;
return this;
}
public MessagingRequest asUtf8() {
this.formatOverride = MqMessageFormat.UTF8_1208;
return this;
}
public MessagingRequest withTimeout(long value, TimeUnit unit) {
this.timeout = Duration.ofMillis(unit.toMillis(value));
if (receivePending && receivedMessage == null) {
doReceive();
}
return this;
}
public MessagingRequest send() {
ensureMode(Mode.SEND);
if (payload == null) {
throw new IllegalStateException("Message payload must be provided before send()");
}
endpoint.send(destinationName, key, payload, formatOverride, headers);
return this;
}
public MessagingRequest receiveWhere(Predicate<ReceivedMessage> filter) {
ensureMode(Mode.RECEIVE);
this.receiveFilter = filter;
this.receivePending = true;
this.receivedMessage = null;
return this;
}
public MessagingRequest andAssertFieldValue(String expression, String expectedValue) {
ReceivedMessage message = getReceivedMessage();
Assertions.assertEquals(expectedValue,
message.extract(expression),
String.format("Unexpected message field value for '%s'. Message body: %s", expression, message.getBody()));
return this;
}
public String extract(String expression) {
return getReceivedMessage().extract(expression);
}
public ReceivedMessage getReceivedMessage() {
ensureMode(Mode.RECEIVE);
if (receivedMessage == null) {
doReceive();
}
return receivedMessage;
}
private void doReceive() {
if (!receivePending) {
receiveFilter = msg -> true;
receivePending = true;
}
receivedMessage = endpoint.receive(
destinationName,
Optional.ofNullable(receiveFilter).orElse(msg -> true),
timeout,
formatOverride
);
receivePending = false;
}
private void ensureMode(Mode requiredMode) {
if (this.mode != requiredMode) {
throw new IllegalStateException("Messaging request is not in " + requiredMode + " mode");
}
}
private void resetReceiveState() {
this.receiveFilter = null;
this.receivedMessage = null;
this.receivePending = false;
}
private enum Mode {
UNSET,
SEND,
RECEIVE
}
}

View File

@ -1,23 +0,0 @@
package cz.moneta.test.harness.support.messaging;
/**
* Message format for IBM MQ messages.
*/
public enum MqMessageFormat {
/**
* JSON format - JMS TextMessage with plain JSON string (UTF-8 default)
*/
JSON,
/**
* XML format - JMS TextMessage with XML string (UTF-8 default)
*/
XML,
/**
* EBCDIC format - JMS BytesMessage with EBCDIC IBM-870 encoding (CZ/SK mainframe)
*/
EBCDIC_870,
/**
* UTF-8 format - JMS BytesMessage with UTF-8 IBM CCSID 1208 encoding
*/
UTF8_1208
}

View File

@ -1,304 +0,0 @@
package cz.moneta.test.harness.support.messaging;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import org.apache.commons.lang3.StringUtils;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.w3c.dom.Document;
/**
* Represents a received message from a messaging system (IBM MQ or Kafka).
* <p>
* Provides unified API for accessing message content regardless of source system.
* Body is always normalized to a String, with content type detection for proper extraction.
* </p>
*/
public class ReceivedMessage {
private static final ObjectMapper JSON_MAPPER = new ObjectMapper();
private static final XmlMapper XML_MAPPER = new XmlMapper();
private final String body;
private final MessageContentType contentType;
private final Map<String, String> headers;
private final long timestamp;
private final String source;
private final String key;
private ReceivedMessage(Builder builder) {
this.body = builder.body;
this.contentType = builder.contentType;
this.headers = builder.headers != null ? Collections.unmodifiableMap(new HashMap<>(builder.headers)) : Collections.emptyMap();
this.timestamp = builder.timestamp;
this.source = builder.source;
this.key = builder.key;
}
/**
* Creates a new builder for ReceivedMessage.
*/
public static Builder builder() {
return new Builder();
}
/**
* Extracts a value from the message body using JSON dot-path or bracket notation.
* <p>
* Supports paths like:
* <ul>
* <li>{@code "field"} - top-level field</li>
* <li>{@code "parent.child"} - nested field</li>
* <li>{@code "items[0]"} - array element</li>
* <li>{@code "items[0].name"} - nested field in array element</li>
* </ul>
* </p>
*
* @param path the JSON path expression
* @return the extracted value as JsonNode
*/
public JsonNode extractJson(String path) {
if (contentType != MessageContentType.JSON) {
throw new IllegalStateException("JSON extraction is only supported for JSON content type, got: " + contentType);
}
try {
JsonNode rootNode = JSON_MAPPER.readTree(body);
return extractNode(path, rootNode);
} catch (IOException e) {
throw new RuntimeException("Failed to parse JSON body: " + body, e);
}
}
/**
* Extracts a value from XML message body using XPath expression.
*
* @param xpath the XPath expression
* @return the extracted value as String
*/
public String extractXml(String xpath) {
if (contentType != MessageContentType.XML) {
throw new IllegalStateException("XML extraction is only supported for XML content type, got: " + contentType);
}
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.parse(new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8)));
XPath xPath = XPathFactory.newInstance().newXPath();
return xPath.evaluate(xpath, doc);
} catch (XPathExpressionException e) {
throw new RuntimeException("Failed to evaluate XPath: " + xpath, e);
} catch (Exception e) {
throw new RuntimeException("Failed to parse XML body: " + body, e);
}
}
/**
* Extracts a value from the message body. Auto-detects content type and uses appropriate extraction method.
*
* @param expression the path expression (JSON path for JSON, XPath for XML)
* @return the extracted value as String
*/
public String extract(String expression) {
return switch (contentType) {
case JSON -> extractJson(expression).asText();
case XML -> extractXml(expression);
case RAW_TEXT -> body;
};
}
/**
* Returns the message body as a String.
*
* @return the body content
*/
public String getBody() {
return body;
}
/**
* Returns the message content type.
*
* @return the content type
*/
public MessageContentType getContentType() {
return contentType;
}
/**
* Returns a header value by name.
*
* @param name the header name
* @return the header value, or null if not present
*/
public String getHeader(String name) {
return headers.get(name);
}
/**
* Returns all headers.
*
* @return unmodifiable map of headers
*/
public Map<String, String> getHeaders() {
return headers;
}
/**
* Returns the message timestamp.
*
* @return timestamp in milliseconds
*/
public long getTimestamp() {
return timestamp;
}
/**
* Returns the source (topic name for Kafka, queue name for IBM MQ).
*
* @return the source name
*/
public String getSource() {
return source;
}
/**
* Returns the message key (Kafka only, null for IBM MQ).
*
* @return the message key or null
*/
public String getKey() {
return key;
}
/**
* Deserializes the message body into a Java object.
* <p>
* For JSON content: uses Jackson ObjectMapper.readValue
* For XML content: uses Jackson XmlMapper
* </p>
*
* @param type the target class
* @param <T> the target type
* @return the deserialized object
*/
public <T> T mapTo(Class<T> type) {
try {
if (contentType == MessageContentType.XML) {
return XML_MAPPER.readValue(body, type);
} else {
return JSON_MAPPER.readValue(body, type);
}
} catch (IOException e) {
throw new RuntimeException("Failed to deserialize message body to " + type.getName(), e);
}
}
/**
* Builder for ReceivedMessage.
*/
public static class Builder {
private String body;
private MessageContentType contentType = MessageContentType.JSON;
private Map<String, String> headers;
private long timestamp = System.currentTimeMillis();
private String source;
private String key;
public Builder body(String body) {
this.body = body;
return this;
}
public Builder contentType(MessageContentType contentType) {
this.contentType = contentType;
return this;
}
public Builder headers(Map<String, String> headers) {
this.headers = headers;
return this;
}
public Builder timestamp(long timestamp) {
this.timestamp = timestamp;
return this;
}
public Builder source(String source) {
this.source = source;
return this;
}
public Builder key(String key) {
this.key = key;
return this;
}
public ReceivedMessage build() {
return new ReceivedMessage(this);
}
}
/**
* Extracts a node from JSON using dot/bracket notation.
*/
private static JsonNode extractNode(String path, JsonNode rootNode) {
Pattern arrayPattern = Pattern.compile("(.*?)\\[([0-9]+)\\]");
return Arrays.stream(path.split("\\."))
.filter(StringUtils::isNotEmpty)
.reduce(rootNode,
(node, part) -> {
Matcher matcher = arrayPattern.matcher(part);
if (matcher.find()) {
return node.path(matcher.group(1)).path(Integer.parseInt(matcher.group(2)));
} else {
return node.path(part);
}
},
(n1, n2) -> n1);
}
/**
* Converts a cz.moneta.test.harness.messaging.ReceivedMessage to this class.
*
* @param other the message to convert
* @return converted message
*/
public static ReceivedMessage fromMessagingReceivedMessage(
cz.moneta.test.harness.messaging.ReceivedMessage other) {
if (other == null) {
return null;
}
cz.moneta.test.harness.support.messaging.MessageContentType contentType =
switch (other.getContentType()) {
case JSON -> cz.moneta.test.harness.support.messaging.MessageContentType.JSON;
case XML -> cz.moneta.test.harness.support.messaging.MessageContentType.XML;
case RAW_TEXT -> cz.moneta.test.harness.support.messaging.MessageContentType.RAW_TEXT;
};
return builder()
.body(other.getBody())
.contentType(contentType)
.headers(other.getHeaders())
.timestamp(other.getTimestamp())
.source(other.getSource())
.key(other.getKey())
.build();
}
}

View File

@ -1,568 +0,0 @@
package cz.moneta.test.harness.support.messaging.kafka;
import java.time.Duration;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import cz.moneta.test.harness.endpoints.kafka.KafkaEndpoint;
import cz.moneta.test.harness.support.util.FileReader;
/**
* Fluent builder for creating and sending Kafka messages.
* <p>
* Usage:
* <pre>{@code
* harness.withKafka()
* .toTopic("order-events")
* .withKey("order-123")
* .withPayload("{\"orderId\": \"123\", \"status\": \"CREATED\"}")
* .withTraceparent("00-traceId-spanId-01")
* .send();
* }</pre>
* </p>
*/
public class KafkaRequest {
private static final ObjectMapper MAPPER = new ObjectMapper();
private static final Pattern ARRAY_NODE_PATTERN = Pattern.compile("(.*?)\\[([0-9]*?)\\]");
private KafkaRequest() {
}
/**
* Creates a new Kafka request builder.
*/
public static KafkaBuilder builder(KafkaEndpoint endpoint) {
return new KafkaRequestBuilder(endpoint);
}
/**
* Phase 1: Select direction (send or receive).
*/
public interface KafkaBuilder {
/**
* Specifies the target topic for sending.
*
* @param topic Kafka topic name
* @return KafkaPayloadPhase for payload configuration
*/
KafkaPayloadPhase toTopic(String topic);
/**
* Specifies the source topic for receiving.
*
* @param topic Kafka topic name
* @return KafkaReceiveFilterPhase for filter configuration
*/
KafkaReceiveFilterPhase fromTopic(String topic);
}
/**
* Phase 2a: Configure payload and headers for sending.
*/
public interface KafkaPayloadPhase {
/**
* Sets the message key.
*
* @param key Message key
* @return this for chaining
*/
KafkaPayloadPhase withKey(String key);
/**
* Sets the payload as JSON string.
*
* @param json JSON payload
* @return this for chaining
*/
KafkaPayloadPhase withPayload(String json);
/**
* Loads payload from a file.
*
* @param path Path to JSON file in resources
* @return this for chaining
*/
KafkaPayloadPhase withPayloadFromFile(String path);
/**
* Loads payload from a template file and renders it.
*
* @param path Path to template file
* @param variables Variables for template rendering
* @return this for chaining
*/
KafkaPayloadPhase withPayloadFromTemplate(String path, Map<String, Object> variables);
/**
* Adds a field to the JSON payload.
*
* @param fieldName Field name
* @param value Field value
* @return this for chaining
*/
KafkaPayloadPhase addField(String fieldName, Object value);
/**
* Adds a field to a nested path in the JSON payload.
*
* @param path Path to parent node (e.g., "items[1].details")
* @param fieldName Field name to add
* @param value Field value
* @return this for chaining
*/
KafkaPayloadPhase addField(String path, String fieldName, Object value);
/**
* Appends a value to an array in the JSON payload.
*
* @param path Path to array (e.g., "items")
* @param value Value to append
* @return this for chaining
*/
KafkaPayloadPhase appendToArray(String path, Object value);
/**
* Adds W3C Trace Context traceparent header.
*
* @param value Traceparent value (e.g., "00-traceId-spanId-01")
* @return this for chaining
*/
KafkaPayloadPhase withTraceparent(String value);
/**
* Adds requestID header.
*
* @param value Request ID
* @return this for chaining
*/
KafkaPayloadPhase withRequestID(String value);
/**
* Adds activityID header.
*
* @param value Activity ID
* @return this for chaining
*/
KafkaPayloadPhase withActivityID(String value);
/**
* Adds sourceCodebookId header.
*
* @param value Source codebook ID
* @return this for chaining
*/
KafkaPayloadPhase withSourceCodebookId(String value);
/**
* Adds a custom header.
*
* @param key Header name
* @param value Header value
* @return this for chaining
*/
KafkaPayloadPhase withHeader(String key, String value);
/**
* Sends the message.
*/
void send();
}
/**
* Phase 2b: Configure filter for receiving.
*/
public interface KafkaReceiveFilterPhase {
/**
* Specifies the filter predicate for receiving messages.
*
* @param filter Predicate to filter messages
* @return KafkaAwaitingPhase for timeout configuration
*/
KafkaAwaitingPhase receiveWhere(Predicate<ReceivedMessage> filter);
}
/**
* Phase 3: Configure timeout.
*/
public interface KafkaAwaitingPhase {
/**
* Sets the timeout for waiting for a message.
*
* @param duration Timeout duration
* @param unit Time unit
* @return MessageResponse for assertions
*/
MessageResponse withTimeout(long duration, TimeUnit unit);
}
/**
* Internal implementation class.
*/
private static class KafkaRequestBuilder implements
KafkaBuilder,
KafkaPayloadPhase, KafkaReceiveFilterPhase, KafkaAwaitingPhase,
MessageResponse {
private final KafkaEndpoint endpoint;
// Send configuration
private String topic;
private String key;
private String payload;
private final Map<String, String> headers = new HashMap<>();
// Receive configuration
private Predicate<ReceivedMessage> filter;
private Duration timeout;
// Response (after receive)
private List<ReceivedMessage> messages;
public KafkaRequestBuilder(KafkaEndpoint endpoint) {
this.endpoint = endpoint;
}
// === Send phase ===
@Override
public KafkaPayloadPhase toTopic(String topic) {
this.topic = topic;
return this;
}
@Override
public KafkaPayloadPhase withKey(String key) {
this.key = key;
return this;
}
@Override
public KafkaPayloadPhase withPayload(String json) {
this.payload = json;
return this;
}
@Override
public KafkaPayloadPhase withPayloadFromFile(String path) {
this.payload = FileReader.readFileFromResources(path);
return this;
}
@Override
public KafkaPayloadPhase withPayloadFromTemplate(String path, Map<String, Object> variables) {
String templateContent = FileReader.readFileFromResources(path);
cz.moneta.test.harness.support.util.Template template =
new cz.moneta.test.harness.support.util.Template(templateContent);
if (variables != null) {
variables.forEach((k, v) -> template.set(k, v != null ? v.toString() : ""));
}
this.payload = template.render();
return this;
}
@Override
public KafkaPayloadPhase addField(String fieldName, Object value) {
return addField("", fieldName, value);
}
@Override
public KafkaPayloadPhase addField(String path, String fieldName, Object value) {
try {
if (payload == null) {
payload = "{}";
}
JsonNode rootNode = MAPPER.readTree(payload);
JsonNode targetNode = extractNode(path, rootNode);
JsonNode newNode = MAPPER.valueToTree(value);
((ObjectNode) targetNode).set(fieldName, newNode);
payload = MAPPER.writeValueAsString(rootNode);
} catch (Exception e) {
throw new IllegalStateException("Failed to add field '" + fieldName + "' at path '" + path + "': " + e.getMessage(), e);
}
return this;
}
@Override
public KafkaPayloadPhase appendToArray(String path, Object value) {
try {
if (payload == null) {
payload = "{}";
}
JsonNode rootNode = MAPPER.readTree(payload);
JsonNode targetNode = extractNode(path, rootNode);
JsonNode newNode = MAPPER.valueToTree(value);
if (targetNode.isArray()) {
((ArrayNode) targetNode).add(newNode);
} else {
throw new IllegalStateException("Path '" + path + "' does not point to an array");
}
payload = MAPPER.writeValueAsString(rootNode);
} catch (Exception e) {
throw new IllegalStateException("Failed to append to array at path '" + path + "': " + e.getMessage(), e);
}
return this;
}
@Override
public KafkaPayloadPhase withTraceparent(String value) {
return withHeader("traceparent", value);
}
@Override
public KafkaPayloadPhase withRequestID(String value) {
return withHeader("requestID", value);
}
@Override
public KafkaPayloadPhase withActivityID(String value) {
return withHeader("activityID", value);
}
@Override
public KafkaPayloadPhase withSourceCodebookId(String value) {
return withHeader("sourceCodebookId", value);
}
@Override
public KafkaPayloadPhase withHeader(String key, String value) {
this.headers.put(key, value);
return this;
}
@Override
public void send() {
if (topic == null) {
throw new IllegalStateException("Topic not specified. Call toTopic() first.");
}
if (payload == null) {
throw new IllegalStateException("Payload not specified. Call withPayload() or withPayloadFromFile() first.");
}
endpoint.send(topic, key, payload, headers);
}
// === Receive phase ===
@Override
public KafkaReceiveFilterPhase fromTopic(String topic) {
this.topic = topic;
return this;
}
@Override
public KafkaAwaitingPhase receiveWhere(Predicate<ReceivedMessage> filter) {
this.filter = filter;
return this;
}
@Override
public MessageResponse withTimeout(long duration, TimeUnit unit) {
this.timeout = Duration.of(duration, unit.toChronoUnit());
messages = endpoint.receive(topic, filter, timeout);
return this;
}
// === MessageResponse implementation ===
@Override
public MessageResponse andAssertFieldValue(String path, String value) {
if (messages == null || messages.isEmpty()) {
throw new IllegalStateException("No message received to assert on");
}
ReceivedMessage msg = messages.get(0);
String actual = msg.extract(path);
if (!Objects.equals(value, actual)) {
throw new AssertionError(
String.format("Field '%s' has value '%s', expected '%s'", path, actual, value));
}
return this;
}
@Override
public MessageResponse andAssertPresent(String path) {
if (messages == null || messages.isEmpty()) {
throw new IllegalStateException("No message received to assert on");
}
ReceivedMessage msg = messages.get(0);
JsonNode node = msg.extractJson(path);
if (node.isMissingNode()) {
throw new AssertionError("Field '" + path + "' is missing");
}
return this;
}
@Override
public MessageResponse andAssertNotPresent(String path) {
if (messages == null || messages.isEmpty()) {
throw new IllegalStateException("No message received to assert on");
}
ReceivedMessage msg = messages.get(0);
JsonNode node = msg.extractJson(path);
if (!node.isMissingNode()) {
throw new AssertionError("Field '" + path + "' is present with value: " + node.asText());
}
return this;
}
@Override
public MessageResponse andAssertHeaderValue(String headerName, String value) {
if (messages == null || messages.isEmpty()) {
throw new IllegalStateException("No message received to assert on");
}
ReceivedMessage msg = messages.get(0);
String actual = msg.getHeader(headerName);
if (!Objects.equals(value, actual)) {
throw new AssertionError(
String.format("Header '%s' has value '%s', expected '%s'", headerName, actual, value));
}
return this;
}
@Override
public MessageResponse andAssertBodyContains(String substring) {
if (messages == null || messages.isEmpty()) {
throw new IllegalStateException("No message received to assert on");
}
ReceivedMessage msg = messages.get(0);
String body = msg.getBody();
if (body == null || !body.contains(substring)) {
throw new AssertionError(
String.format("Body does not contain '%s'. Actual body: %s", substring, body));
}
return this;
}
@Override
public net.javacrumbs.jsonunit.assertj.JsonAssert.ConfigurableJsonAssert andAssertWithAssertJ() {
if (messages == null || messages.isEmpty()) {
throw new IllegalStateException("No message received to assert on");
}
return net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson(messages.get(0).getBody());
}
@Override
public JsonPathValue extract(String path) {
if (messages == null || messages.isEmpty()) {
throw new IllegalStateException("No message received to extract from");
}
JsonNode node = messages.get(0).extractJson(path);
return new JsonPathValueImpl(node);
}
@Override
public <T> T mapTo(Class<T> type) {
if (messages == null || messages.isEmpty()) {
throw new IllegalStateException("No message received to map");
}
return messages.get(0).mapTo(type);
}
@Override
public ReceivedMessage getMessage() {
if (messages == null || messages.isEmpty()) {
throw new IllegalStateException("No message received");
}
return messages.get(0);
}
@Override
public String getBody() {
if (messages == null || messages.isEmpty()) {
return null;
}
return messages.get(0).getBody();
}
@Override
public String getHeader(String name) {
if (messages == null || messages.isEmpty()) {
return null;
}
return messages.get(0).getHeader(name);
}
// === Helper methods ===
private JsonNode extractNode(String path, JsonNode rootNode) {
if (StringUtils.isBlank(path)) {
return rootNode;
}
return Arrays.stream(path.split("\\."))
.filter(StringUtils::isNotEmpty)
.reduce(rootNode,
(r, p) -> {
Matcher matcher = ARRAY_NODE_PATTERN.matcher(p);
if (matcher.find()) {
return r.path(matcher.group(1)).path(Integer.valueOf(matcher.group(2)));
} else {
return r.path(p);
}
},
(j1, j2) -> j1);
}
}
/**
* Implementation of JsonPathValue.
*/
private static class JsonPathValueImpl implements MessageResponse.JsonPathValue {
private final JsonNode node;
public JsonPathValueImpl(JsonNode node) {
this.node = node;
}
@Override
public String asText() {
if (node == null || node.isMissingNode()) {
return null;
}
return node.asText();
}
@Override
public Integer asInt() {
if (node == null || node.isMissingNode()) {
return null;
}
return node.asInt();
}
@Override
public Long asLong() {
if (node == null || node.isMissingNode()) {
return null;
}
return node.asLong();
}
@Override
public Boolean asBoolean() {
if (node == null || node.isMissingNode()) {
return null;
}
return node.asBoolean();
}
@Override
public boolean isMissing() {
return node == null || node.isMissingNode();
}
}
}

View File

@ -1,19 +0,0 @@
package cz.moneta.test.harness.support.messaging.kafka;
/**
* Enum representing the content type of a received message.
*/
public enum MessageContentType {
/**
* JSON content - parsed with Jackson ObjectMapper
*/
JSON,
/**
* XML content - can be parsed with XPath or Jackson XmlMapper
*/
XML,
/**
* Raw text content - not parsed, returned as-is
*/
RAW_TEXT
}

View File

@ -1,139 +0,0 @@
package cz.moneta.test.harness.support.messaging.kafka;
import net.javacrumbs.jsonunit.assertj.JsonAssert;
/**
* Interface representing the response from a message receive operation.
* Provides fluent assertion methods for testing received messages.
*/
public interface MessageResponse {
/**
* Asserts that a field has the expected value.
*
* @param path JSON path or XPath
* @param value expected value as string
* @return this for chaining
*/
MessageResponse andAssertFieldValue(String path, String value);
/**
* Asserts that a field is present.
*
* @param path JSON path or XPath
* @return this for chaining
*/
MessageResponse andAssertPresent(String path);
/**
* Asserts that a field is not present.
*
* @param path JSON path or XPath
* @return this for chaining
*/
MessageResponse andAssertNotPresent(String path);
/**
* Asserts that a header has the expected value.
*
* @param headerName name of the header
* @param value expected value
* @return this for chaining
*/
MessageResponse andAssertHeaderValue(String headerName, String value);
/**
* Asserts that the body contains a substring.
* Useful for EBCDIC/UTF-8 raw text messages.
*
* @param substring expected substring
* @return this for chaining
*/
MessageResponse andAssertBodyContains(String substring);
/**
* Returns AssertJ JsonAssert for complex assertions.
*
* @return JsonAssert instance
*/
JsonAssert.ConfigurableJsonAssert andAssertWithAssertJ();
/**
* Extracts a value from the message body.
*
* @param path JSON path or XPath
* @return JsonPathValue wrapper for further conversion
*/
JsonPathValue extract(String path);
/**
* Deserializes the message body to a Java object.
*
* @param type the target type
* @param <T> the target type
* @return deserialized object
*/
<T> T mapTo(Class<T> type);
/**
* Gets the underlying received message.
*
* @return the received message
*/
ReceivedMessage getMessage();
/**
* Gets the message body as string.
*
* @return body string
*/
String getBody();
/**
* Gets a header value.
*
* @param name header name
* @return header value or null
*/
String getHeader(String name);
/**
* Interface for extracted path values.
*/
interface JsonPathValue {
/**
* Converts to string.
*
* @return string value
*/
String asText();
/**
* Converts to integer.
*
* @return integer value
*/
Integer asInt();
/**
* Converts to long.
*
* @return long value
*/
Long asLong();
/**
* Converts to boolean.
*
* @return boolean value
*/
Boolean asBoolean();
/**
* Checks if value is missing/null.
*
* @return true if value is missing
*/
boolean isMissing();
}
}

View File

@ -1,365 +0,0 @@
package cz.moneta.test.harness.support.messaging.kafka;
import java.io.StringReader;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import org.apache.commons.lang3.StringUtils;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import net.javacrumbs.jsonunit.assertj.JsonAssert;
/**
* Represents a received message from a messaging system (Kafka or IBM MQ).
* Provides unified API for extracting data regardless of the source system.
*/
public class ReceivedMessage {
private static final ObjectMapper JSON_MAPPER = new ObjectMapper();
private static final XmlMapper XML_MAPPER = new XmlMapper();
private static final Pattern ARRAY_NODE_PATTERN = Pattern.compile("(.*?)\\[([0-9]*?)\\]");
private final String body;
private final MessageContentType contentType;
private final Map<String, String> headers;
private final long timestamp;
private final String source;
private final String key;
private ReceivedMessage(Builder builder) {
this.body = builder.body;
this.contentType = builder.contentType;
this.headers = builder.headers != null ? new HashMap<>(builder.headers) : new HashMap<>();
this.timestamp = builder.timestamp;
this.source = builder.source;
this.key = builder.key;
}
/**
* Creates a new builder for ReceivedMessage.
*/
public static Builder builder() {
return new Builder();
}
/**
* Extracts value using JSON path (dot/bracket notation).
* For XML content, automatically converts to XPath evaluation.
*
* @param path JSON path (e.g., "items[0].sku") or XPath for XML
* @return JsonNode representing the extracted value
*/
public JsonNode extractJson(String path) {
if (body == null) {
return null;
}
if (contentType == MessageContentType.XML) {
// For XML, try XPath first, then fall back to JSON path
try {
return extractAsXml(path);
} catch (Exception e) {
// Fall through to JSON parsing
}
}
try {
JsonNode rootNode = JSON_MAPPER.readTree(body);
return extractNode(path, rootNode);
} catch (Exception e) {
throw new RuntimeException("Failed to extract JSON path '" + path + "' from body: " + e.getMessage(), e);
}
}
/**
* Extracts value using XPath expression (for XML messages).
*
* @param xpath XPath expression (e.g., "/response/balance")
* @return String value of the extracted node
*/
public String extractXml(String xpath) {
if (body == null) {
return null;
}
try {
Document doc = XML_MAPPER.readTree(body).isMissingNode() ? null : XML_MAPPER.readTree(body).isObject()
? parseXmlToDocument(body)
: parseXmlToDocument(body);
if (doc == null) {
doc = parseXmlToDocument(body);
}
XPath xPath = XPathFactory.newInstance().newXPath();
NodeList nodes = (NodeList) xPath.evaluate(xpath, doc, XPathConstants.NODESET);
if (nodes.getLength() == 0) {
return null;
}
return nodes.item(0).getTextContent();
} catch (Exception e) {
throw new RuntimeException("Failed to evaluate XPath '" + xpath + "' on body: " + e.getMessage(), e);
}
}
/**
* Universal extract method - auto-detects content type and evaluates expression accordingly.
*
* @param expression JSON path for JSON content, XPath for XML content
* @return String value of the extracted node
*/
public String extract(String expression) {
if (body == null) {
return null;
}
return switch (contentType) {
case JSON -> extractJson(expression).asText();
case XML -> extractXml(expression);
case RAW_TEXT -> body;
};
}
/**
* Deserializes the message body to a Java object.
*
* @param type the target type
* @param <T> the target type
* @return deserialized object
*/
public <T> T mapTo(Class<T> type) {
if (body == null) {
return null;
}
try {
if (contentType == MessageContentType.JSON) {
return JSON_MAPPER.readValue(body, type);
} else if (contentType == MessageContentType.XML) {
return XML_MAPPER.readValue(body, type);
} else {
throw new IllegalStateException("Cannot deserialize RAW_TEXT to " + type.getName());
}
} catch (Exception e) {
throw new RuntimeException("Failed to deserialize body to " + type.getName() + ": " + e.getMessage(), e);
}
}
/**
* Deserializes the message body to a list of objects.
*
* @param type the element type
* @param <T> the element type
* @return list of deserialized objects
*/
public <T> List<T> mapToList(Class<T> type) {
if (body == null) {
return Collections.emptyList();
}
try {
if (contentType == MessageContentType.JSON) {
return JSON_MAPPER.readValue(body, new TypeReference<List<T>>() {});
} else {
throw new IllegalStateException("Cannot deserialize to list for content type " + contentType);
}
} catch (Exception e) {
throw new RuntimeException("Failed to deserialize body to List<" + type.getName() + ">: " + e.getMessage(), e);
}
}
/**
* Gets the message body as string.
*/
public String getBody() {
return body;
}
/**
* Gets the message key (Kafka) or null (IBM MQ).
*/
public String getKey() {
return key;
}
/**
* Gets header value by name (Kafka header or JMS property).
*/
public String getHeader(String name) {
return headers.get(name);
}
/**
* Gets all headers.
*/
public Map<String, String> getHeaders() {
return Collections.unmodifiableMap(headers);
}
/**
* Gets the message timestamp.
*/
public long getTimestamp() {
return timestamp;
}
/**
* Gets the source (topic name for Kafka, queue name for IBM MQ).
*/
public String getSource() {
return source;
}
/**
* Gets the content type.
*/
public MessageContentType getContentType() {
return contentType;
}
/**
* Creates JsonAssert for AssertJ-style assertions on the message body.
*/
public JsonAssert.ConfigurableJsonAssert andAssertWithAssertJ() {
if (body == null) {
throw new IllegalStateException("Cannot assert on null body");
}
return net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson(body);
}
private JsonNode extractNode(String path, JsonNode rootNode) {
if (StringUtils.isBlank(path)) {
return rootNode;
}
return Arrays.stream(path.split("\\."))
.filter(StringUtils::isNotEmpty)
.reduce(rootNode,
(r, p) -> {
Matcher matcher = ARRAY_NODE_PATTERN.matcher(p);
if (matcher.find()) {
return r.path(matcher.group(1)).path(Integer.valueOf(matcher.group(2)));
} else {
return r.path(p);
}
},
(j1, j2) -> j1);
}
private JsonNode extractAsXml(String path) throws Exception {
// Try XPath first
try {
String value = extractXml(path);
if (value != null) {
return JSON_MAPPER.valueToTree(value);
}
} catch (Exception e) {
// Fall back to JSON path on XML body
}
return extractNode(path, XML_MAPPER.readTree(body));
}
private Document parseXmlToDocument(String xml) throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature(javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING, true);
DocumentBuilder builder = factory.newDocumentBuilder();
return builder.parse(new InputSource(new StringReader(xml)));
}
/**
* Builder for ReceivedMessage.
*/
public static class Builder {
private String body;
private MessageContentType contentType = MessageContentType.JSON;
private Map<String, String> headers = new HashMap<>();
private long timestamp = System.currentTimeMillis();
private String source;
private String key;
public Builder body(String body) {
this.body = body;
return this;
}
public Builder contentType(MessageContentType contentType) {
this.contentType = contentType;
return this;
}
public Builder headers(Map<String, String> headers) {
this.headers = headers;
return this;
}
public Builder timestamp(long timestamp) {
this.timestamp = timestamp;
return this;
}
public Builder source(String source) {
this.source = source;
return this;
}
public Builder key(String key) {
this.key = key;
return this;
}
public ReceivedMessage build() {
return new ReceivedMessage(this);
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ReceivedMessage that = (ReceivedMessage) o;
return timestamp == that.timestamp &&
Objects.equals(body, that.body) &&
contentType == that.contentType &&
Objects.equals(headers, that.headers) &&
Objects.equals(source, that.source) &&
Objects.equals(key, that.key);
}
@Override
public int hashCode() {
return Objects.hash(body, contentType, headers, timestamp, source, key);
}
@Override
public String toString() {
return "ReceivedMessage{" +
"body='" + body + '\'' +
", contentType=" + contentType +
", headers=" + headers +
", timestamp=" + timestamp +
", source='" + source + '\'' +
", key='" + key + '\'' +
'}';
}
}

View File

@ -14,7 +14,7 @@ public class Template {
}
public Template(String templateString) {
this.template = new ST(templateString);
this.template = new ST(templateString, '$', '$');
}
public Template set(String variable, String value) {

7
tests/.checkstyle Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<fileset-config file-format-version="1.2.0" simple-config="true" sync-formatter="false">
<fileset name="all" enabled="true" check-config-name="Google Checks" local="false">
<file-match-pattern match-pattern="." include-pattern="true"/>
</fileset>
</fileset-config>

View File

@ -1,77 +1,95 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>cz.moneta.test</groupId>
<artifactId>tests</artifactId>
<version>2.29-SNAPSHOT</version>
<properties>
<harness.version>7.55-SNAPSHOT</harness.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<junit.platform.version>1.5.1</junit.platform.version>
</properties>
<dependencies>
<dependency>
<groupId>cz.moneta.test</groupId>
<artifactId>harness</artifactId>
<version>${harness.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-csv</artifactId>
<version>2.16.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.16.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc_auth</artifactId>
<version>8.2.0.x86</version>
<type>dll</type>
</dependency>
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>15.0</version>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-reporting</artifactId>
<version>${junit.platform.version}</version>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-console</artifactId>
<version>${junit.platform.version}</version>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.26</version>
</dependency>
</dependencies>
<profiles>
<profile>
<id>Moneta Artifactory</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<build>
<project
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>cz.moneta.test</groupId>
<artifactId>tests</artifactId>
<version>2.28-SNAPSHOT</version>
<properties>
<harness.version>7.55-SNAPSHOT</harness.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<junit.platform.version>1.5.1</junit.platform.version>
<ibm.mq.version>9.4.5.0</ibm.mq.version>
</properties>
<dependencies>
<dependency>
<groupId>cz.moneta.test</groupId>
<artifactId>harness</artifactId>
<version>${harness.version}</version>
</dependency>
<dependency>
<groupId>com.ibm.mq</groupId>
<artifactId>com.ibm.mq.allclient</artifactId>
<version>${ibm.mq.version}</version>
</dependency>
<!-- StringTemplate -->
<dependency>
<groupId>org.antlr</groupId>
<artifactId>ST4</artifactId>
<version>4.3.4</version>
</dependency>
<dependency>
<groupId>javax.jms</groupId>
<artifactId>javax.jms-api</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-csv</artifactId>
<version>2.16.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.16.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc_auth</artifactId>
<version>8.2.0.x86</version>
<type>dll</type>
</dependency>
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>15.0</version>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-reporting</artifactId>
<version>${junit.platform.version}</version>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-console</artifactId>
<version>${junit.platform.version}</version>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.26</version>
</dependency>
</dependencies>
<profiles>
<profile>
<id>Moneta Artifactory</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
@ -91,30 +109,37 @@
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
<configuration>
<argLine>--add-opens java.base/java.lang.invoke=ALL-UNNAMED -Dhttp.proxyHost=wsa-aws.mbid.cz -Dhttp.proxyPort=8008 -Dhttps.proxyHost=wsa-aws.mbid.cz -Dhttps.proxyPort=8008
-Dhttp.nonProxyHosts="elasticclusterawscoord*|elasticclusterawsingest*|jenkinslivex*|cbltstx|vault|vault.svc.k8s.moneta-containers.net|selenium-hub.svc.k8s.moneta-containers.net|jira*|d000*|x000*|l000*|digdev*|r000|spii-live-significant|mbczvl1dl0ihat3.ux.mbid.cz|mbczvl1dl0ihet3.ux.mbid.cz|wso2-fve-gw.ux.mbid.cz|wso2eifve.lb.mbid.cz|wso2eippe.lb.mbid.cz|wso2-ppe-gw.ux.mbid.cz|mbczvl0bl0enin3.ux.mbid.cz|wso2-tst1-gw.ux.mbid.cz|wso2eitst1.lb.mbid.cz|wso2-edu-gw.ux.mbid.cz|mbczvl0bl0enin5.ux.mbid.cz|mbczvl1dl0enin6.ux.mbid.cz|wso2api01-wso2-02.ux.mbid.cz|api-szr.tst.moneta-containers.net|api-szr.ppe.moneta-containers.net|docker1|mbczvl1dl0mockt.ux.mbid.cz|api.tst.moneta-containers.net|api.ppe.moneta-containers.net"</argLine>
<includes>
<include>cz.moneta.test.sandbox.demo.HarnessDemoTest.java</include>
</includes>
<trimStackTrace>false</trimStackTrace>
<useFile>false</useFile>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
<configuration>
<argLine>
--add-opens
java.base/java.lang.invoke=ALL-UNNAMED
-Dhttp.proxyHost=wsa-aws.mbid.cz
-Dhttp.proxyPort=8008
-Dhttps.proxyHost=wsa-aws.mbid.cz
-Dhttps.proxyPort=8008
-Dhttp.nonProxyHosts="elasticclusterawscoord*|elasticclusterawsingest*|jenkinslivex*|cbltstx|vault|vault.svc.k8s.moneta-containers.net|selenium-hub.svc.k8s.moneta-containers.net|jira*|d000*|x000*|l000*|digdev*|r000|spii-live-significant|mbczvl1dl0ihat3.ux.mbid.cz|mbczvl1dl0ihet3.ux.mbid.cz|wso2-fve-gw.ux.mbid.cz|wso2eifve.lb.mbid.cz|wso2eippe.lb.mbid.cz|wso2-ppe-gw.ux.mbid.cz|mbczvl0bl0enin3.ux.mbid.cz|wso2-tst1-gw.ux.mbid.cz|wso2eitst1.lb.mbid.cz|wso2-edu-gw.ux.mbid.cz|mbczvl0bl0enin5.ux.mbid.cz|mbczvl1dl0enin6.ux.mbid.cz|wso2api01-wso2-02.ux.mbid.cz|api-szr.tst.moneta-containers.net|api-szr.ppe.moneta-containers.net|docker1|mbczvl1dl0mockt.ux.mbid.cz|api.tst.moneta-containers.net|api.ppe.moneta-containers.net"</argLine>
<includes>
<include>
cz.moneta.test.sandbox.demo.HarnessDemoTest.java</include>
</includes>
<trimStackTrace>false</trimStackTrace>
<useFile>false</useFile>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<snapshots>
<enabled>false</enabled>
@ -148,122 +173,129 @@
<name>MVN Repository</name>
<url>https://mvnrepository.com/artifact/</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
<id>central</id>
<name>plugins-release</name>
<url>https://artifactory-aws.ux.mbid.cz/artifactory/plugins-release</url>
</pluginRepository>
<pluginRepository>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
<id>snapshots</id>
<name>plugins-snapshot</name>
<url>https://artifactory-aws.ux.mbid.cz/artifactory/plugins-snapshot</url>
</pluginRepository>
</pluginRepositories>
</profile>
<profile>
<id>build.package</id>
<activation>
<property>
<name>build.package</name>
<value>true</value>
</property>
</activation>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<archive>
<manifest>
<mainClass>cz.moneta.test.testrunner.TestRunner</mainClass>
</manifest>
</archive>
<descriptors>
<descriptor>src/assembly/assembly.xml</descriptor>
</descriptors>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
<id>central</id>
<name>libs-release</name>
<url>https://artifactory-aws.ux.mbid.cz/artifactory/libs-release</url>
</repository>
<repository>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
<id>snapshots</id>
<name>libs-snapshot</name>
<url>https://artifactory-aws.ux.mbid.cz/artifactory/libs-snapshot</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
<id>central</id>
<name>plugins-release</name>
<url>https://artifactory-aws.ux.mbid.cz/artifactory/plugins-release</url>
</pluginRepository>
<pluginRepository>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
<id>snapshots</id>
<name>plugins-snapshot</name>
<url>https://artifactory-aws.ux.mbid.cz/artifactory/plugins-snapshot</url>
</pluginRepository>
</pluginRepositories>
</profile>
</profiles>
</repositories>
<pluginRepositories>
<pluginRepository>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
<id>central</id>
<name>plugins-release</name>
<url>
https://artifactory-aws.ux.mbid.cz/artifactory/plugins-release</url>
</pluginRepository>
<pluginRepository>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
<id>snapshots</id>
<name>plugins-snapshot</name>
<url>
https://artifactory-aws.ux.mbid.cz/artifactory/plugins-snapshot</url>
</pluginRepository>
</pluginRepositories>
</profile>
<profile>
<id>build.package</id>
<activation>
<property>
<name>build.package</name>
<value>true</value>
</property>
</activation>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.6.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<archive>
<manifest>
<mainClass>
cz.moneta.test.testrunner.TestRunner</mainClass>
</manifest>
</archive>
<descriptors>
<descriptor>src/assembly/assembly.xml</descriptor>
</descriptors>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
<id>central</id>
<name>libs-release</name>
<url>
https://artifactory-aws.ux.mbid.cz/artifactory/libs-release</url>
</repository>
<repository>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
<id>snapshots</id>
<name>libs-snapshot</name>
<url>
https://artifactory-aws.ux.mbid.cz/artifactory/libs-snapshot</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
<id>central</id>
<name>plugins-release</name>
<url>
https://artifactory-aws.ux.mbid.cz/artifactory/plugins-release</url>
</pluginRepository>
<pluginRepository>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
<id>snapshots</id>
<name>plugins-snapshot</name>
<url>
https://artifactory-aws.ux.mbid.cz/artifactory/plugins-snapshot</url>
</pluginRepository>
</pluginRepositories>
</profile>
</profiles>
</project>

View File

@ -7,7 +7,7 @@ import cz.moneta.test.dsl.auto.smartauto.setman.SmartAutoSetman;
import cz.moneta.test.dsl.broadcom.Broadcom;
import cz.moneta.test.dsl.brokerportal.BrokerPortal;
import cz.moneta.test.dsl.caapi.CaApiBuilder;
import cz.moneta.test.dsl.cagw.CaGw;
import cz.moneta.test.dsl.cagw.CaGwBuilder;
import cz.moneta.test.dsl.cashman.Cashman;
import cz.moneta.test.dsl.cebia.Cebia;
import cz.moneta.test.dsl.demo.Spirits;
@ -23,10 +23,9 @@ import cz.moneta.test.dsl.greenscreen.GreenScreen;
import cz.moneta.test.dsl.hypos.Hypos;
import cz.moneta.test.dsl.ib.Ib;
import cz.moneta.test.dsl.ilods.Ilods;
import cz.moneta.test.dsl.imq.ImqFirstVision;
import cz.moneta.test.dsl.kafka.Kafka;
import cz.moneta.test.dsl.kasanova.Kasanova;
import cz.moneta.test.dsl.mobile.smartbanking.home.Sb;
import cz.moneta.test.dsl.messaging.Messaging;
import cz.moneta.test.dsl.monetaapiportal.MonetaApiPortal;
import cz.moneta.test.dsl.monetaportal.MonetaPortal;
import cz.moneta.test.dsl.mwf.IHub;
@ -112,8 +111,8 @@ public class Harness extends BaseStoreAccessor {
return new CaApiBuilder(this);
}
public CaGw withCaGw() {
return new CaGw(this);
public CaGwBuilder withCaGw() {
return new CaGwBuilder(this);
}
@Deprecated
@ -284,12 +283,8 @@ public class Harness extends BaseStoreAccessor {
return new Cashman(this);
}
public ImqFirstVision withImqFirstVision() {
return new ImqFirstVision(this);
}
public Kafka withKafka() {
return new Kafka(this);
public Messaging withMessaging() {
return new Messaging(this);
}
private void initGenerators() {

View File

@ -47,7 +47,7 @@ public interface NewCalculationPage extends SmartAutoFlow<NewCalculationPage>, S
String VEHICLE_REGISTRATION_DATE_CHOOSE_BUTTON = "//button//span[contains(text(), 'Vybrat')]";
String VEHICLE_FINANCE_PRODUCT = "(//DIV//p//span[contains(text(), 'Vyberte záznam ...')])[1]";
String VEHICLE_FINANCE_PRODUCT_CHOOSE_BUTTON_DEFAULT = "(//div/h3[contains(text(), '%s')])/..//following-sibling::div//button//span[contains(text(), 'Vybrat')][1]/..";
String VEHICLE_CAR_INSURANCE = "//h4/following-sibling::div/div[contains(text(),'Vyberte záznam ...')][1]";
String VEHICLE_CAR_INSURANCE = "//span[contains(text(),'Povinné ručení')]/following-sibling::div/div[contains(text(),'Vyberte záznam ...')][1]";
String VEHICLE_CAR_ACCIDENT_INSURANCE = "(//DIV//p//span[contains(text(), 'Vyberte záznam ...')])[1]";
String VEHICLE_CAR_BENEFITS = "(//DIV//p//span[contains(text(), 'Vyberte záznam ...')])[1]";
String VEHICLE_CAR_BENEFITS_OPTION_DEFAULT = "(//label//span[contains(text(), '%s')])[1]/..";
@ -75,7 +75,6 @@ public interface NewCalculationPage extends SmartAutoFlow<NewCalculationPage>, S
String VEHICLE_CAR_INSURANCE_VEHICLE_SECOND_FUEL_TYPE_CHOOSE = "//label[contains(.,'Palivo')]/../div/ul/li[contains(.,'%s')]";
String VEHICLE_CAR_INSURANCE_VEHICLE_SEARCH_NO_VIN = "//button[contains(.,'Vyhledat')]";
String VEHICLE_CAR_INSURANCE_VEHICLE_VOLUME_AGRO = "//label[contains(.,'Objem')]/following-sibling::div/div/input";
String VEHICLE_CAR_INSURANCE_VEHICLE_NUMBER_OF_SEATS = "//label[contains(.,'Počet sedadel')]/following-sibling::div/div/input";
String VEHICLE_CAR_INSURANCE_VEHICLE_POWER_AGRO = "//label[contains(.,'Výkon')]/following-sibling::div/div/input";
String VEHICLE_CAR_INSURANCE_VEHICLE_WEIGHT_AGRO = "//label[contains(.,'Hmotnost')]/following-sibling::div/div/input";
String VEHICLE_CAR_INSURANCE_VEHICLE_FUEL_AGRO = "//label[contains(.,'Palivo')]/following-sibling::div/button";
@ -85,8 +84,7 @@ public interface NewCalculationPage extends SmartAutoFlow<NewCalculationPage>, S
String VEHICLE_CAR_INSURANCE_INSURANCE_HAV_FIELD_CHOOSE_DEFAULT = "//h4[contains(.,'Havarijní pojištění')]/../../../../div[2]/div[2]/div/div/section/div/div/div/button";
String VEHICLE_CAR_INSURANCE_INSURANCE_POV_FIELD_DEFAULT = "//h4[contains(.,'Povinné ručení')]/../../../../div[2]/div[1]/div/div[1]/section/h4/button/span[2]";
String VEHICLE_CAR_INSURANCE_INSURANCE_POV_FIELD_CHOOSE_DEFAULT = "//h4[contains(.,'Povinné ručení')]/../../../../div[2]/div[1]/div/div/section/div/div/div/button";
String VEHICLE_CAR_INSURANCE_INSURANCE_CHOOSE = "//section[@data-testid='Collapse']//button[contains(text(),'Vybrat')]";
String VEHICLE_CAR_INSURANCE_INSURANCE_CLOSE = "/html/body/aside[1]/div/div/div[2]/div/div/button[contains(text(),'Zavřít')]";
String VEHICLE_CAR_INSURANCE_INSURANCE_CHOOSE = "//section[@data-testid='Tabs']/div[2]/div[3]/div/div[3]/div/div/button[contains(.,'Vybrat')]";
String CREATE_APPLICATION = "//span[contains(text(), 'Vytvořit žádost')]/parent::button";
String DIV_CLICK_BLOCKER = "//div[@class='_21KP5GbzHYNm19YwGmWy0r']";
String DIV_CLICK_BLOCKER_2 = "//div[@class='_3Lp0XIzdhx1ktbMqO92O8T']";
@ -253,8 +251,8 @@ public interface NewCalculationPage extends SmartAutoFlow<NewCalculationPage>, S
@TypeInto(value = VEHICLE_CAR_INSURANCE_CUSTOMER_NATIONALITY, clear = true)
NewCalculationPage typeVehicleInsuranceCustomerNationality(String nationality);
@Wait(value = VEHICLE_CAR_INSURANCE_CUSTOMER_NATIONALITY_FIELD)
@Click(value = VEHICLE_CAR_INSURANCE_CUSTOMER_NATIONALITY_FIELD, jsClick = true)
@Wait(VEHICLE_CAR_INSURANCE_CUSTOMER_NATIONALITY_FIELD)
@Click(value = VEHICLE_CAR_INSURANCE_CUSTOMER_NATIONALITY_FIELD)
NewCalculationPage clickVehicleInsuranceCustomerNationality();
@Wait(VEHICLE_CAR_INSURANCE_CUSTOMER_ADDRESS)
@ -273,12 +271,9 @@ public interface NewCalculationPage extends SmartAutoFlow<NewCalculationPage>, S
@TypeInto(value = VEHICLE_CAR_INSURANCE_VEHICLE_VOLUME_AGRO)
NewCalculationPage typeVehicleInsuranceVehicleVolume(String volume);
@Wait(VEHICLE_CAR_INSURANCE_VEHICLE_TAB)
@TypeInto(value = VEHICLE_CAR_INSURANCE_VEHICLE_NUMBER_OF_SEATS)
NewCalculationPage typeVehicleInsuranceVehicleNumberOfSeats(String numberOfSeats);
@Wait(VEHICLE_CAR_INSURANCE_VEHICLE_TAB)
@TypeInto(value = VEHICLE_CAR_INSURANCE_VEHICLE_POWER_AGRO, andWait = @Wait(explicitWaitSeconds = 2))
@TypeInto(value = VEHICLE_CAR_INSURANCE_VEHICLE_POWER_AGRO)
NewCalculationPage typeVehicleInsuranceVehiclePower(String power);
@Wait(VEHICLE_CAR_INSURANCE_VEHICLE_TAB)
@ -350,10 +345,6 @@ public interface NewCalculationPage extends SmartAutoFlow<NewCalculationPage>, S
@Click(value = VEHICLE_CAR_INSURANCE_INSURANCE_CHOOSE,jsClick = true)
NewCalculationPage clickVehicleInsuranceInsuranceChoose();
@Wait(value = VEHICLE_CAR_INSURANCE_INSURANCE_CLOSE, waitSecondsForElement = 30)
@Click(value = VEHICLE_CAR_INSURANCE_INSURANCE_CLOSE,jsClick = true)
NewCalculationPage clickVehicleInsuranceInsuranceClose();
@Wait(value = {DIV_CLICK_BLOCKER, DIV_CLICK_BLOCKER_2}, until = Until.GONE, waitSecondsForElement = 30)
@Wait(value = VEHICLE_CAR_ACCIDENT_INSURANCE, waitSecondsForElement = 30)
@Click(value = VEHICLE_CAR_ACCIDENT_INSURANCE, andWait = @Wait(value = CommonElements.MODAL, waitSecondsForElement = 30))

View File

@ -14,7 +14,7 @@ public interface OthersPage extends SmartAutoFlow<OthersPage> {
String ACCOUNT_NUMBER = "//input[@name='sign.bankContactC2C.accountNumber']";
String BANK_CODE = "//span[contains(text(), 'Kód banky')]/../../..//input";
String BUTTON = "//button[contains(text(), '%s')]";
String APPROVAL_MESSAGE = "//h6[contains(text(), 'Zpráva pro schvalovatele')]/../../../div/div/span/textarea";
String APPROVAL_MESSAGE = "//span[contains(text(), 'Zpráva pro schvalovatele')]/../../../textarea";
String VEHICLE_PAGE = "//span[contains(text(), 'Předmět')]";
String DUPLICATE_APPLICATION = "//span[contains(text(), 'Duplikovat')]";
String CALCULATION_NAME = "//input[@name='calculationName']";

View File

@ -13,7 +13,7 @@ import static cz.moneta.test.dsl.auto.smartauto.QueuePage.*;
waitSecondsForElement = 40)
public interface QueuePage extends SmartAutoFlow<QueuePage>, StoreAccessor, CalendarAccessor {
String HEADING_SEND_APPLICATIONS = "//h1[contains(text(), 'Odeslané žádosti')]";
String HEADING_SEND_APPLICATIONS = "//span[contains(text(), 'Odeslané žádosti')]";
String FIRST_APPLICATION = "//div/div/div/button/i";
String DATE_FROM = "//label[contains(., 'Datum od')]/following-sibling::div/button";
String DATE_TO = "//label[contains(., 'Datum do')]/following-sibling::div/button";

View File

@ -6,11 +6,11 @@ import cz.moneta.test.harness.support.web.*;
public interface SavedCalculationsPage extends SmartAutoFlow<SavedCalculationsPage>, StoreAccessor, CalendarAccessor {
String HEADING_SAVED_CALCULATIONS = "//h1[contains(text(), 'Kalkulace')]";
String FIRST_APPLICATION_DIV = "/html/body/div[1]/div[1]/div/div/div[1]/div[2]/div[1]/div/div[1]";
String CALCULATION_NAME = FIRST_APPLICATION_DIV + "/div[2]/div[contains(text(), 'TestDuplikace')]";
String CALCULATION_STATUS = FIRST_APPLICATION_DIV + "//span[contains(text(), 'Rozpracovaná žádost')]";
String CALCULATION_DATE = FIRST_APPLICATION_DIV + "/div[3]/div[2]";
String HEADING_SAVED_CALCULATIONS = "//h1/span[contains(text(), 'Kalkulace')]";
String FIRST_APPLICATION_DIV = "//h2/../div[1]/div/";
String CALCULATION_NAME = FIRST_APPLICATION_DIV + "div/div[1]";
String CALCULATION_STATUS = FIRST_APPLICATION_DIV + "/span/span[contains(text(), 'Rozpracovaná žádost')]";
String CALCULATION_DATE = FIRST_APPLICATION_DIV + "div/div[5]";
String DATE_FROM = "//label[contains(., 'Datum od')]/following-sibling::div/button";
String DATE_TO = "//label[contains(., 'Datum do')]/following-sibling::div/button";
String SEARCH_BUTTON = "//button[@testid='bbutton' and span[text()='Zobrazit']]";

View File

@ -31,7 +31,7 @@ public interface VehiclePage extends SmartAutoFlow<VehiclePage>, StoreAccessor {
String SEATS = "//input[@name='vehicle.specificCar.sittingPlaceNr']";
String FUEL = "(//span[contains(text(), 'Palivo')])[2]/../..//following-sibling::div//input[@value='%s']";
String MILEAGE = "//input[@name='vehicle.specificCar.travel']";
String IMPORT_VEHICLE = "//span[contains(text(), 'Vozidlo z dovozu')]/../../../div/div/div/span/button//span[contains(text(), '%s')]/..";
String IMPORT_VEHICLE = "//span[contains(text(), 'Vozidlo z dovozu')]/../../../div/div/span/button/span[contains(text(), '%s')]";
String DEALER_IS_OWNER = "//span[contains(text(), 'Dealer je majitelem vozidla')]/../../..//span[contains(text(), '%s')]/..";
String DESCRIPTION = "//textarea[@name='vehicle.specificCar.description']";
String OTHERS_PAGE = "//span[contains(text(), 'Ostatní')]";
@ -130,10 +130,10 @@ public interface VehiclePage extends SmartAutoFlow<VehiclePage>, StoreAccessor {
@TypeInto(value = DESCRIPTION, andWait = @Wait(value = LOADER_DIV, until = Until.GONE))
VehiclePage typeDescription(String description);
@Click(value = IMPORT_VEHICLE, jsClick = true)
@Click(IMPORT_VEHICLE)
VehiclePage clickImportVehicle(ImportVehicle importVehicle);
@Click(value = DEALER_IS_OWNER,jsClick = true)
@Click(DEALER_IS_OWNER)
VehiclePage clickDealerIsOwner(DealerIsOwner dealerIsOwner);
@Click(value = OTHERS_PAGE, jsClick = true)

View File

@ -23,8 +23,8 @@ public interface ForBillingPage extends SmartAutoFlow<ForBillingPage> {
String FROM_DATE_TEXT = "//span[contains(text(), 'Od data')]";
String TO_DATE_TEXT = "//span[contains(text(), 'Do data')]";
String COMMISSIONS_CREDIT_NOTES_TEXT = "//span[contains(text(), 'Provize/Dobropis')]";
String FILTER_TITLE = "//h6[contains(text(), 'Filtr')]";
String BILLING_COMMISSIONS_TITLE = "//h6[contains(text(), 'Fakturace')]";
String FILTER_TITLE = "//span[contains(text(), 'Filtr')]";
String BILLING_COMMISSIONS_TITLE = "//span[contains(text(), 'Provize k fakturaci')]";
String RESET_BUTTON = "//span[contains(text(), 'Resetovat')]";
String SEARCH_BUTTON = "//span[contains(text(), 'Vyhledat')]";
String INVOICE_BUTTON = "//span[contains(text(), 'Vyfakturovat')]";

View File

@ -25,7 +25,7 @@ public interface DashboardPage extends SmartAutoFlow<DashboardPage> {
String TOTAL_AVAILABLE_LIMIT_TEXT = "//span[contains(text(), 'Volný limit')]";
String FOR_PAYMENT_TEXT = "//span[contains(text(), 'K úhradě')]";
String OVERDUE_PAYMENT_TEXT = "//span[contains(text(), 'Z toho po splatnosti')]";
String PRODUCT_LIST_TITLE = "//h6[contains(text(), 'Produkty')]";
String PRODUCT_LIST_TITLE = "//span[contains(text(), 'Seznam produktů')]";
String ALL_CONTRACT_BUTTON = "//span[contains(text(), 'Všechny zakázky')]";
@CheckElementsPresent(value = {

View File

@ -25,7 +25,6 @@ public class CalculationData {
private Subsidy subsidy;
private ItemType itemType;
private int firstPayment;
private String numberOfSeats;
public CalculationData setCustomer(Customer customer) {
this.customer = customer;
@ -132,8 +131,4 @@ public class CalculationData {
return this;
}
public CalculationData setNumberOfSeats(String numberOfSeats) {
this.numberOfSeats = numberOfSeats;
return this;
}
}

View File

@ -1,33 +0,0 @@
package cz.moneta.test.dsl.cagw;
import cz.moneta.test.dsl.Harness;
import cz.moneta.test.dsl.cagw.api.ApiBuilder;
import cz.moneta.test.dsl.cagw.auth.AuthBuilder;
import cz.moneta.test.dsl.cagw.oauth2.Oauth2Builder;
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
import cz.moneta.test.harness.support.rest.RawRestRequest;
public class CaGw extends CaGwBuilder {
public CaGw(Harness harness) {
super(harness);
}
public ApiBuilder api() {
return new ApiBuilder(harness);
}
public Oauth2Builder oauth2() {
return new Oauth2Builder(harness);
}
public AuthBuilder auth() {
return new AuthBuilder(harness);
}
public RawRestRequest.Path prepareRequest() {
CaGwEndpoint endpoint = harness.getEndpoint(CaGwEndpoint.class);
return RawRestRequest.jsonBuilder(endpoint);
}
}

View File

@ -1,20 +1,34 @@
package cz.moneta.test.dsl.cagw;
import cz.moneta.test.dsl.Harness;
import cz.moneta.test.dsl.cagw.api.ApiBuilder;
import cz.moneta.test.dsl.cagw.auth.AuthBuilder;
import cz.moneta.test.dsl.cagw.oauth2.Oauth2Builder;
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
import cz.moneta.test.harness.support.rest.Builders;
import cz.moneta.test.harness.support.rest.RestRequest;
public class CaGwBuilder {
protected final Harness harness;
private final Harness harness;
protected CaGwBuilder(Harness harness) {
public CaGwBuilder(Harness harness) {
this.harness = harness;
}
protected <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
public ApiBuilder api() {
return new ApiBuilder(harness);
}
}
public Oauth2Builder oauth2() {
return new Oauth2Builder(harness);
}
public AuthBuilder auth() {
return new AuthBuilder(harness);
}
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
}
}

View File

@ -1,16 +1,19 @@
package cz.moneta.test.dsl.cagw.api;
import cz.moneta.test.dsl.Harness;
import cz.moneta.test.dsl.cagw.CaGwBuilder;
import cz.moneta.test.dsl.cagw.api.cbl.CblBuilder;
import cz.moneta.test.dsl.cagw.api.v2.V2Builder;
import cz.moneta.test.dsl.cagw.api.v4.V4Builder;
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
import cz.moneta.test.dsl.cagw.api.v1.V1Builder;
import cz.moneta.test.harness.support.rest.Builders;
import cz.moneta.test.harness.support.rest.RestRequest;
public class ApiBuilder extends CaGwBuilder {
public class ApiBuilder {
private final Harness harness;
public ApiBuilder(Harness harness) {
super(harness);
this.harness = harness;
}
public CblBuilder cbl() {
@ -29,4 +32,7 @@ public class ApiBuilder extends CaGwBuilder {
return new V1Builder(harness);
}
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
}
}

View File

@ -1,17 +1,23 @@
package cz.moneta.test.dsl.cagw.api.cbl;
import cz.moneta.test.dsl.Harness;
import cz.moneta.test.dsl.cagw.CaGwBuilder;
import cz.moneta.test.dsl.cagw.api.cbl.psd.PsdBuilder;
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
import cz.moneta.test.harness.support.rest.Builders;
import cz.moneta.test.harness.support.rest.RestRequest;
public class CblBuilder extends CaGwBuilder {
public class CblBuilder {
private final Harness harness;
public CblBuilder(Harness harness) {
super(harness);
this.harness = harness;
}
public PsdBuilder psd() {
return new PsdBuilder(harness);
}
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
}
}

View File

@ -1,14 +1,17 @@
package cz.moneta.test.dsl.cagw.api.cbl.psd;
import cz.moneta.test.dsl.Harness;
import cz.moneta.test.dsl.cagw.CaGwBuilder;
import cz.moneta.test.dsl.cagw.api.cbl.psd.aisp.AispBuilder;
import cz.moneta.test.dsl.cagw.api.cbl.psd.pisp.PispBuilder;
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
import cz.moneta.test.harness.support.rest.Builders;
import cz.moneta.test.harness.support.rest.RestRequest;
public class PsdBuilder extends CaGwBuilder {
public class PsdBuilder {
private final Harness harness;
public PsdBuilder(Harness harness) {
super(harness);
this.harness = harness;
}
public PispBuilder pisp() {
@ -19,4 +22,7 @@ public class PsdBuilder extends CaGwBuilder {
return new AispBuilder(harness);
}
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
}
}

View File

@ -1,17 +1,23 @@
package cz.moneta.test.dsl.cagw.api.cbl.psd.aisp;
import cz.moneta.test.dsl.Harness;
import cz.moneta.test.dsl.cagw.CaGwBuilder;
import cz.moneta.test.dsl.cagw.api.cbl.psd.aisp.my.MyBuilder;
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
import cz.moneta.test.harness.support.rest.Builders;
import cz.moneta.test.harness.support.rest.RestRequest;
public class AispBuilder extends CaGwBuilder {
public class AispBuilder {
private final Harness harness;
public AispBuilder(Harness harness) {
super(harness);
this.harness = harness;
}
public MyBuilder my() {
return new MyBuilder(harness);
}
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
}
}

View File

@ -1,16 +1,19 @@
package cz.moneta.test.dsl.cagw.api.cbl.psd.aisp.my;
import cz.moneta.test.dsl.Harness;
import cz.moneta.test.dsl.cagw.CaGwBuilder;
import cz.moneta.test.dsl.cagw.api.cbl.psd.aisp.my.accounts.Account;
import cz.moneta.test.dsl.cagw.api.cbl.psd.aisp.my.accounts.AccountsBuilder;
import cz.moneta.test.dsl.cagw.api.cbl.psd.aisp.my.accounts.AccountsWithParams;
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
import cz.moneta.test.harness.support.rest.Builders;
import cz.moneta.test.harness.support.rest.RestRequest;
public class MyBuilder extends CaGwBuilder {
public class MyBuilder {
private final Harness harness;
public MyBuilder(Harness harness) {
super(harness);
this.harness = harness;
}
public AccountsBuilder accounts() {
@ -25,4 +28,7 @@ public class MyBuilder extends CaGwBuilder {
return getBuilder(AccountsWithParams.class);
}
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
}
}

View File

@ -1,17 +1,23 @@
package cz.moneta.test.dsl.cagw.api.cbl.psd.aisp.my.accounts;
import cz.moneta.test.dsl.Harness;
import cz.moneta.test.dsl.cagw.CaGwBuilder;
import cz.moneta.test.dsl.cagw.api.cbl.psd.aisp.my.accounts.id.IdBuilder;
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
import cz.moneta.test.harness.support.rest.Builders;
import cz.moneta.test.harness.support.rest.RestRequest;
public class AccountsBuilder extends CaGwBuilder {
public class AccountsBuilder {
private final Harness harness;
public AccountsBuilder(Harness harness) {
super(harness);
this.harness = harness;
}
public IdBuilder id() {
return new IdBuilder(harness);
}
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
}
}

View File

@ -1,15 +1,18 @@
package cz.moneta.test.dsl.cagw.api.cbl.psd.aisp.my.accounts.id;
import cz.moneta.test.dsl.Harness;
import cz.moneta.test.dsl.cagw.CaGwBuilder;
import cz.moneta.test.dsl.cagw.api.cbl.psd.aisp.my.accounts.id.balance.Balance;
import cz.moneta.test.dsl.cagw.api.cbl.psd.aisp.my.accounts.id.transactions.Transactions;
import cz.moneta.test.dsl.cagw.api.cbl.psd.aisp.my.accounts.id.transactions.TransactionsWithParams;
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
import cz.moneta.test.harness.support.rest.Builders;
import cz.moneta.test.harness.support.rest.RestRequest;
public class IdBuilder extends CaGwBuilder {
public class IdBuilder {
private final Harness harness;
public IdBuilder(Harness harness) {
super(harness);
this.harness = harness;
}
public Balance prepareBalanceRequest() {
@ -23,5 +26,8 @@ public class IdBuilder extends CaGwBuilder {
public TransactionsWithParams prepareTransactionsWithParamsRequest() {
return getBuilder(TransactionsWithParams.class);
}
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
}
}

View File

@ -1,17 +1,23 @@
package cz.moneta.test.dsl.cagw.api.cbl.psd.pisp;
import cz.moneta.test.dsl.Harness;
import cz.moneta.test.dsl.cagw.CaGwBuilder;
import cz.moneta.test.dsl.cagw.api.cbl.psd.pisp.my.MyBuilder;
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
import cz.moneta.test.harness.support.rest.Builders;
import cz.moneta.test.harness.support.rest.RestRequest;
public class PispBuilder extends CaGwBuilder {
public class PispBuilder {
private final Harness harness;
public PispBuilder(Harness harness) {
super(harness);
this.harness = harness;
}
public MyBuilder my() {
return new MyBuilder(harness);
}
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
}
}

View File

@ -1,14 +1,17 @@
package cz.moneta.test.dsl.cagw.api.cbl.psd.pisp.my;
import cz.moneta.test.dsl.Harness;
import cz.moneta.test.dsl.cagw.CaGwBuilder;
import cz.moneta.test.dsl.cagw.api.cbl.psd.pisp.my.payments.Payment;
import cz.moneta.test.dsl.cagw.api.cbl.psd.pisp.my.payments.PaymentsBuilder;
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
import cz.moneta.test.harness.support.rest.Builders;
import cz.moneta.test.harness.support.rest.RestRequest;
public class MyBuilder extends CaGwBuilder {
public class MyBuilder {
private final Harness harness;
public MyBuilder(Harness harness) {
super(harness);
this.harness = harness;
}
public PaymentsBuilder payments() {
@ -19,4 +22,7 @@ public class MyBuilder extends CaGwBuilder {
return getBuilder(Payment.class);
}
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
}
}

View File

@ -1,17 +1,23 @@
package cz.moneta.test.dsl.cagw.api.cbl.psd.pisp.my.payments;
import cz.moneta.test.dsl.Harness;
import cz.moneta.test.dsl.cagw.CaGwBuilder;
import cz.moneta.test.dsl.cagw.api.cbl.psd.pisp.my.payments.paymentid.PaymentIdBuilder;
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
import cz.moneta.test.harness.support.rest.Builders;
import cz.moneta.test.harness.support.rest.RestRequest;
public class PaymentsBuilder extends CaGwBuilder {
public class PaymentsBuilder {
private final Harness harness;
public PaymentsBuilder(Harness harness) {
super(harness);
this.harness = harness;
}
public PaymentIdBuilder paymentId() {
return new PaymentIdBuilder(harness);
}
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
}
}

View File

@ -1,17 +1,23 @@
package cz.moneta.test.dsl.cagw.api.cbl.psd.pisp.my.payments.paymentid;
import cz.moneta.test.dsl.Harness;
import cz.moneta.test.dsl.cagw.CaGwBuilder;
import cz.moneta.test.dsl.cagw.api.cbl.psd.pisp.my.payments.paymentid.status.Status;
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
import cz.moneta.test.harness.support.rest.Builders;
import cz.moneta.test.harness.support.rest.RestRequest;
public class PaymentIdBuilder extends CaGwBuilder {
public class PaymentIdBuilder {
private final Harness harness;
public PaymentIdBuilder(Harness harness) {
super(harness);
this.harness = harness;
}
public Status prepareStatusRequest() {
return getBuilder(Status.class);
}
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
}
}

View File

@ -1,17 +1,23 @@
package cz.moneta.test.dsl.cagw.api.v1;
import cz.moneta.test.dsl.Harness;
import cz.moneta.test.dsl.cagw.CaGwBuilder;
import cz.moneta.test.dsl.cagw.api.v1.external.ExternalBuilder;
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
import cz.moneta.test.harness.support.rest.Builders;
import cz.moneta.test.harness.support.rest.RestRequest;
public class V1Builder extends CaGwBuilder {
public class V1Builder {
private final Harness harness;
public V1Builder(Harness harness) {
super(harness);
this.harness = harness;
}
public ExternalBuilder external() {
return new ExternalBuilder(harness);
}
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
}
}

View File

@ -1,14 +1,17 @@
package cz.moneta.test.dsl.cagw.api.v1.external;
import cz.moneta.test.dsl.Harness;
import cz.moneta.test.dsl.cagw.CaGwBuilder;
import cz.moneta.test.dsl.cagw.api.v1.external.product.ProductBuilder;
import cz.moneta.test.dsl.cagw.api.v1.external.service.ServiceBuilder;
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
import cz.moneta.test.harness.support.rest.Builders;
import cz.moneta.test.harness.support.rest.RestRequest;
public class ExternalBuilder extends CaGwBuilder {
public class ExternalBuilder {
private final Harness harness;
public ExternalBuilder(Harness harness) {
super(harness);
this.harness = harness;
}
public ProductBuilder product() {
@ -19,4 +22,7 @@ public class ExternalBuilder extends CaGwBuilder {
return new ServiceBuilder(harness);
}
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
}
}

View File

@ -1,17 +1,23 @@
package cz.moneta.test.dsl.cagw.api.v1.external.product;
import cz.moneta.test.dsl.Harness;
import cz.moneta.test.dsl.cagw.CaGwBuilder;
import cz.moneta.test.dsl.cagw.api.v1.external.product.cloan.CloanBuilder;
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
import cz.moneta.test.harness.support.rest.Builders;
import cz.moneta.test.harness.support.rest.RestRequest;
public class ProductBuilder {
private final Harness harness;
public class ProductBuilder extends CaGwBuilder {
public ProductBuilder(Harness harness) {
super(harness);
this.harness = harness;
}
public CloanBuilder cbl() {
return new CloanBuilder(harness);
}
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
}
}

View File

@ -1,17 +1,23 @@
package cz.moneta.test.dsl.cagw.api.v1.external.product.cloan;
import cz.moneta.test.dsl.Harness;
import cz.moneta.test.dsl.cagw.CaGwBuilder;
import cz.moneta.test.dsl.cagw.api.v1.external.product.cloan.application.ApplicationBuilder;
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
import cz.moneta.test.harness.support.rest.Builders;
import cz.moneta.test.harness.support.rest.RestRequest;
public class CloanBuilder extends CaGwBuilder {
public class CloanBuilder {
private final Harness harness;
public CloanBuilder(Harness harness) {
super(harness);
this.harness = harness;
}
public ApplicationBuilder application() {
return new ApplicationBuilder(harness);
}
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
}
}

View File

@ -1,16 +1,22 @@
package cz.moneta.test.dsl.cagw.api.v1.external.product.cloan.application;
import cz.moneta.test.dsl.Harness;
import cz.moneta.test.dsl.cagw.CaGwBuilder;
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
import cz.moneta.test.harness.support.rest.Builders;
import cz.moneta.test.harness.support.rest.RestRequest;
public class ApplicationBuilder extends CaGwBuilder {
public class ApplicationBuilder {
private final Harness harness;
public ApplicationBuilder(Harness harness) {
super(harness);
this.harness = harness;
}
public Application prepareApplicationRequest() {
return getBuilder(Application.class);
}
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
}
}

View File

@ -1,17 +1,23 @@
package cz.moneta.test.dsl.cagw.api.v1.external.service;
import cz.moneta.test.dsl.Harness;
import cz.moneta.test.dsl.cagw.CaGwBuilder;
import cz.moneta.test.dsl.cagw.api.v1.external.service.voicebot.VoicebotBuilder;
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
import cz.moneta.test.harness.support.rest.Builders;
import cz.moneta.test.harness.support.rest.RestRequest;
public class ServiceBuilder extends CaGwBuilder {
public class ServiceBuilder {
private final Harness harness;
public ServiceBuilder(Harness harness) {
super(harness);
this.harness = harness;
}
public VoicebotBuilder voicebot() {
return new VoicebotBuilder(harness);
}
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
}
}

View File

@ -1,17 +1,23 @@
package cz.moneta.test.dsl.cagw.api.v1.external.service.voicebot;
import cz.moneta.test.dsl.Harness;
import cz.moneta.test.dsl.cagw.CaGwBuilder;
import cz.moneta.test.dsl.cagw.api.v1.external.service.voicebot.voicepassworddetail.VoicePasswordDetail;
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
import cz.moneta.test.harness.support.rest.Builders;
import cz.moneta.test.harness.support.rest.RestRequest;
public class VoicebotBuilder extends CaGwBuilder {
public class VoicebotBuilder {
private final Harness harness;
public VoicebotBuilder(Harness harness) {
super(harness);
this.harness = harness;
}
public VoicePasswordDetail prepareVoicePasswordDetail() {
public VoicePasswordDetail prepareVoicePasswordDetailApi() {
return getBuilder(VoicePasswordDetail.class);
}
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
}
}

View File

@ -1,17 +1,23 @@
package cz.moneta.test.dsl.cagw.api.v2;
import cz.moneta.test.dsl.Harness;
import cz.moneta.test.dsl.cagw.CaGwBuilder;
import cz.moneta.test.dsl.cagw.api.v2.external.ExternalBuilder;
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
import cz.moneta.test.harness.support.rest.Builders;
import cz.moneta.test.harness.support.rest.RestRequest;
public class V2Builder extends CaGwBuilder {
public class V2Builder {
private final Harness harness;
public V2Builder(Harness harness) {
super(harness);
this.harness = harness;
}
public ExternalBuilder external() {
return new ExternalBuilder(harness);
}
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
}
}

View File

@ -1,17 +1,23 @@
package cz.moneta.test.dsl.cagw.api.v2.external;
import cz.moneta.test.dsl.Harness;
import cz.moneta.test.dsl.cagw.CaGwBuilder;
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
import cz.moneta.test.harness.support.rest.Builders;
import cz.moneta.test.harness.support.rest.RestRequest;
import cz.moneta.test.dsl.cagw.api.v2.external.client.ClientBuilder;
public class ExternalBuilder extends CaGwBuilder {
public class ExternalBuilder {
private final Harness harness;
public ExternalBuilder(Harness harness) {
super(harness);
this.harness = harness;
}
public ClientBuilder client() {
return new ClientBuilder(harness);
}
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
}
}

View File

@ -1,17 +1,23 @@
package cz.moneta.test.dsl.cagw.api.v2.external.client;
import cz.moneta.test.dsl.Harness;
import cz.moneta.test.dsl.cagw.CaGwBuilder;
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
import cz.moneta.test.harness.support.rest.Builders;
import cz.moneta.test.harness.support.rest.RestRequest;
import cz.moneta.test.dsl.cagw.api.v2.external.client.kyc.KycBuilder;
public class ClientBuilder extends CaGwBuilder {
public class ClientBuilder {
private final Harness harness;
public ClientBuilder(Harness harness) {
super(harness);
this.harness = harness;
}
public KycBuilder kyc() {
return new KycBuilder(harness);
}
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
}
}

View File

@ -1,17 +1,23 @@
package cz.moneta.test.dsl.cagw.api.v2.external.client.kyc;
import cz.moneta.test.dsl.Harness;
import cz.moneta.test.dsl.cagw.CaGwBuilder;
import cz.moneta.test.dsl.cagw.api.v2.external.client.kyc.businesscard.BusinessCardBuilder;
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
import cz.moneta.test.harness.support.rest.Builders;
import cz.moneta.test.harness.support.rest.RestRequest;
public class KycBuilder extends CaGwBuilder {
public class KycBuilder {
private final Harness harness;
public KycBuilder(Harness harness) {
super(harness);
this.harness = harness;
}
public BusinessCardBuilder bussinesCard() {
return new BusinessCardBuilder(harness);
}
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
}
}

View File

@ -1,16 +1,22 @@
package cz.moneta.test.dsl.cagw.api.v2.external.client.kyc.businesscard;
import cz.moneta.test.dsl.Harness;
import cz.moneta.test.dsl.cagw.CaGwBuilder;
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
import cz.moneta.test.harness.support.rest.Builders;
import cz.moneta.test.harness.support.rest.RestRequest;
public class BusinessCardBuilder extends CaGwBuilder {
public class BusinessCardBuilder {
private final Harness harness;
public BusinessCardBuilder(Harness harness) {
super(harness);
this.harness = harness;
}
public BusinessCard prepareBusinessCardRequest() {
return getBuilder(BusinessCard.class);
}
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
}
}

View File

@ -1,17 +1,23 @@
package cz.moneta.test.dsl.cagw.api.v4;
import cz.moneta.test.dsl.Harness;
import cz.moneta.test.dsl.cagw.CaGwBuilder;
import cz.moneta.test.dsl.cagw.api.v4.token.TokenBuilder;
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
import cz.moneta.test.harness.support.rest.Builders;
import cz.moneta.test.harness.support.rest.RestRequest;
public class V4Builder extends CaGwBuilder {
public class V4Builder {
private final Harness harness;
public V4Builder(Harness harness) {
super(harness);
this.harness = harness;
}
public TokenBuilder token() {
return new TokenBuilder(harness);
}
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
}
}

View File

@ -1,16 +1,23 @@
package cz.moneta.test.dsl.cagw.api.v4.token;
import cz.moneta.test.dsl.Harness;
import cz.moneta.test.dsl.cagw.CaGwBuilder;
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
import cz.moneta.test.harness.support.rest.Builders;
import cz.moneta.test.harness.support.rest.RestRequest;
public class TokenBuilder extends CaGwBuilder {
public class TokenBuilder {
private final Harness harness;
public TokenBuilder(Harness harness) {
super(harness);
this.harness = harness;
}
public ApiV4Token prepareTokenRequest() {
return getBuilder(ApiV4Token.class);
}
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
}
}

View File

@ -1,17 +1,23 @@
package cz.moneta.test.dsl.cagw.auth;
import cz.moneta.test.dsl.Harness;
import cz.moneta.test.dsl.cagw.CaGwBuilder;
import cz.moneta.test.dsl.cagw.auth.oauth.OauthBuilder;
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
import cz.moneta.test.harness.support.rest.Builders;
import cz.moneta.test.harness.support.rest.RestRequest;
public class AuthBuilder extends CaGwBuilder {
public class AuthBuilder {
private final Harness harness;
public AuthBuilder(Harness harness) {
super(harness);
this.harness = harness;
}
public OauthBuilder oauth() {
return new OauthBuilder(harness);
}
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
}
}

View File

@ -1,17 +1,23 @@
package cz.moneta.test.dsl.cagw.auth.oauth;
import cz.moneta.test.dsl.Harness;
import cz.moneta.test.dsl.cagw.CaGwBuilder;
import cz.moneta.test.dsl.cagw.auth.oauth.v2.V2Builder;
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
import cz.moneta.test.harness.support.rest.Builders;
import cz.moneta.test.harness.support.rest.RestRequest;
public class OauthBuilder extends CaGwBuilder {
public class OauthBuilder {
private final Harness harness;
public OauthBuilder(Harness harness) {
super(harness);
this.harness = harness;
}
public V2Builder v2() {
return new V2Builder(harness);
}
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
}
}

View File

@ -1,17 +1,23 @@
package cz.moneta.test.dsl.cagw.auth.oauth.v2;
import cz.moneta.test.dsl.Harness;
import cz.moneta.test.dsl.cagw.CaGwBuilder;
import cz.moneta.test.dsl.cagw.auth.oauth.v2.token.TokenBuilder;
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
import cz.moneta.test.harness.support.rest.Builders;
import cz.moneta.test.harness.support.rest.RestRequest;
public class V2Builder extends CaGwBuilder {
public class V2Builder {
private final Harness harness;
public V2Builder(Harness harness) {
super(harness);
this.harness = harness;
}
public TokenBuilder token() {
return new TokenBuilder(harness);
}
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
}
}

View File

@ -1,17 +1,24 @@
package cz.moneta.test.dsl.cagw.auth.oauth.v2.token;
import cz.moneta.test.dsl.Harness;
import cz.moneta.test.dsl.cagw.CaGwBuilder;
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
import cz.moneta.test.harness.support.rest.Builders;
import cz.moneta.test.harness.support.rest.RestRequest;
public class TokenBuilder extends CaGwBuilder {
public class TokenBuilder {
private final Harness harness;
public TokenBuilder(Harness harness) {
super(harness);
this.harness = harness;
}
public OauthToken prepareOauth2V2TokenRequest() {
return getBuilder(OauthToken.class);
}
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
}
}

View File

@ -1,17 +1,23 @@
package cz.moneta.test.dsl.cagw.oauth2;
import cz.moneta.test.dsl.Harness;
import cz.moneta.test.dsl.cagw.CaGwBuilder;
import cz.moneta.test.dsl.cagw.oauth2.token.TokenBuilder;
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
import cz.moneta.test.harness.support.rest.Builders;
import cz.moneta.test.harness.support.rest.RestRequest;
public class Oauth2Builder extends CaGwBuilder {
public class Oauth2Builder {
private final Harness harness;
public Oauth2Builder(Harness harness) {
super(harness);
this.harness = harness;
}
public TokenBuilder token() {
return new TokenBuilder(harness);
}
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
}
}

View File

@ -1,18 +1,24 @@
package cz.moneta.test.dsl.cagw.oauth2.token;
import cz.moneta.test.dsl.Harness;
import cz.moneta.test.dsl.cagw.CaGwBuilder;
import cz.moneta.test.harness.endpoints.cagw.CaGwEndpoint;
import cz.moneta.test.harness.support.rest.Builders;
import cz.moneta.test.harness.support.rest.RestRequest;
public class TokenBuilder extends CaGwBuilder {
public class TokenBuilder {
private final Harness harness;
public TokenBuilder(Harness harness) {
super(harness);
this.harness = harness;
}
@Deprecated
public Oauth2Token prepareOauth2TokenRequest() {
return getBuilder(Oauth2Token.class);
}
private <RESP, B extends RestRequest<B, ?, RESP>> B getBuilder(Class<B> builderClass) {
return Builders.newRestBuilder(builderClass, CaGwEndpoint.class, harness);
}
}

View File

@ -1,6 +1,5 @@
package cz.moneta.test.dsl.exevido.components;
import cz.moneta.test.dsl.exevido.pages.inheritance.FoldersPage;
import cz.moneta.test.dsl.exevido.pages.IncomingMessagesPage;
import cz.moneta.test.dsl.exevido.pages.OutgoingMessagesPage;
import cz.moneta.test.harness.support.web.Click;
@ -10,7 +9,6 @@ import cz.moneta.test.harness.support.web.Wait;
import static cz.moneta.test.dsl.exevido.components.ExevidoPanels.LOADER_DIV;
import static cz.moneta.test.dsl.exevido.components.LeftMenu.PAGE_SA_SMART_MENU;
import static cz.moneta.test.dsl.exevido.pages.inheritance.FoldersPage.PAGE_LABEL_XPATH;
@Wait(PAGE_SA_SMART_MENU)
@Wait(value = LOADER_DIV, until = Until.GONE)
@ -24,7 +22,6 @@ public interface LeftMenu<T> {
String CATEGORY_INHERITANCE_MMB_XPATH = "//li[@id='Dědictví - MMB']";
String SUBCATEGORY_WITHOUT_FOLDER_XPATH = CATEGORY_INHERITANCE_MMB_XPATH + "//a[contains(., 'Bez spisu')]";
String SUBCATEGORY_FOR_PROCESSING_XPATH = CATEGORY_INHERITANCE_MMB_XPATH + "//a[contains(., 'Ke zpracování')]";
String SUBCATEGORY_FOLDERS_XPATH = CATEGORY_INHERITANCE_MMB_XPATH + "//a[contains(., 'Spisy')]";
String FOR_PROCESSING_D_REQUEST_MMB_XPATH = CATEGORY_INHERITANCE_MMB_XPATH + "//a[contains(., 'D - Žádost [M] - MMB')]";
String REFRESH_COUNTERS_BUTTON = "refresh_counters";
@ -40,7 +37,7 @@ public interface LeftMenu<T> {
@Click(SUBCATEGORY_ALL_OUTGOING_MESSAGES_A)
OutgoingMessagesPage clickAllOutgoingMessages();
@Click(value = CATEGORY_INHERITANCE_MMB_XPATH, by = Lookup.XPATH, andWait = @Wait(value = SUBCATEGORY_WITHOUT_FOLDER_XPATH, by = Lookup.XPATH, until = Until.VISIBLE))
@Click(value = CATEGORY_INHERITANCE_MMB_XPATH, by = Lookup.XPATH, andWait = @Wait(value = SUBCATEGORY_WITHOUT_FOLDER_XPATH, by = Lookup.XPATH ,until = Until.VISIBLE))
@Click(value = SUBCATEGORY_WITHOUT_FOLDER_XPATH, by = Lookup.XPATH, andWait = @Wait(value = LOADER_DIV, until = Until.GONE))
IncomingMessagesPage clickInheritanceMmbWithoutFolder();
@ -48,10 +45,6 @@ public interface LeftMenu<T> {
@Click(value = SUBCATEGORY_FOR_PROCESSING_XPATH, by = Lookup.XPATH, andWait = @Wait(value = FOR_PROCESSING_D_REQUEST_MMB_XPATH, by = Lookup.XPATH, until = Until.VISIBLE))
LeftMenu clickInheritanceMmbForProcessing();
@Click(value = CATEGORY_INHERITANCE_MMB_XPATH, by = Lookup.XPATH, andWait = @Wait(value = SUBCATEGORY_FOR_PROCESSING_XPATH, by = Lookup.XPATH, until = Until.VISIBLE))
@Click(value = SUBCATEGORY_FOLDERS_XPATH, by = Lookup.XPATH, andWait = @Wait(value = PAGE_LABEL_XPATH, by = Lookup.XPATH, until = Until.VISIBLE))
FoldersPage clickInheritanceMmbFolders();
@Click(value = FOR_PROCESSING_D_REQUEST_MMB_XPATH, by = Lookup.XPATH, andWait = @Wait(value = LOADER_DIV, until = Until.GONE))
IncomingMessagesPage clickRequestMmb();

View File

@ -36,11 +36,6 @@ public interface DetailIncomingMessagePage extends ExevidoWebFlow<DetailIncoming
String DOWNLOAD_ZFO = "//button[normalize-space()='Stáhnout ZFO']";
String MESSAGE_RETURN_P = "//p[text()='Zpráva byla vrácena na podatelnu k dalšímu zpracování']";
String INTERNAL_REFERENCE_NUMBER = "//accordion-group[.//b[text()='Spisy']]//table//tr/td[2][normalize-space()='%s']";
String ADD_MESSAGE_FOLDER_BUTTON = "add_message_folder_button";
String ADD_FOLDER_ISSUE_NUMBER_INPUT = "add_folder_issue_number";
String SAVE_FOLDER_ISSUE_NUMBER = "//td//i[@title='Přiřadit ke spisu']";
String REMOVE_FOLDER_ISSUE_NUMBER = "//tr[td[2][normalize-space(text())='%s']]//td[4]//i[@title='Odebrat spis']";
String CONFIRM_BUTTON = "confirm_button";
String MESSAGE_ID_SPAN = "message_id";
@Click(ASSIGN_BUTTON)
@ -124,25 +119,6 @@ public interface DetailIncomingMessagePage extends ExevidoWebFlow<DetailIncoming
@CheckElementPresent(value = INTERNAL_REFERENCE_NUMBER, by = Lookup.XPATH, isStringDynamicXpath = true)
DetailIncomingMessagePage checkInternalReferenceNumber(String referenceNumber);
@CustomAction
default DetailIncomingMessagePage clickAddMessageFolder() {
ExevidoEndpoint endpoint = getEndpoint(ExevidoEndpoint.class);
endpoint.moveToElement(ADD_MESSAGE_FOLDER_BUTTON);
endpoint.click(() -> ADD_MESSAGE_FOLDER_BUTTON);
return null;
}
@TypeInto(value = ADD_FOLDER_ISSUE_NUMBER_INPUT, clear = true, andWait = @Wait(value = ExevidoPanels.LOADER_DIV, until = Until.GONE))
@KeyPress(Key.ENTER)
DetailIncomingMessagePage fillFolderIssueNumber(String folderIssueNumber);
@Click(value = SAVE_FOLDER_ISSUE_NUMBER, by = Lookup.XPATH, andWait = @Wait(value = ExevidoPanels.LOADER_DIV, until = Until.GONE))
DetailIncomingMessagePage saveFolderIssueNumber();
@Click(value = REMOVE_FOLDER_ISSUE_NUMBER, by = Lookup.XPATH, isStringDynamicXpath = true)
@Click(CONFIRM_BUTTON)
DetailIncomingMessagePage removeFolderIssueNumber(String folderIssueNumber);
@CheckElementContent(value = MESSAGE_ID_SPAN)
NewIncomingMessagePage checkMessageId(String id);
}

View File

@ -49,15 +49,6 @@ public interface IncomingMessagesPage extends ExevidoWebFlow<IncomingMessagesPag
return null;
}
@CustomAction
default IncomingMessagesPage verifyIncomingMessageRowNotPresent() {
ExevidoEndpoint endpoint = getEndpoint(ExevidoEndpoint.class);
if (endpoint.isElementPresent(INCOMING_MESSAGE_ROW, Lookup.ID)) {
throw new AssertionError("Incoming message row should NOT be present.");
}
return null;
}
@TypeInto(value = DATE_RANGE_ACCEPTANCE_TIME, clear = true)
@KeyPress(value = Key.ENTER, andWait = @Wait(value = ExevidoPanels.LOADER_DIV, until = Until.GONE))
IncomingMessagesPage searchByDateRange(String date);

View File

@ -42,8 +42,8 @@ public interface NewOutgoingMessagePage extends ExevidoWebFlow<NewOutgoingMessag
String MESSAGE_IS_SENDING_DIV = "message_identification";
String MESSAGE_SUCCESSFULLY_SEND_LOG_XPATH = "//span[contains(text(), 'byla odeslána')]";
String MESSAGE_SEND_FAILURE_LOG_XPATH = "//span[contains(text(), 'selhání odeslání zprávy')]";
String ATTACHMENT_PATH = "regression/exevido/web/testExevido.txt";
String SECOND_ATTACHMENT_PATH = "regression/exevido/web/testExevido2.txt";
String ATTACHMENT_PATH = "regression/exevido/testExevido.txt";
String SECOND_ATTACHMENT_PATH = "regression/exevido/testExevido2.txt";
String MESSAGE_ID_SPAN = "message_id";
String MULTIPLE_RECIPIENTS_SPAN = "//label[@for='multiple_recipients']";
String ADD_RECIPIENT_TO_LIST_BUTTON = "add_to_list_button";

View File

@ -1,25 +0,0 @@
package cz.moneta.test.dsl.exevido.pages.inheritance;
import cz.moneta.test.dsl.exevido.ExevidoWebFlow;
import cz.moneta.test.dsl.exevido.pages.DetailIncomingMessagePage;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.exevido.ExevidoEndpoint;
import cz.moneta.test.harness.support.web.*;
import static cz.moneta.test.dsl.exevido.pages.inheritance.DetailFolderPage.PAGE_LABEL_XPATH;
@Wait(value = PAGE_LABEL_XPATH, by = Lookup.XPATH)
public interface DetailFolderPage extends ExevidoWebFlow<DetailFolderPage>, StoreAccessor {
String PAGE_LABEL_XPATH = "//b[contains(text(),'Spis:')]";
String OUTGOING_MESSAGES_LABEL_XPATH = "//a[contains(text(),'Odchozí zprávy')]";
String ROW_BY_MESSAGE_ID = "//tr[contains(@class,'clickable-row')]//td[2]/div[normalize-space()='%s']/ancestor::tr";
@CustomAction
default DetailIncomingMessagePage openMessageById(String messageId) {
ExevidoEndpoint endpoint = getEndpoint(ExevidoEndpoint.class);
endpoint.moveToElement(OUTGOING_MESSAGES_LABEL_XPATH, Lookup.XPATH);
endpoint.click(() -> String.format(ROW_BY_MESSAGE_ID, messageId), Lookup.XPATH);
return null;
}
}

View File

@ -1,26 +0,0 @@
package cz.moneta.test.dsl.exevido.pages.inheritance;
import cz.moneta.test.dsl.exevido.ExevidoWebFlow;
import cz.moneta.test.dsl.exevido.components.ExevidoPanels;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.exevido.ExevidoEndpoint;
import cz.moneta.test.harness.support.web.*;
import static cz.moneta.test.dsl.exevido.pages.inheritance.FoldersPage.PAGE_LABEL_XPATH;
@Wait(value = PAGE_LABEL_XPATH, by = Lookup.XPATH)
public interface FoldersPage extends ExevidoWebFlow<FoldersPage>, StoreAccessor {
String PAGE_LABEL_XPATH = "//b[contains(text(),'Spisy')]";
String PRODUCT_OWNER_NAME_INPUT = "text_product_owner_name";
String FOLDER_MESSAGE_ROW = "folder_0_row";
@TypeInto(value = PRODUCT_OWNER_NAME_INPUT)
@KeyPress(value = Key.ENTER, andWait = @Wait(value = ExevidoPanels.LOADER_DIV, until = Until.GONE))
FoldersPage fillProductOwnerName(String name);
@Click(value = FOLDER_MESSAGE_ROW, andWait = @Wait(value = ExevidoPanels.LOADER_DIV, until = Until.GONE))
DetailFolderPage clickFolderRow();
}

View File

@ -97,7 +97,7 @@ public class HyposContractPrepare {
.realtyPriceCurrent(3500000)
.realtyType(RealtyType.FLAT.getValue())
.pledgeType(PledgeType.PROPERTY_HOUSING.getValue())
.contractRelation(ContractRelation.PLEDGE_WITH_CURRENT_REALTY.getValue())
.contractRelation(ContranctRelation.PLEDGE_WITH_CURRENT_REALTY.getValue())
.collateralType(CollateralType.FLAT.getValue())
.appraiserCompany(AppraiserCompany.MONETA_SUPERVISION.getValue())
.build();

View File

@ -13,4 +13,4 @@ public enum AppraiserCompany {
public String getValue() {
return appraiserCompany;
}
}
}

View File

@ -1,8 +1,6 @@
package cz.moneta.test.dsl.hypos.enums;
import cz.moneta.test.harness.support.web.SelectByValue;
public enum CadasterRequestType implements SelectByValue {
public enum CadasterRequestType {
PART_OWNER_LIST("Část. výpis LV"),
CADASTER_MAP("Kat. mapa"),
OWNER_LIST("List vlastnictví"),
@ -14,7 +12,6 @@ public enum CadasterRequestType implements SelectByValue {
this.cadasterRequestType = cadasterRequestType;
}
@Override
public String getValue() {
return cadasterRequestType;
}

View File

@ -12,4 +12,4 @@ public enum CollateralCode {
public String getValue() {
return collateralCode;
}
}
}

View File

@ -1,8 +1,6 @@
package cz.moneta.test.dsl.hypos.enums;
import cz.moneta.test.harness.support.web.SelectByValue;
public enum CollectableFee implements SelectByValue {
public enum CollectableFee {
APPENDIX_CLIENT_INITIATIVE("DODATEK - Z PODNĚTU KLIENTA"),
FEE_DOWNLOAD_LV_KM("Poplatek za stažení LV/KM"),
OTHER_BANK_INFORMATION("OSTATNÍ - BANKOVNÍ INFORMACE");
@ -13,7 +11,6 @@ public enum CollectableFee implements SelectByValue {
this.collectableFee = collectableFee;
}
@Override
public String getValue() {
return collectableFee;
}

Some files were not shown because too many files have changed in this diff Show More