IBM MQ test drafts

This commit is contained in:
Radek Davidek 2026-03-17 09:24:25 +01:00
parent ef4562ab74
commit ce54e336ba
11 changed files with 1247 additions and 3 deletions

View File

@ -0,0 +1,516 @@
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 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).
*/
PayloadPhase asJson();
/**
* Set message format to XML.
*/
PayloadPhase asXml();
/**
* Set message format to EBCDIC (IBM-870).
*/
PayloadPhase asEbcdic();
/**
* Set message format to UTF-8 (CCSID 1208).
*/
PayloadPhase 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);
/**
* 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);
}
/**
* 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 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 PayloadPhase asJson() {
this.format = MqMessageFormat.JSON;
return this;
}
@Override
public PayloadPhase asXml() {
this.format = MqMessageFormat.XML;
return this;
}
@Override
public PayloadPhase asEbcdic() {
this.format = MqMessageFormat.EBCDIC_870;
return this;
}
@Override
public PayloadPhase 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 void send() {
String finalPayload = buildPayload();
Map<String, String> properties = new HashMap<>();
if (logicalQueue != null) {
properties.put("LogicalQueue", logicalQueue.name());
}
if (selector != null && !selector.isBlank()) {
properties.put("Selector", selector);
}
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];
if (!current.containsKey(part)) {
current.put(part, new HashMap<String, Object>());
}
current = (Map<String, Object>) current.get(part);
}
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];
if (!current.containsKey(part)) {
current.put(part, new ArrayList<Map<String, Object>>());
}
current = (Map<String, Object>) current.get(part);
}
List<Map<String, Object>> array = (List<Map<String, Object>>) current.get(parts[parts.length - 1]);
if (array == null) {
array = new ArrayList<>();
current.put(parts[parts.length - 1], 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

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,7 +6,7 @@
<artifactId>tests</artifactId>
<version>2.29-SNAPSHOT</version>
<properties>
<harness.version>7.55.820</harness.version>
<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>
@ -15,6 +15,7 @@
<groupId>cz.moneta.test</groupId>
<artifactId>harness</artifactId>
<version>${harness.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>

View File

@ -23,6 +23,7 @@ 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.kasanova.Kasanova;
import cz.moneta.test.dsl.mobile.smartbanking.home.Sb;
import cz.moneta.test.dsl.monetaapiportal.MonetaApiPortal;
@ -282,6 +283,10 @@ public class Harness extends BaseStoreAccessor {
return new Cashman(this);
}
public ImqFirstVision withImqFirstVision() {
return new ImqFirstVision(this);
}
private void initGenerators() {
addGenerator(RC, new RcGenerator());
addGenerator(FIRST_NAME, new FirstNameGenerator());

View File

@ -0,0 +1,187 @@
package cz.moneta.test.dsl.imq;
import cz.moneta.test.dsl.Harness;
import cz.moneta.test.harness.endpoints.imq.ImqFirstVisionEndpoint;
import cz.moneta.test.harness.endpoints.imq.ImqFirstVisionQueue;
import cz.moneta.test.harness.messaging.ReceivedMessage;
import cz.moneta.test.harness.messaging.exception.MessagingTimeoutException;
import cz.moneta.test.harness.support.messaging.ImqRequest;
import cz.moneta.test.harness.messaging.MqMessageFormat;
import java.time.Duration;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
/**
* IBM MQ First Vision DSL.
* <p>
* Usage example:
* <pre>{@code
* harness.withImqFirstVision()
* .toQueue(ImqFirstVisionQueue.PAYMENT_NOTIFICATIONS)
* .withPayload("{\"paymentId\": \"PAY-123\"}")
* .send();
*
* harness.withImqFirstVision()
* .fromQueue(ImqFirstVisionQueue.PAYMENT_NOTIFICATIONS)
* .receiveWhere(msg -> msg.extract("paymentId").equals("PAY-123"))
* .withTimeout(10, TimeUnit.SECONDS)
* .andAssertFieldValue("result", "OK");
* }</pre>
* </p>
*/
public class ImqFirstVision {
private final Harness harness;
public ImqFirstVision(Harness harness) {
this.harness = harness;
}
/**
* Start building a message to send to a queue.
*/
public ImqRequest.QueuePhase toQueue(ImqFirstVisionQueue queue) {
ImqFirstVisionEndpoint endpoint = harness.getEndpoint(ImqFirstVisionEndpoint.class);
return ImqRequest.toQueue(endpoint, queue);
}
/**
* Start building a message to receive from a queue.
*/
public ImqRequest.ReceivePhase fromQueue(ImqFirstVisionQueue queue) {
ImqFirstVisionEndpoint endpoint = harness.getEndpoint(ImqFirstVisionEndpoint.class);
return ImqRequest.fromQueue(endpoint, queue);
}
/**
* Send a JSON message to a queue.
*/
public void sendToQueue(ImqFirstVisionQueue queue, String payload) {
sendToQueue(queue, payload, MqMessageFormat.JSON, null);
}
/**
* Send a message to a queue with specified format.
*/
public void sendToQueue(ImqFirstVisionQueue queue, String payload, MqMessageFormat format) {
sendToQueue(queue, payload, format, null);
}
/**
* Send a message to a queue with specified format and properties.
*/
public void sendToQueue(ImqFirstVisionQueue queue, String payload, MqMessageFormat format,
Map<String, String> properties) {
ImqFirstVisionEndpoint endpoint = harness.getEndpoint(ImqFirstVisionEndpoint.class);
endpoint.send(queue, payload, format, properties);
}
/**
* Send a JSON message to a queue by physical name.
*/
public void sendToQueue(String queueName, String payload) {
sendToQueue(queueName, payload, MqMessageFormat.JSON, null);
}
/**
* Send a message to a queue by physical name with specified format.
*/
public void sendToQueue(String queueName, String payload, MqMessageFormat format) {
sendToQueue(queueName, payload, format, null);
}
/**
* Send a message to a queue by physical name with specified format and properties.
*/
public void sendToQueue(String queueName, String payload, MqMessageFormat format,
Map<String, String> properties) {
ImqFirstVisionEndpoint endpoint = harness.getEndpoint(ImqFirstVisionEndpoint.class);
endpoint.send(queueName, payload, format, properties);
}
/**
* Receive a message from a queue with timeout.
*/
public ReceivedMessage receiveFromQueue(ImqFirstVisionQueue queue, Duration timeout) {
return receiveFromQueue(queue, null, MqMessageFormat.JSON, timeout);
}
/**
* Receive a message from a queue with selector and timeout.
*/
public ReceivedMessage receiveFromQueue(ImqFirstVisionQueue queue, String selector, Duration timeout) {
return receiveFromQueue(queue, selector, MqMessageFormat.JSON, timeout);
}
/**
* Receive a message from a queue with format and timeout.
*/
public ReceivedMessage receiveFromQueue(ImqFirstVisionQueue queue, MqMessageFormat format, Duration timeout) {
return receiveFromQueue(queue, null, format, timeout);
}
/**
* Receive a message from a queue with selector, format and timeout.
*/
public ReceivedMessage receiveFromQueue(ImqFirstVisionQueue queue, String selector,
MqMessageFormat format, Duration timeout) {
ImqFirstVisionEndpoint endpoint = harness.getEndpoint(ImqFirstVisionEndpoint.class);
return endpoint.receive(queue, selector, format, timeout);
}
/**
* Receive a message matching predicate from a queue.
*/
public ReceivedMessage receiveFromQueue(ImqFirstVisionQueue queue, Predicate<ReceivedMessage> filter,
Duration timeout) {
long startTime = System.currentTimeMillis();
long timeoutMs = timeout.toMillis();
while (System.currentTimeMillis() - startTime < timeoutMs) {
try {
ReceivedMessage message = receiveFromQueue(queue, MqMessageFormat.JSON, Duration.ofSeconds(1));
if (filter.test(message)) {
return message;
}
} catch (MessagingTimeoutException e) {
// Continue polling
}
}
throw new MessagingTimeoutException(
"No message matching filter found on queue '" + queue.getConfigKey() +
"' within " + timeout.toMillis() + "ms");
}
/**
* Browse messages from a queue (non-destructive).
*/
public List<ReceivedMessage> browseQueue(ImqFirstVisionQueue queue, int maxMessages) {
return browseQueue(queue, null, MqMessageFormat.JSON, maxMessages);
}
/**
* Browse messages from a queue with selector (non-destructive).
*/
public List<ReceivedMessage> browseQueue(ImqFirstVisionQueue queue, String selector, int maxMessages) {
return browseQueue(queue, selector, MqMessageFormat.JSON, maxMessages);
}
/**
* Browse messages from a queue with format and max count (non-destructive).
*/
public List<ReceivedMessage> browseQueue(ImqFirstVisionQueue queue, MqMessageFormat format, int maxMessages) {
return browseQueue(queue, null, format, maxMessages);
}
/**
* Browse messages from a queue with selector, format and max count (non-destructive).
*/
public List<ReceivedMessage> browseQueue(ImqFirstVisionQueue queue, String selector,
MqMessageFormat format, int maxMessages) {
ImqFirstVisionEndpoint endpoint = harness.getEndpoint(ImqFirstVisionEndpoint.class);
return endpoint.browse(queue, selector, format, maxMessages);
}
}

View File

@ -76,4 +76,21 @@ endpoints.szr-mock-api.url=https://api-szr.ppe.moneta-containers.net
endpoints.forte.url=https://forteppe.ux.mbid.cz/portal/home/
#Exevido
endpoints.exevido.url=https://exevido.ppe.moneta-containers.net/#/auth/login
endpoints.exevido.url=https://exevido.ppe.moneta-containers.net/#/auth/login
#IBM MQ First Vision
endpoints.imq-first-vision.connection-name-list=mq9multipe5x(1414),mq9multipe6x(1414)
endpoints.imq-first-vision.channel=CLIENT.CHANNEL
endpoints.imq-first-vision.queue-manager=MVSW2PPE
endpoints.imq-first-vision.ssl-cipher-suite=TLS_RSA_WITH_AES_256_CBC_SHA256
#IBM MQ queues
endpoints.imq-first-vision.payment-notifications.queue=MVSW2PPE.DELIVERY.NOTIFICATION
endpoints.imq-first-vision.payment-request.queue=MVSW2PPE.DELIVERY.REQUEST
endpoints.imq-first-vision.mf-requests.queue=MVSW2PPE.MF.REQUESTS
endpoints.imq-first-vision.mf-responses.queue=MVSW2PPE.MF.RESPONSES
endpoints.imq-first-vision.mf-ebcdic.queue=MVSW2PPE.MF.EBCDIC
endpoints.imq-first-vision.mf-utf8.queue=MVSW2PPE.MF.UTF8
#Vault path for IBM MQ credentials
vault.imq-first-vision.secrets.path=/kv/autotesty/ppe/imq-first-vision

View File

@ -97,4 +97,21 @@ endpoints.szr-mock-api.url=https://api-szr.tst.moneta-containers.net
endpoints.exevido.url=https://exevido.tst.moneta-containers.net/#/auth/login
#Cashman
endpoints.cashman.url=https://cashmantst.mbid.cz/
endpoints.cashman.url=https://cashmantst.mbid.cz/
#IBM MQ First Vision
endpoints.imq-first-vision.connection-name-list=mq9multitst5x(1414),mq9multitst6x(1414)
endpoints.imq-first-vision.channel=CLIENT.CHANNEL
endpoints.imq-first-vision.queue-manager=MVSW2TST3
endpoints.imq-first-vision.ssl-cipher-suite=TLS_RSA_WITH_AES_256_CBC_SHA256
#IBM MQ queues
endpoints.imq-first-vision.payment-notifications.queue=MVSW2TST3.DELIVERY.NOTIFICATION
endpoints.imq-first-vision.payment-request.queue=MVSW2TST3.DELIVERY.REQUEST
endpoints.imq-first-vision.mf-requests.queue=MVSW2TST3.MF.REQUESTS
endpoints.imq-first-vision.mf-responses.queue=MVSW2TST3.MF.RESPONSES
endpoints.imq-first-vision.mf-ebcdic.queue=MVSW2TST3.MF.EBCDIC
endpoints.imq-first-vision.mf-utf8.queue=MVSW2TST3.MF.UTF8
#Vault path for IBM MQ credentials
vault.imq-first-vision.secrets.path=/kv/autotesty/tst1/imq-first-vision