jms -> mq

This commit is contained in:
Radek Davidek 2026-04-20 10:17:56 +02:00
parent 70c4540c4a
commit ab7d898b8e
5554 changed files with 439957 additions and 439459 deletions

View File

@ -1,6 +1,6 @@
.idea .idea
target target
*.iml *.iml
.classpath .classpath
.project .project
.settings .settings

View File

@ -1,3 +1,3 @@
# Test Harness # Test Harness
This repo conatins Test Harness, code name "Hercules". This repo conatins Test Harness, code name "Hercules".

View File

@ -30,7 +30,6 @@
<commons-configuration.version>1.6</commons-configuration.version> <commons-configuration.version>1.6</commons-configuration.version>
<cxf.version>4.0.3</cxf.version> <cxf.version>4.0.3</cxf.version>
<ibm.mq.version>9.4.5.0</ibm.mq.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> <kafka.clients.version>3.7.0</kafka.clients.version>
<confluent.version>7.6.0</confluent.version> <confluent.version>7.6.0</confluent.version>
<assertj.version>3.24.2</assertj.version> <assertj.version>3.24.2</assertj.version>
@ -296,12 +295,6 @@
<artifactId>com.ibm.mq.allclient</artifactId> <artifactId>com.ibm.mq.allclient</artifactId>
<version>${ibm.mq.version}</version> <version>${ibm.mq.version}</version>
</dependency> </dependency>
<dependency>
<groupId>javax.jms</groupId>
<artifactId>javax.jms-api</artifactId>
<version>${javax.jms.version}</version>
</dependency>
<!-- Kafka dependencies --> <!-- Kafka dependencies -->
<dependency> <dependency>
<groupId>org.apache.kafka</groupId> <groupId>org.apache.kafka</groupId>

View File

@ -1,418 +1,418 @@
package cz.moneta.test.harness; package cz.moneta.test.harness;
import cz.moneta.test.harness.annotations.*; import cz.moneta.test.harness.annotations.*;
import cz.moneta.test.harness.config.ConfigProvider; import cz.moneta.test.harness.config.ConfigProvider;
import cz.moneta.test.harness.constants.HarnessConfigConstants; import cz.moneta.test.harness.constants.HarnessConfigConstants;
import cz.moneta.test.harness.context.BaseStoreAccessor; import cz.moneta.test.harness.context.BaseStoreAccessor;
import cz.moneta.test.harness.context.StoreAccessor; import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.data.Browser; import cz.moneta.test.harness.data.Browser;
import cz.moneta.test.harness.endpoints.Endpoint; import cz.moneta.test.harness.endpoints.Endpoint;
import cz.moneta.test.harness.endpoints.MobileEndpoint; import cz.moneta.test.harness.endpoints.MobileEndpoint;
import cz.moneta.test.harness.endpoints.WebEndpoint; import cz.moneta.test.harness.endpoints.WebEndpoint;
import cz.moneta.test.harness.endpoints.greenscreen.GreenScreenEndpoint; import cz.moneta.test.harness.endpoints.greenscreen.GreenScreenEndpoint;
import cz.moneta.test.harness.endpoints.jira.JiraTestResultPublisher; import cz.moneta.test.harness.endpoints.jira.JiraTestResultPublisher;
import cz.moneta.test.harness.exception.BeforeAllHarnessException; import cz.moneta.test.harness.exception.BeforeAllHarnessException;
import cz.moneta.test.harness.exception.HarnessConfigurationException; import cz.moneta.test.harness.exception.HarnessConfigurationException;
import cz.moneta.test.harness.exception.HarnessException; import cz.moneta.test.harness.exception.HarnessException;
import cz.moneta.test.harness.support.auth.AuthSupport; import cz.moneta.test.harness.support.auth.AuthSupport;
import cz.moneta.test.harness.support.auth.Credentials; import cz.moneta.test.harness.support.auth.Credentials;
import cz.moneta.test.harness.support.auth.Key; import cz.moneta.test.harness.support.auth.Key;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.junit.jupiter.api.extension.*; import org.junit.jupiter.api.extension.*;
import org.junit.jupiter.api.extension.ExtensionContext.Namespace; import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
import org.junit.jupiter.api.extension.ExtensionContext.Store; import org.junit.jupiter.api.extension.ExtensionContext.Store;
import org.junit.platform.engine.UniqueId; import org.junit.platform.engine.UniqueId;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Parameter; import java.lang.reflect.Parameter;
import java.util.*; import java.util.*;
import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.CopyOnWriteArraySet;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
public class HarnessJunit5Extension implements BeforeAllCallback, BeforeEachCallback, ParameterResolver, public class HarnessJunit5Extension implements BeforeAllCallback, BeforeEachCallback, ParameterResolver,
TestExecutionExceptionHandler, AfterEachCallback, ExecutionCondition, LifecycleMethodExecutionExceptionHandler, TestExecutionExceptionHandler, AfterEachCallback, ExecutionCondition, LifecycleMethodExecutionExceptionHandler,
BeforeTestExecutionCallback, AfterAllCallback { BeforeTestExecutionCallback, AfterAllCallback {
private static final Logger logger = LogManager.getLogger(HarnessJunit5Extension.class); private static final Logger logger = LogManager.getLogger(HarnessJunit5Extension.class);
public static final String ACTIVE_ENDPOINTS = "ACTIVE_ENDPOINTS"; public static final String ACTIVE_ENDPOINTS = "ACTIVE_ENDPOINTS";
private static final Namespace ENDPOINT_NAMESPACE = Namespace.create("ENDPOINT"); private static final Namespace ENDPOINT_NAMESPACE = Namespace.create("ENDPOINT");
protected static final Namespace CONFIG_NAMESPACE = Namespace.create("CONFIG"); protected static final Namespace CONFIG_NAMESPACE = Namespace.create("CONFIG");
protected static final Namespace GENERATORS_NAMESPACE = Namespace.create("GENERATORS"); protected static final Namespace GENERATORS_NAMESPACE = Namespace.create("GENERATORS");
private static final String RESOLUTION_PASS = "PASS"; private static final String RESOLUTION_PASS = "PASS";
private static final String RESOLUTION_FAIL = "FAIL"; private static final String RESOLUTION_FAIL = "FAIL";
private static final String BROWSER_CONFIG_KEY = "browser"; private static final String BROWSER_CONFIG_KEY = "browser";
private boolean hasBeforeAllFail = false; private boolean hasBeforeAllFail = false;
private String beforeAllThrowableMessage; private String beforeAllThrowableMessage;
@Override @Override
public void beforeAll(ExtensionContext extensionContext) { public void beforeAll(ExtensionContext extensionContext) {
extensionContext.getStore(ENDPOINT_NAMESPACE).put(ACTIVE_ENDPOINTS, new CopyOnWriteArraySet<Class<? extends Endpoint>>()); extensionContext.getStore(ENDPOINT_NAMESPACE).put(ACTIVE_ENDPOINTS, new CopyOnWriteArraySet<Class<? extends Endpoint>>());
} }
@Override @Override
public void beforeEach(ExtensionContext extensionContext) { public void beforeEach(ExtensionContext extensionContext) {
extensionContext.getStore(ENDPOINT_NAMESPACE).put(ACTIVE_ENDPOINTS, new CopyOnWriteArraySet<Class<? extends Endpoint>>()); extensionContext.getStore(ENDPOINT_NAMESPACE).put(ACTIVE_ENDPOINTS, new CopyOnWriteArraySet<Class<? extends Endpoint>>());
extensionContext.getStore(ExtensionContext.Namespace.create("UPLOADS")).remove("jira.uploads"); extensionContext.getStore(ExtensionContext.Namespace.create("UPLOADS")).remove("jira.uploads");
} }
@Override @Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
return Stream.<Supplier<Optional<?>>>of( return Stream.<Supplier<Optional<?>>>of(
() -> getTestContext(parameterContext), () -> getTestContext(parameterContext),
() -> getAuthKey(parameterContext)) () -> getAuthKey(parameterContext))
.map(Supplier::get) .map(Supplier::get)
.anyMatch(Optional::isPresent); .anyMatch(Optional::isPresent);
} }
@Override @Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) { public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) {
storeCurrentTestName(extensionContext); storeCurrentTestName(extensionContext);
return Stream.<Supplier<Optional<?>>>of( return Stream.<Supplier<Optional<?>>>of(
() -> getTestContext(parameterContext).map(tc -> { () -> getTestContext(parameterContext).map(tc -> {
try { try {
return tc.getType().getConstructor(Store.class, Store.class, Store.class, Store.class, Store.class) return tc.getType().getConstructor(Store.class, Store.class, Store.class, Store.class, Store.class)
.newInstance( .newInstance(
extensionContext.getRoot().getStore(Namespace.GLOBAL), extensionContext.getRoot().getStore(Namespace.GLOBAL),
extensionContext.getStore(Namespace.GLOBAL), extensionContext.getStore(Namespace.GLOBAL),
extensionContext.getStore(ENDPOINT_NAMESPACE), extensionContext.getStore(ENDPOINT_NAMESPACE),
extensionContext.getStore(CONFIG_NAMESPACE), extensionContext.getStore(CONFIG_NAMESPACE),
extensionContext.getStore(GENERATORS_NAMESPACE)); extensionContext.getStore(GENERATORS_NAMESPACE));
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
throw new ParameterResolutionException("Failed to initialize parameter " + throw new ParameterResolutionException("Failed to initialize parameter " +
parameterContext.getParameter().getType().getSimpleName(), e); parameterContext.getParameter().getType().getSimpleName(), e);
} }
}), }),
() -> getAuthKey(parameterContext) () -> getAuthKey(parameterContext)
.map(ak -> ak.getAnnotation(Key.class).value()) .map(ak -> ak.getAnnotation(Key.class).value())
.map(ak -> AuthSupport.getCredentials(ak, new BaseStoreAccessor( .map(ak -> AuthSupport.getCredentials(ak, new BaseStoreAccessor(
extensionContext.getRoot().getStore(Namespace.GLOBAL), extensionContext.getRoot().getStore(Namespace.GLOBAL),
extensionContext.getStore(Namespace.GLOBAL), extensionContext.getStore(Namespace.GLOBAL),
extensionContext.getStore(ENDPOINT_NAMESPACE), extensionContext.getStore(ENDPOINT_NAMESPACE),
extensionContext.getStore(CONFIG_NAMESPACE), extensionContext.getStore(CONFIG_NAMESPACE),
extensionContext.getStore(GENERATORS_NAMESPACE)) { extensionContext.getStore(GENERATORS_NAMESPACE)) {
}))) })))
.map(Supplier::get) .map(Supplier::get)
.filter(Optional::isPresent) .filter(Optional::isPresent)
.map(Optional::get) .map(Optional::get)
.findFirst() .findFirst()
.orElseThrow(() -> new ParameterResolutionException("Parameter of type " + .orElseThrow(() -> new ParameterResolutionException("Parameter of type " +
parameterContext.getParameter().getType().getSimpleName() + parameterContext.getParameter().getType().getSimpleName() +
" is not supported")); " is not supported"));
} }
@Override @Override
public void handleTestExecutionException(ExtensionContext extensionContext, Throwable throwable) throws Throwable { public void handleTestExecutionException(ExtensionContext extensionContext, Throwable throwable) throws Throwable {
processException(extensionContext); processException(extensionContext);
throw Optional.of(throwable) throw Optional.of(throwable)
.filter(HarnessException.class::isInstance) .filter(HarnessException.class::isInstance)
.map(AssertionError::new) .map(AssertionError::new)
.map(Throwable.class::cast) .map(Throwable.class::cast)
.orElse(throwable); .orElse(throwable);
} }
@Override @Override
public void handleBeforeAllMethodExecutionException(ExtensionContext extensionContext, Throwable throwable) { public void handleBeforeAllMethodExecutionException(ExtensionContext extensionContext, Throwable throwable) {
//This is workaround for possible bug in JUnit5 and Surefire - in case of beforeAll exception are test skipped instead of failed //This is workaround for possible bug in JUnit5 and Surefire - in case of beforeAll exception are test skipped instead of failed
//https://github.com/junit-team/junit5/issues/2178 //https://github.com/junit-team/junit5/issues/2178
hasBeforeAllFail = true; hasBeforeAllFail = true;
beforeAllThrowableMessage = throwable.toString(); beforeAllThrowableMessage = throwable.toString();
processException(extensionContext); processException(extensionContext);
JiraTestResultPublisher.logJiraTestResult(extensionContext, throwable); JiraTestResultPublisher.logJiraTestResult(extensionContext, throwable);
logger.error("Method @BeforeAll in test class {} failed with exception: {}.", () -> extensionContext.getRequiredTestClass().getName(), () -> beforeAllThrowableMessage); logger.error("Method @BeforeAll in test class {} failed with exception: {}.", () -> extensionContext.getRequiredTestClass().getName(), () -> beforeAllThrowableMessage);
} }
@Override @Override
public void beforeTestExecution(ExtensionContext extensionContext) { public void beforeTestExecution(ExtensionContext extensionContext) {
if (hasBeforeAllFail) { if (hasBeforeAllFail) {
throw new BeforeAllHarnessException("Exception in before all occurred. Original exception message: " + beforeAllThrowableMessage); throw new BeforeAllHarnessException("Exception in before all occurred. Original exception message: " + beforeAllThrowableMessage);
} }
} }
private void processException(ExtensionContext extensionContext) { private void processException(ExtensionContext extensionContext) {
Set<String> files = processWebEndpoints(extensionContext); Set<String> files = processWebEndpoints(extensionContext);
processMobileEndpoints(extensionContext); processMobileEndpoints(extensionContext);
processGreenscreenEndpoints(extensionContext); processGreenscreenEndpoints(extensionContext);
Store uploads = extensionContext.getStore(Namespace.create("UPLOADS")); Store uploads = extensionContext.getStore(Namespace.create("UPLOADS"));
uploads.put("jira.uploads", files); uploads.put("jira.uploads", files);
} }
private void processGreenscreenEndpoints(ExtensionContext extensionContext) { private void processGreenscreenEndpoints(ExtensionContext extensionContext) {
getActiveEndpoints(extensionContext).stream() getActiveEndpoints(extensionContext).stream()
.filter(p -> GreenScreenEndpoint.class.isAssignableFrom(p.getLeft())) .filter(p -> GreenScreenEndpoint.class.isAssignableFrom(p.getLeft()))
.map(c -> extensionContext.getStore(ENDPOINT_NAMESPACE).get(c, GreenScreenEndpoint.class)) .map(c -> extensionContext.getStore(ENDPOINT_NAMESPACE).get(c, GreenScreenEndpoint.class))
.forEach(e -> logger.info("Failed on screen:\n" + e.getText(1, 1, 0))); .forEach(e -> logger.info("Failed on screen:\n" + e.getText(1, 1, 0)));
} }
private void processMobileEndpoints(ExtensionContext extensionContext) { private void processMobileEndpoints(ExtensionContext extensionContext) {
getActiveEndpoints(extensionContext).stream() getActiveEndpoints(extensionContext).stream()
.filter(p -> MobileEndpoint.class.isAssignableFrom(p.getLeft())) .filter(p -> MobileEndpoint.class.isAssignableFrom(p.getLeft()))
.map(c -> extensionContext.getStore(ENDPOINT_NAMESPACE).get(c, MobileEndpoint.class)) .map(c -> extensionContext.getStore(ENDPOINT_NAMESPACE).get(c, MobileEndpoint.class))
.forEach(mobileEndpoint -> { .forEach(mobileEndpoint -> {
mobileEndpoint.takeSnapshot(extensionContext.getDisplayName()); mobileEndpoint.takeSnapshot(extensionContext.getDisplayName());
mobileEndpoint.saveSources(extensionContext.getDisplayName()); mobileEndpoint.saveSources(extensionContext.getDisplayName());
}); });
} }
private Set<String> processWebEndpoints(ExtensionContext extensionContext) { private Set<String> processWebEndpoints(ExtensionContext extensionContext) {
return getActiveEndpoints(extensionContext).stream() return getActiveEndpoints(extensionContext).stream()
.filter(p -> WebEndpoint.class.isAssignableFrom(p.getLeft())) .filter(p -> WebEndpoint.class.isAssignableFrom(p.getLeft()))
.map(c -> extensionContext.getStore(ENDPOINT_NAMESPACE).get(c, WebEndpoint.class)) .map(c -> extensionContext.getStore(ENDPOINT_NAMESPACE).get(c, WebEndpoint.class))
.map(webEndpoint -> { .map(webEndpoint -> {
Set<String> files = new HashSet<>(); Set<String> files = new HashSet<>();
String filePrefix = (extensionContext.getTestClass().get().getSimpleName() + "_" + extensionContext.getDisplayName()).replaceAll("[\\\\/:*?\"<>|]", ""); String filePrefix = (extensionContext.getTestClass().get().getSimpleName() + "_" + extensionContext.getDisplayName()).replaceAll("[\\\\/:*?\"<>|]", "");
files.add(webEndpoint.takeSnapshot(filePrefix)); files.add(webEndpoint.takeSnapshot(filePrefix));
files.addAll(webEndpoint.captureLogs(filePrefix)); files.addAll(webEndpoint.captureLogs(filePrefix));
webEndpoint.captureDom(filePrefix); webEndpoint.captureDom(filePrefix);
return files; return files;
}) })
.filter(set -> !set.isEmpty()) .filter(set -> !set.isEmpty())
.collect(HashSet::new, Set::addAll, Set::addAll); .collect(HashSet::new, Set::addAll, Set::addAll);
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private Set<Pair<Class<? extends Endpoint>, Object[]>> getActiveEndpoints(ExtensionContext extensionContext) { private Set<Pair<Class<? extends Endpoint>, Object[]>> getActiveEndpoints(ExtensionContext extensionContext) {
return extensionContext.getStore(ENDPOINT_NAMESPACE).get(ACTIVE_ENDPOINTS, Set.class); return extensionContext.getStore(ENDPOINT_NAMESPACE).get(ACTIVE_ENDPOINTS, Set.class);
} }
private void closeActiveEndpoints(ExtensionContext extensionContext) { private void closeActiveEndpoints(ExtensionContext extensionContext) {
getActiveEndpoints(extensionContext).stream() getActiveEndpoints(extensionContext).stream()
.filter(Objects::nonNull) .filter(Objects::nonNull)
.map(c -> extensionContext.getStore(ENDPOINT_NAMESPACE).remove(c, Endpoint.class)) .map(c -> extensionContext.getStore(ENDPOINT_NAMESPACE).remove(c, Endpoint.class))
.peek(e -> { .peek(e -> {
if (e instanceof MobileEndpoint) { if (e instanceof MobileEndpoint) {
((MobileEndpoint) e).captureVideo(extensionContext.getDisplayName()); ((MobileEndpoint) e).captureVideo(extensionContext.getDisplayName());
} }
}) })
.forEach(Endpoint::close); .forEach(Endpoint::close);
} }
@Override @Override
public void afterEach(ExtensionContext extensionContext) { public void afterEach(ExtensionContext extensionContext) {
closeActiveEndpoints(extensionContext); closeActiveEndpoints(extensionContext);
if (!hasBeforeAllFail) { if (!hasBeforeAllFail) {
JiraTestResultPublisher.logJiraTestResult(extensionContext); JiraTestResultPublisher.logJiraTestResult(extensionContext);
} }
logTestResult(extensionContext); logTestResult(extensionContext);
} }
@Override @Override
public void afterAll(ExtensionContext extensionContext) { public void afterAll(ExtensionContext extensionContext) {
//Closes only endpoints initialized in BeforeAll and not used in test methods //Closes only endpoints initialized in BeforeAll and not used in test methods
closeActiveEndpoints(extensionContext); closeActiveEndpoints(extensionContext);
} }
private Optional<Parameter> getAuthKey(ParameterContext parameterContext) { private Optional<Parameter> getAuthKey(ParameterContext parameterContext) {
return Optional.of(parameterContext.getParameter()) return Optional.of(parameterContext.getParameter())
.filter(p -> p.getAnnotation(Key.class) != null) .filter(p -> p.getAnnotation(Key.class) != null)
.filter(p -> Credentials.class.isAssignableFrom(p.getType())); .filter(p -> Credentials.class.isAssignableFrom(p.getType()));
} }
private Optional<Parameter> getTestContext(ParameterContext parameterContext) { private Optional<Parameter> getTestContext(ParameterContext parameterContext) {
return Optional.of(parameterContext.getParameter()) return Optional.of(parameterContext.getParameter())
.filter(p -> p.getType().getAnnotation(TestContext.class) != null) .filter(p -> p.getType().getAnnotation(TestContext.class) != null)
.filter(p -> StoreAccessor.class.isAssignableFrom(p.getType())); .filter(p -> StoreAccessor.class.isAssignableFrom(p.getType()));
} }
@Override @Override
public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {
if (isClassExtensionContext(context)) { if (isClassExtensionContext(context)) {
return evaluateClassExecutionContext(context); return evaluateClassExecutionContext(context);
} }
Environment environment = getCurrentEnvironment(context); Environment environment = getCurrentEnvironment(context);
return context.getTestMethod() return context.getTestMethod()
.filter(m -> environment != null) .filter(m -> environment != null)
.flatMap(m -> Stream.<Supplier<Optional<ConditionEvaluationResult>>>of( .flatMap(m -> Stream.<Supplier<Optional<ConditionEvaluationResult>>>of(
() -> checkTestCaseEnvironments(environment, m, context.getDisplayName()), () -> checkTestCaseEnvironments(environment, m, context.getDisplayName()),
() -> checkDefectEnvironments(environment, m, context.getDisplayName(), context), () -> checkDefectEnvironments(environment, m, context.getDisplayName(), context),
() -> checkGlobalParameterSet(context, m, context.getDisplayName()), () -> checkGlobalParameterSet(context, m, context.getDisplayName()),
() -> checkJiraReportingRules(context, m, context.getDisplayName())) () -> checkJiraReportingRules(context, m, context.getDisplayName()))
.map(Supplier::get) .map(Supplier::get)
.filter(Optional::isPresent) .filter(Optional::isPresent)
.map(Optional::get) .map(Optional::get)
.findFirst()) .findFirst())
.orElse(enableEvaluationAndLogExecutionStart(context)); .orElse(enableEvaluationAndLogExecutionStart(context));
} }
private boolean isClassExtensionContext(ExtensionContext extensionContext) { private boolean isClassExtensionContext(ExtensionContext extensionContext) {
String simpleTestName = extensionContext.getTestClass() String simpleTestName = extensionContext.getTestClass()
.get() .get()
.getSimpleName(); .getSimpleName();
return !extensionContext.getTestMethod().isPresent() && extensionContext.getDisplayName().equals(simpleTestName); return !extensionContext.getTestMethod().isPresent() && extensionContext.getDisplayName().equals(simpleTestName);
} }
private ConditionEvaluationResult evaluateClassExecutionContext(ExtensionContext extensionContext) { private ConditionEvaluationResult evaluateClassExecutionContext(ExtensionContext extensionContext) {
ConfigProvider.readConfig().forEach((k, v) -> extensionContext.getStore(CONFIG_NAMESPACE).put(k, v)); ConfigProvider.readConfig().forEach((k, v) -> extensionContext.getStore(CONFIG_NAMESPACE).put(k, v));
Browser definedBrowser = getBrowserFromConfig(extensionContext); Browser definedBrowser = getBrowserFromConfig(extensionContext);
String className = getClassCanonicalName(extensionContext); String className = getClassCanonicalName(extensionContext);
Class<?> testClass = extensionContext.getTestClass().get(); Class<?> testClass = extensionContext.getTestClass().get();
return Stream.<Supplier<Optional<Browser[]>>>of( return Stream.<Supplier<Optional<Browser[]>>>of(
() -> Optional.ofNullable(testClass.getAnnotation(TestScenario.class)) () -> Optional.ofNullable(testClass.getAnnotation(TestScenario.class))
.map(TestScenario::browsers), .map(TestScenario::browsers),
() -> Optional.ofNullable(testClass.getAnnotation(NonConcurrentTestScenario.class)) () -> Optional.ofNullable(testClass.getAnnotation(NonConcurrentTestScenario.class))
.map(NonConcurrentTestScenario::browsers), .map(NonConcurrentTestScenario::browsers),
() -> Optional.ofNullable(testClass.getAnnotation(TestScenarioWithOrder.class)) () -> Optional.ofNullable(testClass.getAnnotation(TestScenarioWithOrder.class))
.map(TestScenarioWithOrder::browsers)) .map(TestScenarioWithOrder::browsers))
.map(Supplier::get) .map(Supplier::get)
.filter(Optional::isPresent) .filter(Optional::isPresent)
.map(Optional::get) .map(Optional::get)
.findFirst() .findFirst()
.filter(browsers -> Arrays.stream(browsers) .filter(browsers -> Arrays.stream(browsers)
.noneMatch(definedBrowser::equals)) .noneMatch(definedBrowser::equals))
.map(browsers -> ConditionEvaluationResult.disabled("Tests in class " + .map(browsers -> ConditionEvaluationResult.disabled("Tests in class " +
className + " disabled. It is marked to only run on " + className + " disabled. It is marked to only run on " +
Arrays.stream(browsers) Arrays.stream(browsers)
.map(Enum::name) .map(Enum::name)
.collect(Collectors.joining(", ")))) .collect(Collectors.joining(", "))))
.orElse(enableClassForTests(className)); .orElse(enableClassForTests(className));
} }
private ConditionEvaluationResult enableClassForTests(String className) { private ConditionEvaluationResult enableClassForTests(String className) {
logger.info("Test execution in test class {} started.", () -> className); logger.info("Test execution in test class {} started.", () -> className);
return ConditionEvaluationResult.enabled("Tests in " + className + " enabled."); return ConditionEvaluationResult.enabled("Tests in " + className + " enabled.");
} }
private Optional<ConditionEvaluationResult> checkGlobalParameterSet(ExtensionContext extensionContext, Method testMethod, String testName) { private Optional<ConditionEvaluationResult> checkGlobalParameterSet(ExtensionContext extensionContext, Method testMethod, String testName) {
Store rootStore = extensionContext.getRoot().getStore(Namespace.GLOBAL); Store rootStore = extensionContext.getRoot().getStore(Namespace.GLOBAL);
return Optional.of(Arrays.stream(testMethod.getAnnotationsByType(EnableIfSet.class)) return Optional.of(Arrays.stream(testMethod.getAnnotationsByType(EnableIfSet.class))
.map(annotation -> { .map(annotation -> {
if (rootStore == null) { if (rootStore == null) {
return ConditionEvaluationResult.disabled(String.format("Test %s is disabled: Need to check key '%s' " + return ConditionEvaluationResult.disabled(String.format("Test %s is disabled: Need to check key '%s' " +
"to be set, but root context is null. Skipping...", testName, annotation.globalKey())); "to be set, but root context is null. Skipping...", testName, annotation.globalKey()));
} }
Object value = rootStore.get(annotation.globalKey()); Object value = rootStore.get(annotation.globalKey());
if (value == null) { if (value == null) {
return ConditionEvaluationResult.disabled(String.format("Test %s is disabled: Key '%s' " + return ConditionEvaluationResult.disabled(String.format("Test %s is disabled: Key '%s' " +
"is not set in global context. Skipping...", testName, annotation.globalKey())); "is not set in global context. Skipping...", testName, annotation.globalKey()));
} }
return null; return null;
}).filter(Objects::nonNull) }).filter(Objects::nonNull)
.findFirst() .findFirst()
.orElse(ConditionEvaluationResult.enabled("Test " + testName + " enabled"))); .orElse(ConditionEvaluationResult.enabled("Test " + testName + " enabled")));
} }
private Optional<ConditionEvaluationResult> checkJiraReportingRules(ExtensionContext extensionContext, Method testMethod, String testName) { private Optional<ConditionEvaluationResult> checkJiraReportingRules(ExtensionContext extensionContext, Method testMethod, String testName) {
if (Boolean.parseBoolean(extensionContext.getStore(CONFIG_NAMESPACE).get("reports.tmfj.publish", String.class))) { if (Boolean.parseBoolean(extensionContext.getStore(CONFIG_NAMESPACE).get("reports.tmfj.publish", String.class))) {
return Optional.of(Arrays.stream(testMethod.getAnnotationsByType(JiraTestCase.class)) return Optional.of(Arrays.stream(testMethod.getAnnotationsByType(JiraTestCase.class))
.map(annotation -> { .map(annotation -> {
if (JiraTestResultPublisher.doesTestCaseExist(annotation.id(), extensionContext)) { if (JiraTestResultPublisher.doesTestCaseExist(annotation.id(), extensionContext)) {
return null; return null;
} else { } else {
return ConditionEvaluationResult.disabled(String.format("Test %s is disabled: Cannot find Test Case with id '%s' " + return ConditionEvaluationResult.disabled(String.format("Test %s is disabled: Cannot find Test Case with id '%s' " +
"in Jira, but reporting to Jira is requested. Skipping...", testName, annotation.id())); "in Jira, but reporting to Jira is requested. Skipping...", testName, annotation.id()));
} }
}).filter(Objects::nonNull) }).filter(Objects::nonNull)
.findFirst() .findFirst()
.orElse(ConditionEvaluationResult.enabled("Test " + testName + " enabled"))); .orElse(ConditionEvaluationResult.enabled("Test " + testName + " enabled")));
} }
return Optional.empty(); return Optional.empty();
} }
private Optional<ConditionEvaluationResult> checkTestCaseEnvironments(Environment environment, Method testMethod, private Optional<ConditionEvaluationResult> checkTestCaseEnvironments(Environment environment, Method testMethod,
String testName) { String testName) {
return Stream.<Supplier<Optional<Environment[]>>>of( return Stream.<Supplier<Optional<Environment[]>>>of(
() -> Optional.ofNullable(testMethod.getAnnotation(TestCase.class)) () -> Optional.ofNullable(testMethod.getAnnotation(TestCase.class))
.map(TestCase::environments), .map(TestCase::environments),
() -> Optional.ofNullable(testMethod.getAnnotation(ParameterizedTestCase.class)) () -> Optional.ofNullable(testMethod.getAnnotation(ParameterizedTestCase.class))
.map(ParameterizedTestCase::environments)) .map(ParameterizedTestCase::environments))
.map(Supplier::get) .map(Supplier::get)
.filter(Optional::isPresent) .filter(Optional::isPresent)
.map(Optional::get) .map(Optional::get)
.findFirst() .findFirst()
.filter(envs -> Arrays.stream(envs).noneMatch(environment::equals)) .filter(envs -> Arrays.stream(envs).noneMatch(environment::equals))
.map(envs -> ConditionEvaluationResult.disabled("Test " + .map(envs -> ConditionEvaluationResult.disabled("Test " +
testName + " disabled. It is marked to only run on " + testName + " disabled. It is marked to only run on " +
Arrays.stream(envs).map(Enum::name).collect(Collectors.joining(", ")))); Arrays.stream(envs).map(Enum::name).collect(Collectors.joining(", "))));
} }
private Optional<ConditionEvaluationResult> checkDefectEnvironments(Environment environment, Method testMethod, private Optional<ConditionEvaluationResult> checkDefectEnvironments(Environment environment, Method testMethod,
String testName, ExtensionContext context) { String testName, ExtensionContext context) {
return Optional.of(context.getStore(CONFIG_NAMESPACE) return Optional.of(context.getStore(CONFIG_NAMESPACE)
.getOrComputeIfAbsent("harness.defects.ignore", k -> "false")) .getOrComputeIfAbsent("harness.defects.ignore", k -> "false"))
.filter(ignore -> !"true".equals(ignore)) .filter(ignore -> !"true".equals(ignore))
.flatMap(i -> Arrays.stream(testMethod.getAnnotationsByType(Defect.class)) .flatMap(i -> Arrays.stream(testMethod.getAnnotationsByType(Defect.class))
.filter(d -> Arrays.stream(d.environments()).anyMatch(environment::equals)) .filter(d -> Arrays.stream(d.environments()).anyMatch(environment::equals))
.findFirst()) .findFirst())
.map(d -> ConditionEvaluationResult.disabled("Test " + .map(d -> ConditionEvaluationResult.disabled("Test " +
testName + " in environment " + environment + " disabled due to defect '" + d.value() + "'")); testName + " in environment " + environment + " disabled due to defect '" + d.value() + "'"));
} }
private Environment getCurrentEnvironment(ExtensionContext context) { private Environment getCurrentEnvironment(ExtensionContext context) {
return Optional.ofNullable(context.getStore(CONFIG_NAMESPACE) return Optional.ofNullable(context.getStore(CONFIG_NAMESPACE)
.get(HarnessConfigConstants.ENVIRONMENT_TYPE)) .get(HarnessConfigConstants.ENVIRONMENT_TYPE))
.filter(String.class::isInstance) .filter(String.class::isInstance)
.map(String.class::cast) .map(String.class::cast)
.map(Environment::fromString) .map(Environment::fromString)
.map(e -> { .map(e -> {
logger.info("Detected environment: {}", () -> e.name()); logger.info("Detected environment: {}", () -> e.name());
return e; return e;
}) })
.orElseThrow(() -> .orElseThrow(() ->
new HarnessConfigurationException("You need to configure mandatory parameter " + HarnessConfigConstants.ENVIRONMENT_TYPE)); new HarnessConfigurationException("You need to configure mandatory parameter " + HarnessConfigConstants.ENVIRONMENT_TYPE));
} }
private Browser getBrowserFromConfig(ExtensionContext extensionContext) { private Browser getBrowserFromConfig(ExtensionContext extensionContext) {
String browserConfigName = System.getProperty(BROWSER_CONFIG_KEY); String browserConfigName = System.getProperty(BROWSER_CONFIG_KEY);
if (browserConfigName == null) { if (browserConfigName == null) {
browserConfigName = Optional.ofNullable(extensionContext.getStore(CONFIG_NAMESPACE).get(BROWSER_CONFIG_KEY, String.class)) browserConfigName = Optional.ofNullable(extensionContext.getStore(CONFIG_NAMESPACE).get(BROWSER_CONFIG_KEY, String.class))
.orElseThrow(() -> new HarnessConfigurationException(("You need to configure browser to run tests!"))); .orElseThrow(() -> new HarnessConfigurationException(("You need to configure browser to run tests!")));
} }
Browser browser = Browser.getBrowserByConfigName(browserConfigName); Browser browser = Browser.getBrowserByConfigName(browserConfigName);
logger.info("Detected browser: {}", browser::name); logger.info("Detected browser: {}", browser::name);
return browser; return browser;
} }
private ConditionEvaluationResult enableEvaluationAndLogExecutionStart(ExtensionContext extensionContext) { private ConditionEvaluationResult enableEvaluationAndLogExecutionStart(ExtensionContext extensionContext) {
String testName = extensionContext.getDisplayName(); String testName = extensionContext.getDisplayName();
logger.info("Test case {} execution in test class {} started.", () -> testName, () -> getClassCanonicalName(extensionContext)); logger.info("Test case {} execution in test class {} started.", () -> testName, () -> getClassCanonicalName(extensionContext));
return ConditionEvaluationResult.enabled("Test " + testName + " enabled"); return ConditionEvaluationResult.enabled("Test " + testName + " enabled");
} }
private void logTestResult(ExtensionContext extensionContext) { private void logTestResult(ExtensionContext extensionContext) {
Method testMethod = extensionContext.getRequiredTestMethod(); Method testMethod = extensionContext.getRequiredTestMethod();
Optional<Throwable> executionException = extensionContext.getExecutionException(); Optional<Throwable> executionException = extensionContext.getExecutionException();
String resolution = executionException.map(e -> RESOLUTION_FAIL).orElse(RESOLUTION_PASS); String resolution = executionException.map(e -> RESOLUTION_FAIL).orElse(RESOLUTION_PASS);
logger.info("Test case {} in test class {} executed with resolution - {}.", () -> extensionContext.getDisplayName(), () -> testMethod.getDeclaringClass(), () -> resolution); logger.info("Test case {} in test class {} executed with resolution - {}.", () -> extensionContext.getDisplayName(), () -> testMethod.getDeclaringClass(), () -> resolution);
if (resolution.equals(RESOLUTION_FAIL)) { if (resolution.equals(RESOLUTION_FAIL)) {
logger.error("ERROR: ", () -> executionException.get()); logger.error("ERROR: ", () -> executionException.get());
} }
} }
private String getClassCanonicalName(ExtensionContext extensionContext) { private String getClassCanonicalName(ExtensionContext extensionContext) {
return extensionContext.getTestClass() return extensionContext.getTestClass()
.get() .get()
.getCanonicalName(); .getCanonicalName();
} }
private void storeCurrentTestName(ExtensionContext extensionContext) { private void storeCurrentTestName(ExtensionContext extensionContext) {
String uniqueId = UniqueId.parse(extensionContext.getUniqueId()) String uniqueId = UniqueId.parse(extensionContext.getUniqueId())
.getSegments() .getSegments()
.stream() .stream()
.skip(1) .skip(1)
.map(UniqueId.Segment::getValue) .map(UniqueId.Segment::getValue)
.collect(Collectors.joining(".")); .collect(Collectors.joining("."));
extensionContext.getRoot().getStore(Namespace.GLOBAL).put(HarnessConfigConstants.TEST_UNIQUE_ID, uniqueId); extensionContext.getRoot().getStore(Namespace.GLOBAL).put(HarnessConfigConstants.TEST_UNIQUE_ID, uniqueId);
String shortId = (extensionContext.getTestClass().get().getSimpleName() + "_" + extensionContext.getDisplayName()).replaceAll("[\\\\/:*?\"<>|]", ""); String shortId = (extensionContext.getTestClass().get().getSimpleName() + "_" + extensionContext.getDisplayName()).replaceAll("[\\\\/:*?\"<>|]", "");
extensionContext.getRoot().getStore(Namespace.GLOBAL).put(HarnessConfigConstants.TEST_SHORT_ID, shortId); extensionContext.getRoot().getStore(Namespace.GLOBAL).put(HarnessConfigConstants.TEST_SHORT_ID, shortId);
} }
} }

View File

@ -1,33 +1,33 @@
package cz.moneta.test.harness.annotations; package cz.moneta.test.harness.annotations;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable; import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import static cz.moneta.test.harness.annotations.Environment.DEV; import static cz.moneta.test.harness.annotations.Environment.DEV;
import static cz.moneta.test.harness.annotations.Environment.EDU; import static cz.moneta.test.harness.annotations.Environment.EDU;
import static cz.moneta.test.harness.annotations.Environment.FVE; import static cz.moneta.test.harness.annotations.Environment.FVE;
import static cz.moneta.test.harness.annotations.Environment.PPE; import static cz.moneta.test.harness.annotations.Environment.PPE;
import static cz.moneta.test.harness.annotations.Environment.TST1; import static cz.moneta.test.harness.annotations.Environment.TST1;
/** /**
* Disables test in specified environments with a reference to the related issue * Disables test in specified environments with a reference to the related issue
*/ */
@Target(ElementType.METHOD) @Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Documented @Documented
@Repeatable(Defects.class) @Repeatable(Defects.class)
public @interface Defect { public @interface Defect {
/** /**
* Reason for test being disabled (ideally) including a reference to a JIRA or TEAMTRACK issue * Reason for test being disabled (ideally) including a reference to a JIRA or TEAMTRACK issue
*/ */
String value(); String value();
/** /**
* Applicable environments where the test should be effectively disabled (default is all environments) * Applicable environments where the test should be effectively disabled (default is all environments)
*/ */
Environment[] environments() default {DEV, TST1, PPE, EDU, FVE}; Environment[] environments() default {DEV, TST1, PPE, EDU, FVE};
} }

View File

@ -1,17 +1,17 @@
package cz.moneta.test.harness.annotations; package cz.moneta.test.harness.annotations;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
/** /**
* container annotation for {@link cz.moneta.test.harness.annotations.Defect} * container annotation for {@link cz.moneta.test.harness.annotations.Defect}
*/ */
@Target(ElementType.METHOD) @Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Documented @Documented
public @interface Defects { public @interface Defects {
Defect[] value(); Defect[] value();
} }

View File

@ -1,13 +1,13 @@
package cz.moneta.test.harness.annotations; package cz.moneta.test.harness.annotations;
import java.lang.annotation.*; import java.lang.annotation.*;
@Target(ElementType.METHOD) @Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Repeatable(EnableIfSets.class) @Repeatable(EnableIfSets.class)
@Documented @Documented
public @interface EnableIfSet { public @interface EnableIfSet {
String globalKey(); String globalKey();
} }

View File

@ -1,15 +1,15 @@
package cz.moneta.test.harness.annotations; package cz.moneta.test.harness.annotations;
import java.lang.annotation.*; import java.lang.annotation.*;
/** /**
* Container annotation * Container annotation
*/ */
@Target({ElementType.METHOD}) @Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Documented @Documented
public @interface EnableIfSets { public @interface EnableIfSets {
EnableIfSet[] value(); EnableIfSet[] value();
} }

View File

@ -1,30 +1,30 @@
package cz.moneta.test.harness.annotations; package cz.moneta.test.harness.annotations;
import java.util.EnumSet; import java.util.EnumSet;
public enum Environment { public enum Environment {
DEV("DigDev"), DEV("DigDev"),
TST1("TST1"), TST1("TST1"),
TST3("TST3"), TST3("TST3"),
PPE("PPE"), PPE("PPE"),
EDU("EDU"), EDU("EDU"),
FVE("FVE"), FVE("FVE"),
LIVE("LIVE"); LIVE("LIVE");
Environment(String jiraAlias) { Environment(String jiraAlias) {
this.jiraAlias = jiraAlias; this.jiraAlias = jiraAlias;
} }
private String jiraAlias; private String jiraAlias;
public static Environment fromString(String env) { public static Environment fromString(String env) {
return EnumSet.allOf(Environment.class).stream() return EnumSet.allOf(Environment.class).stream()
.filter(e -> e.name().equalsIgnoreCase(env)) .filter(e -> e.name().equalsIgnoreCase(env))
.findFirst() .findFirst()
.orElse(null); .orElse(null);
} }
public String getJiraAlias() { public String getJiraAlias() {
return jiraAlias; return jiraAlias;
} }
} }

View File

@ -1,22 +1,22 @@
package cz.moneta.test.harness.annotations; package cz.moneta.test.harness.annotations;
import java.lang.annotation.*; import java.lang.annotation.*;
/** /**
* {@code @JiraTestCase} is used to signal that the annotated method implements * {@code @JiraTestCase} is used to signal that the annotated method implements
* particular test case in Test Management for JIRA. * particular test case in Test Management for JIRA.
* *
* The info in this annotation is used by harness to create Test Run objects in * The info in this annotation is used by harness to create Test Run objects in
* Test Management for JIRA when configured and enabled, otherwise it has no effect. * Test Management for JIRA when configured and enabled, otherwise it has no effect.
*/ */
@Target(ElementType.METHOD) @Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Repeatable(JiraTestCases.class) @Repeatable(JiraTestCases.class)
@Documented @Documented
public @interface JiraTestCase { public @interface JiraTestCase {
String id(); String id();
String project() default ""; String project() default "";
} }

View File

@ -1,14 +1,14 @@
package cz.moneta.test.harness.annotations; package cz.moneta.test.harness.annotations;
import java.lang.annotation.*; import java.lang.annotation.*;
/** /**
* Container annotation * Container annotation
*/ */
@Target({ElementType.METHOD}) @Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Documented @Documented
public @interface JiraTestCases { public @interface JiraTestCases {
JiraTestCase[] value(); JiraTestCase[] value();
} }

View File

@ -1,31 +1,31 @@
package cz.moneta.test.harness.annotations; package cz.moneta.test.harness.annotations;
import cz.moneta.test.harness.HarnessJunit5Extension; import cz.moneta.test.harness.HarnessJunit5Extension;
import cz.moneta.test.harness.data.Browser; import cz.moneta.test.harness.data.Browser;
import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.platform.runner.JUnitPlatform; import org.junit.platform.runner.JUnitPlatform;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
/** /**
* This annotation is used for managing parallel test execution. * This annotation is used for managing parallel test execution.
* Test class with this annotation will not be executed in parallel to other classes with this annotation. * Test class with this annotation will not be executed in parallel to other classes with this annotation.
*/ */
@Target(ElementType.TYPE) @Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Documented @Documented
@RunWith(JUnitPlatform.class) @RunWith(JUnitPlatform.class)
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
@ExtendWith(HarnessJunit5Extension.class) @ExtendWith(HarnessJunit5Extension.class)
public @interface NonConcurrentTestScenario { public @interface NonConcurrentTestScenario {
String name() default ""; String name() default "";
Browser[] browsers() default {Browser.MS_EDGE, Browser.GOOGLE_CHROME}; Browser[] browsers() default {Browser.MS_EDGE, Browser.GOOGLE_CHROME};
} }

View File

@ -1,17 +1,17 @@
package cz.moneta.test.harness.annotations; package cz.moneta.test.harness.annotations;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import java.lang.annotation.*; import java.lang.annotation.*;
@Target(ElementType.METHOD) @Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Documented @Documented
@ParameterizedTest @ParameterizedTest
public @interface ParameterizedTestCase { public @interface ParameterizedTestCase {
String name () default ""; String name () default "";
Environment[] environments() default {Environment.DEV, Environment.TST1, Environment.PPE, Environment.EDU, Environment.FVE}; Environment[] environments() default {Environment.DEV, Environment.TST1, Environment.PPE, Environment.EDU, Environment.FVE};
} }

View File

@ -1,16 +1,16 @@
package cz.moneta.test.harness.annotations; package cz.moneta.test.harness.annotations;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import java.lang.annotation.*; import java.lang.annotation.*;
@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD }) @Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Documented @Documented
@Test @Test
public @interface TestCase { public @interface TestCase {
String name() default ""; String name() default "";
Environment[] environments() default {Environment.DEV, Environment.TST1, Environment.PPE, Environment.EDU, Environment.FVE}; Environment[] environments() default {Environment.DEV, Environment.TST1, Environment.PPE, Environment.EDU, Environment.FVE};
} }

View File

@ -1,9 +1,9 @@
package cz.moneta.test.harness.annotations; package cz.moneta.test.harness.annotations;
import java.lang.annotation.*; import java.lang.annotation.*;
@Target(ElementType.TYPE) @Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Documented @Documented
public @interface TestContext { public @interface TestContext {
} }

View File

@ -1,26 +1,26 @@
package cz.moneta.test.harness.annotations; package cz.moneta.test.harness.annotations;
import cz.moneta.test.harness.HarnessJunit5Extension; import cz.moneta.test.harness.HarnessJunit5Extension;
import cz.moneta.test.harness.data.Browser; import cz.moneta.test.harness.data.Browser;
import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode; import org.junit.jupiter.api.parallel.ExecutionMode;
import org.junit.platform.runner.JUnitPlatform; import org.junit.platform.runner.JUnitPlatform;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import java.lang.annotation.*; import java.lang.annotation.*;
@Target(ElementType.TYPE) @Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Documented @Documented
@RunWith(JUnitPlatform.class) @RunWith(JUnitPlatform.class)
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Execution(ExecutionMode.CONCURRENT) @Execution(ExecutionMode.CONCURRENT)
@ExtendWith(HarnessJunit5Extension.class) @ExtendWith(HarnessJunit5Extension.class)
public @interface TestScenario { public @interface TestScenario {
String name() default ""; String name() default "";
Browser[] browsers() default {Browser.MS_EDGE, Browser.GOOGLE_CHROME}; Browser[] browsers() default {Browser.MS_EDGE, Browser.GOOGLE_CHROME};
} }

View File

@ -1,29 +1,29 @@
package cz.moneta.test.harness.annotations; package cz.moneta.test.harness.annotations;
import cz.moneta.test.harness.HarnessJunit5Extension; import cz.moneta.test.harness.HarnessJunit5Extension;
import cz.moneta.test.harness.data.Browser; import cz.moneta.test.harness.data.Browser;
import org.junit.jupiter.api.MethodOrderer; import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.TestInstance; import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestMethodOrder; import org.junit.jupiter.api.TestMethodOrder;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.parallel.Execution; import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode; import org.junit.jupiter.api.parallel.ExecutionMode;
import org.junit.platform.runner.JUnitPlatform; import org.junit.platform.runner.JUnitPlatform;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import java.lang.annotation.*; import java.lang.annotation.*;
@Target(ElementType.TYPE) @Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Documented @Documented
@RunWith(JUnitPlatform.class) @RunWith(JUnitPlatform.class)
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
@Execution(ExecutionMode.SAME_THREAD) @Execution(ExecutionMode.SAME_THREAD)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class) @TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@ExtendWith(HarnessJunit5Extension.class) @ExtendWith(HarnessJunit5Extension.class)
public @interface TestScenarioWithOrder { public @interface TestScenarioWithOrder {
String name() default ""; String name() default "";
Browser[] browsers() default {Browser.MS_EDGE, Browser.GOOGLE_CHROME}; Browser[] browsers() default {Browser.MS_EDGE, Browser.GOOGLE_CHROME};
} }

View File

@ -1,19 +1,19 @@
package cz.moneta.test.harness.annotations; package cz.moneta.test.harness.annotations;
import cz.moneta.test.harness.HarnessJunit5Extension; import cz.moneta.test.harness.HarnessJunit5Extension;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.platform.runner.JUnitPlatform; import org.junit.platform.runner.JUnitPlatform;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import java.lang.annotation.*; import java.lang.annotation.*;
@Target(ElementType.TYPE) @Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Documented @Documented
@RunWith(JUnitPlatform.class) @RunWith(JUnitPlatform.class)
@ExtendWith(HarnessJunit5Extension.class) @ExtendWith(HarnessJunit5Extension.class)
public @interface TestSuite { public @interface TestSuite {
String name() default ""; String name() default "";
} }

View File

@ -1,117 +1,117 @@
package cz.moneta.test.harness.config; package cz.moneta.test.harness.config;
import cz.moneta.test.harness.constants.HarnessConfigConstants; import cz.moneta.test.harness.constants.HarnessConfigConstants;
import cz.moneta.test.harness.exception.HarnessConfigurationException; import cz.moneta.test.harness.exception.HarnessConfigurationException;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.FileReader; import java.io.FileReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.Reader; import java.io.Reader;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.Properties; import java.util.Properties;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
public final class ConfigProvider { public final class ConfigProvider {
private static final Logger logger = LogManager.getLogger(ConfigProvider.class); private static final Logger logger = LogManager.getLogger(ConfigProvider.class);
private ConfigProvider() {} private ConfigProvider() {}
public static Map<String, String> readConfig() { public static Map<String, String> readConfig() {
String fileName = Stream.<Supplier<String>>of( String fileName = Stream.<Supplier<String>>of(
() -> System.getProperty("config"), () -> System.getProperty("config"),
() -> System.getenv("HARNESS_CONFIG")) () -> System.getenv("HARNESS_CONFIG"))
.map(Supplier::get) .map(Supplier::get)
.filter(Objects::nonNull) .filter(Objects::nonNull)
.findFirst() .findFirst()
.orElseThrow(() -> new HarnessConfigurationException("You need to provide a configuration file")); .orElseThrow(() -> new HarnessConfigurationException("You need to provide a configuration file"));
Map<String, String> resultConfig = loadConfig(fileName, new ArrayList<>()); Map<String, String> resultConfig = loadConfig(fileName, new ArrayList<>());
logger.debug("Effective configuration:"); logger.debug("Effective configuration:");
resultConfig.entrySet() resultConfig.entrySet()
.stream() .stream()
.filter(configEntry -> !configEntry.getKey().equals(HarnessConfigConstants.VAULT_PASSWORD_CONFIG)) //DO NOT LOG VAULT PWD VALUE! .filter(configEntry -> !configEntry.getKey().equals(HarnessConfigConstants.VAULT_PASSWORD_CONFIG)) //DO NOT LOG VAULT PWD VALUE!
.collect(Collectors.toMap(Map.Entry::getKey, configEntry -> configEntry.getValue().trim())) .collect(Collectors.toMap(Map.Entry::getKey, configEntry -> configEntry.getValue().trim()))
.forEach((key, value) -> logger.debug("{}={}", key, value)); .forEach((key, value) -> logger.debug("{}={}", key, value));
return resultConfig; return resultConfig;
} }
private static Map<String, String> loadConfig(String fileName, List<String> configStack) { private static Map<String, String> loadConfig(String fileName, List<String> configStack) {
Map<String, String> properties = readProperties(fileName); Map<String, String> properties = readProperties(fileName);
return Optional.ofNullable(properties.get("environment.from")) return Optional.ofNullable(properties.get("environment.from"))
.map(fn -> { .map(fn -> {
if (configStack.contains(fn)) { if (configStack.contains(fn)) {
throw new IllegalStateException("Config chain contains circular dependencies"); throw new IllegalStateException("Config chain contains circular dependencies");
} }
return fn; return fn;
}) })
.map(fn -> { .map(fn -> {
configStack.add(fn); configStack.add(fn);
return loadConfig(fn, configStack); return loadConfig(fn, configStack);
}) })
.map(p -> { .map(p -> {
p.putAll(properties); p.putAll(properties);
return p; return p;
}) })
.orElse(properties); .orElse(properties);
} }
private static Map<String, String> readProperties(String fileName) { private static Map<String, String> readProperties(String fileName) {
Reader reader; Reader reader;
try { try {
logger.debug(() -> String.format("Trying to load config file: %s", fileName)); logger.debug(() -> String.format("Trying to load config file: %s", fileName));
reader = new FileReader(fileName); reader = new FileReader(fileName);
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
logger.debug(() -> logger.debug(() ->
String.format("Could not load file: %s, trying to load it as classpath resource", fileName)); String.format("Could not load file: %s, trying to load it as classpath resource", fileName));
reader = getReaderForClasspathResource(fileName); reader = getReaderForClasspathResource(fileName);
} }
Properties properties = new Properties(); Properties properties = new Properties();
try { try {
properties.load(reader); properties.load(reader);
} catch (IOException e) { } catch (IOException e) {
throw new IllegalStateException("Cannot read config file", e); throw new IllegalStateException("Cannot read config file", e);
} }
return properties.stringPropertyNames().stream() return properties.stringPropertyNames().stream()
.collect(Collectors.toMap(k -> k, k -> resolveSystemProperty(properties.getProperty(k)))); .collect(Collectors.toMap(k -> k, k -> resolveSystemProperty(properties.getProperty(k))));
} }
private static String resolveSystemProperty(String value) { private static String resolveSystemProperty(String value) {
return Stream.<Supplier<Optional<String>>>of( return Stream.<Supplier<Optional<String>>>of(
() -> Optional.ofNullable(value).filter(v -> !value.startsWith("$")), () -> Optional.ofNullable(value).filter(v -> !value.startsWith("$")),
() -> Optional.ofNullable(value) () -> Optional.ofNullable(value)
.map(v -> System.getProperty(v.substring(1))) .map(v -> System.getProperty(v.substring(1)))
.map(v -> { .map(v -> {
logger.debug("Successfully resolved system property: {}", () -> value); logger.debug("Successfully resolved system property: {}", () -> value);
return v; return v;
})) }))
.map(Supplier::get) .map(Supplier::get)
.filter(Optional::isPresent) .filter(Optional::isPresent)
.map(Optional::get) .map(Optional::get)
.findFirst() .findFirst()
.orElseThrow(() -> new IllegalStateException("Cannot find system property for: " + value)); .orElseThrow(() -> new IllegalStateException("Cannot find system property for: " + value));
} }
private static Reader getReaderForClasspathResource(String resourceName) { private static Reader getReaderForClasspathResource(String resourceName) {
String resourcePath = "envs/" + resourceName; //TODO hidden magic constant - fix this concept String resourcePath = "envs/" + resourceName; //TODO hidden magic constant - fix this concept
InputStream resourceAsStream = ConfigProvider.class.getClassLoader().getResourceAsStream(resourcePath); InputStream resourceAsStream = ConfigProvider.class.getClassLoader().getResourceAsStream(resourcePath);
if (resourceAsStream != null) { if (resourceAsStream != null) {
return new InputStreamReader(resourceAsStream); return new InputStreamReader(resourceAsStream);
} else { } else {
throw new IllegalStateException(String.format("Cannot find config file: %s", resourceName)); throw new IllegalStateException(String.format("Cannot find config file: %s", resourceName));
} }
} }
} }

View File

@ -1,7 +1,7 @@
package cz.moneta.test.harness.connectors; package cz.moneta.test.harness.connectors;
public interface Connector { public interface Connector {
default void close() { default void close() {
} }
} }

View File

@ -1,39 +1,39 @@
package cz.moneta.test.harness.connectors; package cz.moneta.test.harness.connectors;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
public class DemoConnector implements Connector { public class DemoConnector implements Connector {
private Path directory; private Path directory;
public void connectToTempDirectoryService(String name) { public void connectToTempDirectoryService(String name) {
try { try {
directory = Files.createTempDirectory(name); directory = Files.createTempDirectory(name);
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException("Cannot connect to temp filesystem service", e); throw new RuntimeException("Cannot connect to temp filesystem service", e);
} }
} }
public void createFile(String name) { public void createFile(String name) {
try { try {
Files.createFile(directory.resolve(name)); Files.createFile(directory.resolve(name));
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(String.format("Cannot create file: %s", name), e); throw new RuntimeException(String.format("Cannot create file: %s", name), e);
} }
} }
public boolean fileExists(String name) { public boolean fileExists(String name) {
return Files.exists(directory.resolve(name)); return Files.exists(directory.resolve(name));
} }
public void deleteFile(String name) { public void deleteFile(String name) {
try { try {
Files.delete(directory.resolve(name)); Files.delete(directory.resolve(name));
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(String.format("Cannot delete file: %s", name), e); throw new RuntimeException(String.format("Cannot delete file: %s", name), e);
} }
} }
} }

View File

@ -1,156 +1,156 @@
package cz.moneta.test.harness.connectors; package cz.moneta.test.harness.connectors;
import com.bytezone.dm3270.ConnectionListener; import com.bytezone.dm3270.ConnectionListener;
import com.bytezone.dm3270.TerminalClient; import com.bytezone.dm3270.TerminalClient;
import com.bytezone.dm3270.commands.AIDCommand; import com.bytezone.dm3270.commands.AIDCommand;
import com.bytezone.dm3270.display.ScreenDimensions; import com.bytezone.dm3270.display.ScreenDimensions;
import cz.moneta.test.harness.support.greenscreen.Key; import cz.moneta.test.harness.support.greenscreen.Key;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.glassfish.jersey.SslConfigurator; import org.glassfish.jersey.SslConfigurator;
import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.SSLSocketFactory;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.security.KeyStore; import java.security.KeyStore;
import java.security.KeyStoreException; import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.util.concurrent.Semaphore; import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
public class GreenScreenConnector implements Connector { public class GreenScreenConnector implements Connector {
public static final String CLIENT = "08548"; public static final String CLIENT = "08548";
private static final Logger LOG = LogManager.getLogger("GreenScreenConnector"); private static final Logger LOG = LogManager.getLogger("GreenScreenConnector");
private final Semaphore lock = new Semaphore(0); private final Semaphore lock = new Semaphore(0);
private final TerminalClient terminal; private final TerminalClient terminal;
private final String url; private final String url;
private final String user; private final String user;
private final String password; private final String password;
public GreenScreenConnector(String url, String user, String password) { public GreenScreenConnector(String url, String user, String password) {
this.url = url; this.url = url;
this.user = user; this.user = user;
this.password = password; this.password = password;
this.terminal = new TerminalClient(0, new ScreenDimensions(24, 80)); this.terminal = new TerminalClient(0, new ScreenDimensions(24, 80));
terminal.setConnectionTimeoutMillis(20_000); terminal.setConnectionTimeoutMillis(20_000);
terminal.setConnectionListener(buildConnectionListener()); terminal.setConnectionListener(buildConnectionListener());
terminal.addScreenChangeListener(sw -> { terminal.addScreenChangeListener(sw -> {
LOG.debug(() -> "screen changed: \n" + toCp870(terminal.getScreenText())); LOG.debug(() -> "screen changed: \n" + toCp870(terminal.getScreenText()));
lock.release(); lock.release();
}); });
terminal.setSocketFactory(buildSocketFactory()); terminal.setSocketFactory(buildSocketFactory());
} }
private static ConnectionListener buildConnectionListener() { private static ConnectionListener buildConnectionListener() {
return new ConnectionListener() { return new ConnectionListener() {
@Override @Override
public void onConnection() { public void onConnection() {
LOG.info("CONNECTED!!!"); LOG.info("CONNECTED!!!");
} }
@Override @Override
public void onException(Exception e) { public void onException(Exception e) {
LOG.error("Green screen connection failed", e); LOG.error("Green screen connection failed", e);
} }
@Override @Override
public void onConnectionClosed() { public void onConnectionClosed() {
LOG.info("CONNECTION CLOSED!!!"); LOG.info("CONNECTION CLOSED!!!");
} }
}; };
} }
private static String toCp870(String cp1047) { private static String toCp870(String cp1047) {
try { try {
return new String(cp1047.getBytes("CP1047"), "CP870"); return new String(cp1047.getBytes("CP1047"), "CP870");
} catch (UnsupportedEncodingException e) { } catch (UnsupportedEncodingException e) {
throw new IllegalStateException("Error converting CP1047 to CP870"); throw new IllegalStateException("Error converting CP1047 to CP870");
} }
} }
private static String toCp1047(String cp870) { private static String toCp1047(String cp870) {
try { try {
return new String(cp870.getBytes("CP870"), "CP1047"); return new String(cp870.getBytes("CP870"), "CP1047");
} catch (UnsupportedEncodingException e) { } catch (UnsupportedEncodingException e) {
throw new IllegalStateException("Error converting CP870 to CP1047"); throw new IllegalStateException("Error converting CP870 to CP1047");
} }
} }
public void connectAndLogin() { public void connectAndLogin() {
terminal.connect(url, 992); terminal.connect(url, 992);
waitForScreenChange(); waitForScreenChange();
terminal.setFieldTextByCoord(24, 1, "atrf"); terminal.setFieldTextByCoord(24, 1, "atrf");
terminal.sendAID(AIDCommand.AID_ENTER, "ENTER"); terminal.sendAID(AIDCommand.AID_ENTER, "ENTER");
waitForScreenChange(); waitForScreenChange();
terminal.sendAID(AIDCommand.AID_ENTER, "ENTER"); terminal.sendAID(AIDCommand.AID_ENTER, "ENTER");
waitForScreenChange(); waitForScreenChange();
terminal.setFieldTextByCoord(6, 25, CLIENT); terminal.setFieldTextByCoord(6, 25, CLIENT);
terminal.setFieldTextByCoord(8, 24, user); terminal.setFieldTextByCoord(8, 24, user);
terminal.setFieldTextByCoord(10, 24, password); terminal.setFieldTextByCoord(10, 24, password);
terminal.sendAID(AIDCommand.AID_ENTER, "ENTER"); terminal.sendAID(AIDCommand.AID_ENTER, "ENTER");
waitForScreenChange(); waitForScreenChange();
waitForScreenChange(); waitForScreenChange();
terminal.sendAID(AIDCommand.AID_ENTER, "ENTER"); terminal.sendAID(AIDCommand.AID_ENTER, "ENTER");
waitForScreenChange(); waitForScreenChange();
} }
private void waitForScreenChange() { private void waitForScreenChange() {
boolean success = false; boolean success = false;
try { try {
success = lock.tryAcquire(5L, TimeUnit.SECONDS); success = lock.tryAcquire(5L, TimeUnit.SECONDS);
TimeUnit.MILLISECONDS.sleep(200); TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) { } catch (InterruptedException e) {
LOG.error(e); LOG.error(e);
} }
if (!success) { if (!success) {
throw new IllegalStateException("Failed to load screen within 5 second timeout"); throw new IllegalStateException("Failed to load screen within 5 second timeout");
} }
} }
private SSLSocketFactory buildSocketFactory() { private SSLSocketFactory buildSocketFactory() {
try (InputStream truststoreIs = this.getClass().getClassLoader() try (InputStream truststoreIs = this.getClass().getClassLoader()
.getResourceAsStream("keystores/greenScreenTrustStore")) { .getResourceAsStream("keystores/greenScreenTrustStore")) {
KeyStore trustedStore = KeyStore.getInstance(KeyStore.getDefaultType()); KeyStore trustedStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustedStore.load(truststoreIs, "changeit".toCharArray()); trustedStore.load(truststoreIs, "changeit".toCharArray());
return SslConfigurator.newInstance() return SslConfigurator.newInstance()
.trustStore(trustedStore) .trustStore(trustedStore)
.keyStorePassword("changeit".toCharArray()) .keyStorePassword("changeit".toCharArray())
.createSSLContext() .createSSLContext()
.getSocketFactory(); .getSocketFactory();
} catch (KeyStoreException | IOException | CertificateException | NoSuchAlgorithmException e) { } catch (KeyStoreException | IOException | CertificateException | NoSuchAlgorithmException e) {
throw new IllegalStateException("Failed to initialize SSL context", e); throw new IllegalStateException("Failed to initialize SSL context", e);
} }
} }
public String getText(int row, int column, int length) { public String getText(int row, int column, int length) {
String screenText = toCp870(terminal.getScreenText()); String screenText = toCp870(terminal.getScreenText());
if (length == 0) { if (length == 0) {
return screenText; return screenText;
} else { } else {
int beginIndex = (row - 1) * 81 + column - 1; int beginIndex = (row - 1) * 81 + column - 1;
return screenText.substring(beginIndex, beginIndex + length); return screenText.substring(beginIndex, beginIndex + length);
} }
} }
public void pressKey(Key key) { public void pressKey(Key key) {
switch (key) { switch (key) {
case ENTER: case ENTER:
terminal.sendAID(AIDCommand.AID_ENTER, "ENTER"); terminal.sendAID(AIDCommand.AID_ENTER, "ENTER");
waitForScreenChange(); waitForScreenChange();
break; break;
default: default:
throw new IllegalArgumentException("Unknown key " + key.name()); throw new IllegalArgumentException("Unknown key " + key.name());
} }
} }
public void type(int row, int column, String text) { public void type(int row, int column, String text) {
terminal.setFieldTextByCoord(row, column, toCp1047(text)); terminal.setFieldTextByCoord(row, column, toCp1047(text));
} }
} }

View File

@ -1,129 +1,129 @@
package cz.moneta.test.harness.connectors; package cz.moneta.test.harness.connectors;
import itSodTesting.sharedResources.objects.dataTransfer.IlodsServerEnvironmentList; import itSodTesting.sharedResources.objects.dataTransfer.IlodsServerEnvironmentList;
import itSodTesting.sharedResources.objects.dataTransfer.IlodsServerSourceSystem; import itSodTesting.sharedResources.objects.dataTransfer.IlodsServerSourceSystem;
import itSodTesting.sharedResources.objects.dataTransfer.ObjectTransferToClient; import itSodTesting.sharedResources.objects.dataTransfer.ObjectTransferToClient;
import itSodTesting.sharedResources.objects.dataTransfer.ObjectTransferToServer; import itSodTesting.sharedResources.objects.dataTransfer.ObjectTransferToServer;
import itSodTesting.sharedResources.utils.getUser.LoggedUser; import itSodTesting.sharedResources.utils.getUser.LoggedUser;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import java.io.IOException; import java.io.IOException;
import java.io.ObjectInputStream; import java.io.ObjectInputStream;
import java.io.ObjectOutputStream; import java.io.ObjectOutputStream;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.Socket; import java.net.Socket;
import java.net.SocketTimeoutException; import java.net.SocketTimeoutException;
public class IlodsServerConnector implements Connector { public class IlodsServerConnector implements Connector {
private static final Logger logger = LogManager.getLogger(IlodsServerConnector.class); private static final Logger logger = LogManager.getLogger(IlodsServerConnector.class);
private static final String token = "&PE$T.Q;1SXdhF@#$vdnv3,M8r5+dr=e"; private static final String token = "&PE$T.Q;1SXdhF@#$vdnv3,M8r5+dr=e";
private static final Pair<String, String> liveServer = Pair.of("primary", "MBCZVW1DL0LRC02.mbid.cz"); private static final Pair<String, String> liveServer = Pair.of("primary", "MBCZVW1DL0LRC02.mbid.cz");
private static final Pair<String, String> backupServer = Pair.of("secondary", "MBCZVW0BL0LRC01.mbid.cz"); private static final Pair<String, String> backupServer = Pair.of("secondary", "MBCZVW0BL0LRC01.mbid.cz");
private static final Pair<String, String> devServer = Pair.of("develop", "MBCZDWHQXX10366.mbid.cz"); private static final Pair<String, String> devServer = Pair.of("develop", "MBCZDWHQXX10366.mbid.cz");
private Pair<String, String> currentServer = liveServer; private Pair<String, String> currentServer = liveServer;
// private Pair<String, String> currentServer = devServer; // private Pair<String, String> currentServer = devServer;
private IlodsServerEnvironmentList environment; private IlodsServerEnvironmentList environment;
public IlodsServerConnector(IlodsServerEnvironmentList environment) { public IlodsServerConnector(IlodsServerEnvironmentList environment) {
this.environment = environment; this.environment = environment;
} }
public ObjectTransferToClient sendRequest(ObjectTransferToServer data) { public ObjectTransferToClient sendRequest(ObjectTransferToServer data) {
ObjectTransferToClient receivedObject = connectAndReceiveData(data); ObjectTransferToClient receivedObject = connectAndReceiveData(data);
// no response = second try // no response = second try
if (receivedObject == null) { if (receivedObject == null) {
switchServer(); switchServer();
receivedObject = connectAndReceiveData(data); receivedObject = connectAndReceiveData(data);
} }
if (receivedObject == null) { if (receivedObject == null) {
throw new RuntimeException("Cannot connect to Ilods servers"); throw new RuntimeException("Cannot connect to Ilods servers");
} }
return receivedObject; return receivedObject;
} }
private ObjectTransferToClient connectAndReceiveData(ObjectTransferToServer data) { private ObjectTransferToClient connectAndReceiveData(ObjectTransferToServer data) {
ObjectTransferToClient receivedObject = null; ObjectTransferToClient receivedObject = null;
Socket clientSocket = null; Socket clientSocket = null;
try { try {
clientSocket = new Socket(); clientSocket = new Socket();
clientSocket.connect(new InetSocketAddress(currentServer.getRight(), 51001), 1000); clientSocket.connect(new InetSocketAddress(currentServer.getRight(), 51001), 1000);
clientSocket.setSoTimeout(60 * 1000); clientSocket.setSoTimeout(60 * 1000);
ObjectOutputStream outToServer = new ObjectOutputStream(clientSocket.getOutputStream()); ObjectOutputStream outToServer = new ObjectOutputStream(clientSocket.getOutputStream());
ObjectInputStream inFromServer = new ObjectInputStream(clientSocket.getInputStream()); ObjectInputStream inFromServer = new ObjectInputStream(clientSocket.getInputStream());
// add clients informations do data // add clients informations do data
data.setToken(token); data.setToken(token);
data.setSourceSystem(IlodsServerSourceSystem.HARNESS); data.setSourceSystem(IlodsServerSourceSystem.HARNESS);
data.setMachineId(LoggedUser.getComputerName()); data.setMachineId(LoggedUser.getComputerName());
data.setClientId(LoggedUser.getUserName()); data.setClientId(LoggedUser.getUserName());
data.setEnvironment(environment); data.setEnvironment(environment);
// pack and sending object data to server // pack and sending object data to server
outToServer.writeObject(data); outToServer.writeObject(data);
// unpack and reading object response from server // unpack and reading object response from server
Object transferedObject = inFromServer.readObject(); Object transferedObject = inFromServer.readObject();
receivedObject = (ObjectTransferToClient) transferedObject; receivedObject = (ObjectTransferToClient) transferedObject;
// recognize and process errors // recognize and process errors
if (receivedObject.getResultCode() == 12) { if (receivedObject.getResultCode() == 12) {
throw new RuntimeException("Fatal error during processing " + data.getActionType() + ": " + receivedObject.getResultString()); throw new RuntimeException("Fatal error during processing " + data.getActionType() + ": " + receivedObject.getResultString());
} else if (receivedObject.getResultCode() != 0) { } else if (receivedObject.getResultCode() != 0) {
throw new RuntimeException("Server error during processing " + data.getActionType() + ": " + receivedObject.getResultString()); throw new RuntimeException("Server error during processing " + data.getActionType() + ": " + receivedObject.getResultString());
} }
} catch (SocketTimeoutException e) { } catch (SocketTimeoutException e) {
logger.warn("Connection timed out - " + currentServer.getLeft() + " Ilods server didn't respond within the specified interval"); logger.warn("Connection timed out - " + currentServer.getLeft() + " Ilods server didn't respond within the specified interval");
} catch (ClassNotFoundException | IOException e) { } catch (ClassNotFoundException | IOException e) {
logger.warn("Ilods server (" + currentServer.getLeft() + ") unavailable", e); logger.warn("Ilods server (" + currentServer.getLeft() + ") unavailable", e);
} finally { } finally {
closeConnection(clientSocket); closeConnection(clientSocket);
} }
return receivedObject; return receivedObject;
} }
private void switchServer() { private void switchServer() {
// Set up switch between primary/secondary Ilods Server (with check develop mode) // Set up switch between primary/secondary Ilods Server (with check develop mode)
if (currentServer == devServer) { if (currentServer == devServer) {
return; return;
} }
if (currentServer == liveServer) { if (currentServer == liveServer) {
currentServer = backupServer; currentServer = backupServer;
} else { } else {
currentServer = liveServer; currentServer = liveServer;
} }
logger.info("Trying to call " + currentServer.getLeft() + " Ilods server"); logger.info("Trying to call " + currentServer.getLeft() + " Ilods server");
} }
private void closeConnection(Socket socket) { private void closeConnection(Socket socket) {
if (socket == null) if (socket == null)
return; return;
try { try {
socket.shutdownInput(); socket.shutdownInput();
} catch (IOException e) { } catch (IOException e) {
} }
try { try {
socket.shutdownOutput(); socket.shutdownOutput();
} catch (IOException e) { } catch (IOException e) {
} }
try { try {
socket.close(); socket.close();
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
} }

View File

@ -1,123 +1,123 @@
package cz.moneta.test.harness.connectors; package cz.moneta.test.harness.connectors;
import com.bettercloud.vault.SslConfig; import com.bettercloud.vault.SslConfig;
import com.bettercloud.vault.Vault; import com.bettercloud.vault.Vault;
import com.bettercloud.vault.VaultConfig; import com.bettercloud.vault.VaultConfig;
import com.bettercloud.vault.VaultException; import com.bettercloud.vault.VaultException;
import com.bettercloud.vault.api.Logical; import com.bettercloud.vault.api.Logical;
import com.bettercloud.vault.response.LogicalResponse; import com.bettercloud.vault.response.LogicalResponse;
import cz.moneta.test.harness.connectors.rest.BaseRestConnector; import cz.moneta.test.harness.connectors.rest.BaseRestConnector;
import cz.moneta.test.harness.support.auth.Credentials; import cz.moneta.test.harness.support.auth.Credentials;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import java.security.KeyStore; import java.security.KeyStore;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
public class VaultConnector extends BaseRestConnector { public class VaultConnector extends BaseRestConnector {
private static final Logger LOG = LogManager.getLogger(VaultConnector.class); private static final Logger LOG = LogManager.getLogger(VaultConnector.class);
private static String url; private static String url;
private static String username; private static String username;
private static String pwd; private static String pwd;
private static Vault vault; private static Vault vault;
private static VaultConfig config; private static VaultConfig config;
private static LocalDateTime tokenExpiration; private static LocalDateTime tokenExpiration;
public VaultConnector(String url, String username, String pwd) { public VaultConnector(String url, String username, String pwd) {
this.url = url; this.url = url;
this.username = username; this.username = username;
this.pwd = pwd; this.pwd = pwd;
if (tokenExpiration == null) { if (tokenExpiration == null) {
initVaultConnection(); initVaultConnection();
} else if (tokenExpiration.minusMinutes(1).isBefore(LocalDateTime.now())) { } else if (tokenExpiration.minusMinutes(1).isBefore(LocalDateTime.now())) {
loginVault(); loginVault();
} }
} }
private void initVaultConnection() { private void initVaultConnection() {
try { try {
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(this.getClass().getClassLoader().getResourceAsStream("keystores/mb_root"), "changeit".toCharArray()); keyStore.load(this.getClass().getClassLoader().getResourceAsStream("keystores/mb_root"), "changeit".toCharArray());
config = new VaultConfig() config = new VaultConfig()
.address(url) .address(url)
.sslConfig(new SslConfig() .sslConfig(new SslConfig()
.trustStore(keyStore) .trustStore(keyStore)
.build()) .build())
.build(); .build();
vault = new Vault(config, 2); vault = new Vault(config, 2);
loginVault(); loginVault();
} catch (Exception e) { } catch (Exception e) {
throw new IllegalStateException("Failed to connect to vault url: " + url, e); throw new IllegalStateException("Failed to connect to vault url: " + url, e);
} }
} }
private void loginVault() { private void loginVault() {
try { try {
String token = vault.auth().loginByUserPass(username, pwd).getAuthClientToken(); String token = vault.auth().loginByUserPass(username, pwd).getAuthClientToken();
config.token(token).build(); config.token(token).build();
long ttl = vault.auth().lookupSelf().getTTL(); long ttl = vault.auth().lookupSelf().getTTL();
tokenExpiration = LocalDateTime.now().plusSeconds(ttl); tokenExpiration = LocalDateTime.now().plusSeconds(ttl);
} catch (VaultException e) { } catch (VaultException e) {
LOG.error("Cannot get Vault token", e); LOG.error("Cannot get Vault token", e);
} }
} }
public Optional<Credentials> getUsernameAndPassword(String path) { public Optional<Credentials> getUsernameAndPassword(String path) {
return Optional.ofNullable(vault.logical()) return Optional.ofNullable(vault.logical())
.map(v -> readValue(path, v)) .map(v -> readValue(path, v))
.map(LogicalResponse::getDataObject) .map(LogicalResponse::getDataObject)
.flatMap(d -> Optional.ofNullable(d.getString("username")).map(usr -> Pair.of(usr, d))) .flatMap(d -> Optional.ofNullable(d.getString("username")).map(usr -> Pair.of(usr, d)))
.flatMap(p -> Optional.ofNullable(p.getRight().getString("password")).map(pwd -> new Credentials(p.getLeft(), pwd))); .flatMap(p -> Optional.ofNullable(p.getRight().getString("password")).map(pwd -> new Credentials(p.getLeft(), pwd)));
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <T> Optional<T> getValue(String path, String key) { public <T> Optional<T> getValue(String path, String key) {
return Optional.ofNullable(vault.logical()) return Optional.ofNullable(vault.logical())
.map(v -> readValue(path, v)) .map(v -> readValue(path, v))
.map(LogicalResponse::getDataObject) .map(LogicalResponse::getDataObject)
.map(d -> (T) d.getString(key)); .map(d -> (T) d.getString(key));
} }
public void setKeyValue(String path, String key, String value) { public void setKeyValue(String path, String key, String value) {
Optional.ofNullable(readValue(path, vault.logical())) Optional.ofNullable(readValue(path, vault.logical()))
.map(LogicalResponse::getData) .map(LogicalResponse::getData)
.map(map -> new HashMap<String, Object>(map)) .map(map -> new HashMap<String, Object>(map))
.map(m -> { .map(m -> {
m.put(key, value); m.put(key, value);
return m; return m;
}) })
.map(m -> writeValue(path, m, vault.logical())) .map(m -> writeValue(path, m, vault.logical()))
.orElseThrow(() -> new IllegalStateException("Cannot write value to vault")); .orElseThrow(() -> new IllegalStateException("Cannot write value to vault"));
} }
private LogicalResponse writeValue(String path, Map<String, Object> value, Logical vault) { private LogicalResponse writeValue(String path, Map<String, Object> value, Logical vault) {
try { try {
return vault.write(path, value); return vault.write(path, value);
} catch (VaultException e) { } catch (VaultException e) {
LOG.error("Failed to write value from vault: {}\n{}", path, e); LOG.error("Failed to write value from vault: {}\n{}", path, e);
return null; return null;
} }
} }
private LogicalResponse readValue(String path, Logical v) { private LogicalResponse readValue(String path, Logical v) {
try { try {
return v.read(path); return v.read(path);
} catch (VaultException e) { } catch (VaultException e) {
LOG.error("Failed to read value from vault: {}\n{}", path, e); LOG.error("Failed to read value from vault: {}\n{}", path, e);
return null; return null;
} }
} }
} }

View File

@ -1,46 +1,46 @@
package cz.moneta.test.harness.connectors; package cz.moneta.test.harness.connectors;
import jakarta.xml.bind.JAXBContext; import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.JAXBException; import jakarta.xml.bind.JAXBException;
import jakarta.xml.ws.BindingProvider; import jakarta.xml.ws.BindingProvider;
import jakarta.xml.ws.Dispatch; import jakarta.xml.ws.Dispatch;
import jakarta.xml.ws.Service; import jakarta.xml.ws.Service;
import jakarta.xml.ws.handler.MessageContext; import jakarta.xml.ws.handler.MessageContext;
import org.apache.cxf.jaxws.CXFService; import org.apache.cxf.jaxws.CXFService;
import javax.xml.namespace.QName; import javax.xml.namespace.QName;
import java.net.URL; import java.net.URL;
public class WsConnector { public class WsConnector {
private final Service service; private final Service service;
private final QName portName; private final QName portName;
private final String address; private final String address;
private final String serviceNamespace; private final String serviceNamespace;
public WsConnector(String address, URL wsdlUrl, QName serviceName) { public WsConnector(String address, URL wsdlUrl, QName serviceName) {
this.address = address; this.address = address;
Service service = CXFService.create(wsdlUrl, serviceName); Service service = CXFService.create(wsdlUrl, serviceName);
if (service.getPorts().hasNext()) { if (service.getPorts().hasNext()) {
this.portName = service.getPorts().next(); this.portName = service.getPorts().next();
} else { } else {
throw new IllegalStateException(String.format("Service definition %s has no ports defined", wsdlUrl.toString())); throw new IllegalStateException(String.format("Service definition %s has no ports defined", wsdlUrl.toString()));
} }
this.serviceNamespace = serviceName.getNamespaceURI(); this.serviceNamespace = serviceName.getNamespaceURI();
this.service = service; this.service = service;
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <RESP> RESP invoke(Object request, Class<RESP> responseClass) { public <RESP> RESP invoke(Object request, Class<RESP> responseClass) {
try { try {
QName operationName = new QName(serviceNamespace, request.getClass().getSimpleName()); QName operationName = new QName(serviceNamespace, request.getClass().getSimpleName());
JAXBContext jaxbContext = JAXBContext.newInstance(responseClass.getPackage().getName()); JAXBContext jaxbContext = JAXBContext.newInstance(responseClass.getPackage().getName());
Dispatch<Object> dispatch = service.createDispatch(portName, jaxbContext, Service.Mode.PAYLOAD); Dispatch<Object> dispatch = service.createDispatch(portName, jaxbContext, Service.Mode.PAYLOAD);
dispatch.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, address); dispatch.getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, address);
dispatch.getRequestContext().put(MessageContext.WSDL_OPERATION, operationName); dispatch.getRequestContext().put(MessageContext.WSDL_OPERATION, operationName);
return (RESP) dispatch.invoke(request); return (RESP) dispatch.invoke(request);
} catch (JAXBException e) { } catch (JAXBException e) {
throw new IllegalArgumentException("Failed to invoke request", e); throw new IllegalArgumentException("Failed to invoke request", e);
} }
} }
} }

View File

@ -1,27 +1,27 @@
package cz.moneta.test.harness.connectors.common; package cz.moneta.test.harness.connectors.common;
import cz.moneta.test.harness.config.ConfigProvider; import cz.moneta.test.harness.config.ConfigProvider;
import cz.moneta.test.harness.context.ConfigAccessor; import cz.moneta.test.harness.context.ConfigAccessor;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
public class ServletConfigAccessor implements ConfigAccessor { public class ServletConfigAccessor implements ConfigAccessor {
private final Map<String, String> config = new ConcurrentHashMap<>(ConfigProvider.readConfig()); private final Map<String, String> config = new ConcurrentHashMap<>(ConfigProvider.readConfig());
@Override @Override
public String getConfig(String key) { public String getConfig(String key) {
return config.get(key); return config.get(key);
} }
@Override @Override
public String getConfig(String key, String defaultValue) { public String getConfig(String key, String defaultValue) {
return config.getOrDefault(key, defaultValue); return config.getOrDefault(key, defaultValue);
} }
@Override @Override
public void putConfig(String key, String value) { public void putConfig(String key, String value) {
config.put(key, value); config.put(key, value);
} }
} }

View File

@ -1,44 +1,44 @@
package cz.moneta.test.harness.connectors.database; package cz.moneta.test.harness.connectors.database;
import cz.moneta.test.harness.connectors.Connector; import cz.moneta.test.harness.connectors.Connector;
import org.jooq.Record; import org.jooq.Record;
import org.jooq.Result; import org.jooq.Result;
import org.jooq.SQLDialect; import org.jooq.SQLDialect;
import org.jooq.impl.DSL; import org.jooq.impl.DSL;
import java.sql.Connection; import java.sql.Connection;
import java.sql.DriverManager; import java.sql.DriverManager;
import java.sql.SQLException; import java.sql.SQLException;
public class DatabaseConnector implements Connector { public class DatabaseConnector implements Connector {
private final String url; private final String url;
private final String user; private final String user;
private final String password; private final String password;
public DatabaseConnector(String url, String user, String password) { public DatabaseConnector(String url, String user, String password) {
this.url = url; this.url = url;
this.user = user; this.user = user;
this.password = password; this.password = password;
} }
protected Result<Record> executeSql(String sql, SQLDialect sqlDialect) { protected Result<Record> executeSql(String sql, SQLDialect sqlDialect) {
try (Connection connection = DriverManager.getConnection(getUrl(), getUser(), getPassword())) { try (Connection connection = DriverManager.getConnection(getUrl(), getUser(), getPassword())) {
return DSL.using(connection, sqlDialect).fetch(sql); return DSL.using(connection, sqlDialect).fetch(sql);
} catch (SQLException e) { } catch (SQLException e) {
throw new RuntimeException("Error executing native sql database", e); throw new RuntimeException("Error executing native sql database", e);
} }
} }
public String getUrl() { public String getUrl() {
return url; return url;
} }
public String getUser() { public String getUser() {
return user; return user;
} }
public String getPassword() { public String getPassword() {
return password; return password;
} }
} }

View File

@ -1,23 +1,23 @@
package cz.moneta.test.harness.connectors.database; package cz.moneta.test.harness.connectors.database;
import cz.moneta.test.harness.support.auth.Credentials; import cz.moneta.test.harness.support.auth.Credentials;
import org.jooq.Record; import org.jooq.Record;
import org.jooq.Result; import org.jooq.Result;
import org.jooq.SQLDialect; import org.jooq.SQLDialect;
public class MsSqlConnector extends DatabaseConnector { public class MsSqlConnector extends DatabaseConnector {
private static final String windowsAuthenticationOption = ";integratedSecurity=true"; private static final String windowsAuthenticationOption = ";integratedSecurity=true";
public MsSqlConnector(String url, Credentials credentials) { public MsSqlConnector(String url, Credentials credentials) {
super(url, credentials.getUsername(), credentials.getPassword()); super(url, credentials.getUsername(), credentials.getPassword());
} }
public MsSqlConnector(String url) { public MsSqlConnector(String url) {
super(url + windowsAuthenticationOption, "", ""); super(url + windowsAuthenticationOption, "", "");
} }
public Result<Record> executeSql(String sql) { public Result<Record> executeSql(String sql) {
return super.executeSql(sql, SQLDialect.SQLSERVER); return super.executeSql(sql, SQLDialect.SQLSERVER);
} }
} }

View File

@ -1,65 +1,65 @@
package cz.moneta.test.harness.connectors.database; package cz.moneta.test.harness.connectors.database;
import org.jooq.Configuration; import org.jooq.Configuration;
import org.jooq.DSLContext; import org.jooq.DSLContext;
import org.jooq.Record; import org.jooq.Record;
import org.jooq.Result; import org.jooq.Result;
import org.jooq.Routine; import org.jooq.Routine;
import org.jooq.SQLDialect; import org.jooq.SQLDialect;
import org.jooq.TableRecord; import org.jooq.TableRecord;
import org.jooq.impl.DSL; import org.jooq.impl.DSL;
import org.jooq.impl.DefaultConfiguration; import org.jooq.impl.DefaultConfiguration;
import org.jooq.impl.DefaultConnectionProvider; import org.jooq.impl.DefaultConnectionProvider;
import java.sql.Connection; import java.sql.Connection;
import java.sql.DriverManager; import java.sql.DriverManager;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
public class OracleConnector extends DatabaseConnector { public class OracleConnector extends DatabaseConnector {
public OracleConnector(String url, String user, String password) { public OracleConnector(String url, String user, String password) {
super(url, user, password); super(url, user, password);
} }
public <R extends TableRecord<R>> R executeDsl(Function<DSLContext, R> query) { public <R extends TableRecord<R>> R executeDsl(Function<DSLContext, R> query) {
try (Connection connection = DriverManager.getConnection(getUrl(), getUser(), getPassword())) { try (Connection connection = DriverManager.getConnection(getUrl(), getUser(), getPassword())) {
return query.apply(DSL.using(connection, SQLDialect.ORACLE)); return query.apply(DSL.using(connection, SQLDialect.ORACLE));
} catch (SQLException e) { } catch (SQLException e) {
throw new RuntimeException("Error querying database", e); throw new RuntimeException("Error querying database", e);
} }
} }
public <K> K insertDsl(Function<DSLContext, K> query) { public <K> K insertDsl(Function<DSLContext, K> query) {
try (Connection connection = DriverManager.getConnection(getUrl(), getUser(), getPassword())) { try (Connection connection = DriverManager.getConnection(getUrl(), getUser(), getPassword())) {
return query.apply(DSL.using(connection, SQLDialect.ORACLE)); return query.apply(DSL.using(connection, SQLDialect.ORACLE));
} catch (SQLException e) { } catch (SQLException e) {
throw new RuntimeException("Error querying database", e); throw new RuntimeException("Error querying database", e);
} }
} }
public <R extends Routine<?>> R executeProcedure(R procedure) { public <R extends Routine<?>> R executeProcedure(R procedure) {
try (Connection connection = DriverManager.getConnection(getUrl(), getUser(), getPassword())) { try (Connection connection = DriverManager.getConnection(getUrl(), getUser(), getPassword())) {
Configuration configuration = new DefaultConfiguration() Configuration configuration = new DefaultConfiguration()
.derive(new DefaultConnectionProvider(connection)) .derive(new DefaultConnectionProvider(connection))
.derive(SQLDialect.ORACLE); .derive(SQLDialect.ORACLE);
procedure.execute(configuration); procedure.execute(configuration);
return procedure; return procedure;
} catch (SQLException e) { } catch (SQLException e) {
throw new RuntimeException("Error executing procedure database", e); throw new RuntimeException("Error executing procedure database", e);
} }
} }
public Result<Record> executeSql(String sql) { public Result<Record> executeSql(String sql) {
return super.executeSql(sql, SQLDialect.ORACLE); return super.executeSql(sql, SQLDialect.ORACLE);
} }
public void executeStatement(Consumer<Connection> execute) { public void executeStatement(Consumer<Connection> execute) {
try (Connection connection = DriverManager.getConnection(getUrl(), getUser(), getPassword())) { try (Connection connection = DriverManager.getConnection(getUrl(), getUser(), getPassword())) {
execute.accept(connection); execute.accept(connection);
} catch (SQLException e) { } catch (SQLException e) {
throw new RuntimeException("Error executing statement", e); throw new RuntimeException("Error executing statement", e);
} }
} }
} }

View File

@ -1,16 +1,16 @@
package cz.moneta.test.harness.connectors.database; package cz.moneta.test.harness.connectors.database;
import org.jooq.Record; import org.jooq.Record;
import org.jooq.Result; import org.jooq.Result;
import org.jooq.SQLDialect; import org.jooq.SQLDialect;
public class PostgresConnector extends DatabaseConnector { public class PostgresConnector extends DatabaseConnector {
public PostgresConnector(String url, String user, String password) { public PostgresConnector(String url, String user, String password) {
super(url, user, password); super(url, user, password);
} }
public Result<Record> executeSql(String sqlToExecute) { public Result<Record> executeSql(String sqlToExecute) {
return super.executeSql(sqlToExecute, SQLDialect.POSTGRES); return super.executeSql(sqlToExecute, SQLDialect.POSTGRES);
} }
} }

View File

@ -1,24 +1,23 @@
package cz.moneta.test.harness.connectors.messaging; package cz.moneta.test.harness.connectors.messaging;
import java.io.FileInputStream; import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.KeyStore; import java.security.KeyStore;
import java.time.Duration;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.HashMap; import java.util.HashMap;
import java.util.Hashtable;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.jms.BytesMessage;
import javax.jms.JMSConsumer;
import javax.jms.JMSContext;
import javax.jms.JMSException;
import javax.jms.JMSRuntimeException;
import javax.jms.Message;
import javax.jms.TextMessage;
import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.SSLSocketFactory;
@ -27,26 +26,28 @@ import javax.net.ssl.TrustManagerFactory;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import com.ibm.mq.jms.MQConnectionFactory; import com.ibm.mq.MQException;
import com.ibm.mq.MQGetMessageOptions;
import com.ibm.mq.MQMessage;
import com.ibm.mq.MQPutMessageOptions;
import com.ibm.mq.MQQueue;
import com.ibm.mq.MQQueueManager;
import com.ibm.mq.constants.CMQC;
import com.ibm.msg.client.wmq.WMQConstants; import com.ibm.msg.client.wmq.WMQConstants;
import cz.moneta.test.harness.connectors.Connector; import cz.moneta.test.harness.connectors.Connector;
import cz.moneta.test.harness.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.support.messaging.ImqRequest;
import cz.moneta.test.harness.support.messaging.MessageContentType;
import cz.moneta.test.harness.support.messaging.MqMessageFormat;
import cz.moneta.test.harness.support.messaging.ReceivedMessage;
import cz.moneta.test.harness.support.messaging.exception.MessagingConnectionException;
import cz.moneta.test.harness.support.messaging.exception.MessagingDestinationException;
import cz.moneta.test.harness.support.messaging.exception.MessagingTimeoutException;
/** /**
* IBM MQ connector using JMS client with Jakarta JMS API. Supports * IBM MQ connector using the native com.ibm.mq classes. Supports
* multi-instance Queue Manager, SSL/TLS, and multiple message formats. * multi-instance Queue Manager connection lists, SSL/TLS, message properties,
* <p> * and JSON/XML/UTF-8/EBCDIC payload formats.
* Supported formats: - JSON: JMS TextMessage with plain JSON string (default) -
* XML: JMS TextMessage with XML string - UTF-8 (CCSID 1208): JMS BytesMessage
* with UTF-8 encoding - EBCDIC (CCSID 870): JMS BytesMessage with EBCDIC
* IBM-870 encoding
*/ */
public class IbmMqConnector implements Connector { public class IbmMqConnector implements Connector {
@ -59,12 +60,19 @@ public class IbmMqConnector implements Connector {
private static final long DEFAULT_MAX_POLL_INTERVAL_MS = 1000; private static final long DEFAULT_MAX_POLL_INTERVAL_MS = 1000;
private static final String TLS_VERSION = "TLSv1.2"; private static final String TLS_VERSION = "TLSv1.2";
private static final String JMS_ID_PREFIX = "ID:";
private static final Pattern SELECTOR_EQUALS_PATTERN = Pattern
.compile("\\s*([A-Za-z_][A-Za-z0-9_.-]*)\\s*=\\s*'((?:''|[^'])*)'\\s*");
private final MQConnectionFactory connectionFactory; private final String connectionNameList;
private JMSContext jmsContext; private final String channel;
private final String queueManager; private final String queueManager;
private final String user; private final String user;
private final String password; private final String password;
private final SSLSocketFactory sslSocketFactory;
private final String sslCipherSuite;
private MQQueueManager mqQueueManager;
/** /**
* Constructor with multi-instance Queue Manager support. * Constructor with multi-instance Queue Manager support.
@ -82,35 +90,17 @@ public class IbmMqConnector implements Connector {
*/ */
public IbmMqConnector(String connectionNameList, String channel, String queueManager, String user, String password, public IbmMqConnector(String connectionNameList, String channel, String queueManager, String user, String password,
String keystorePath, String keystorePassword, String sslCipherSuite) { String keystorePath, String keystorePassword, String sslCipherSuite) {
this.connectionNameList = connectionNameList;
this.channel = channel;
this.queueManager = queueManager; this.queueManager = queueManager;
this.user = user; this.user = user;
this.password = password; this.password = password;
this.sslCipherSuite = sslCipherSuite;
try { try {
connectionFactory = new MQConnectionFactory(); this.sslSocketFactory = keystorePath != null && !keystorePath.isBlank() && keystorePassword != null
connectionFactory.setConnectionNameList(connectionNameList); && !keystorePassword.isBlank() ? getSslSocketFactory(keystorePath, keystorePassword) : null;
connectionFactory.setQueueManager(queueManager);
connectionFactory.setChannel(channel);
connectionFactory.setTransportType(WMQConstants.WMQ_CM_CLIENT);
if (user != null && !user.isBlank()) {
connectionFactory.setStringProperty(WMQConstants.USERID, user);
}
if (password != null && !password.isBlank()) {
connectionFactory.setStringProperty(WMQConstants.PASSWORD, password);
}
if (keystorePath != null && !keystorePath.isBlank() && keystorePassword != null
&& !keystorePassword.isBlank()) {
connectionFactory.setSSLSocketFactory(getSslSocketFactory(keystorePath, keystorePassword));
}
if (sslCipherSuite != null && !sslCipherSuite.isBlank()) {
connectionFactory.setSSLCipherSuite(sslCipherSuite);
}
// Initialize JMS context
connect(); connect();
} catch (Exception e) { } catch (Exception e) {
throw new MessagingConnectionException("Failed to create IBM MQ connection to " + queueManager, e); throw new MessagingConnectionException("Failed to create IBM MQ connection to " + queueManager, e);
} }
@ -121,97 +111,82 @@ public class IbmMqConnector implements Connector {
*/ */
private void connect() { private void connect() {
try { try {
this.jmsContext = connectionFactory.createContext(user, password, JMSContext.AUTO_ACKNOWLEDGE); this.mqQueueManager = new MQQueueManager(queueManager, createConnectionProperties());
this.jmsContext.start();
LOG.info("Connected to IBM MQ: {}", queueManager); LOG.info("Connected to IBM MQ: {}", queueManager);
} catch (Exception e) { } catch (MQException e) {
throw new MessagingConnectionException( throw new MessagingConnectionException(
"Failed to connect to IBM MQ: " + queueManager + " - " + e.getMessage(), e); "Failed to connect to IBM MQ: " + queueManager + " - " + e.getMessage(), e);
} }
} }
/** private Hashtable<String, Object> createConnectionProperties() {
* Send a JSON or XML message as TextMessage. Hashtable<String, Object> properties = new Hashtable<>();
*/ if (connectionNameList != null && !connectionNameList.isBlank()) {
private void sendTextMessage(String queueName, String payload, Map<String, String> properties) { properties.put(WMQConstants.WMQ_CONNECTION_NAME_LIST, connectionNameList);
javax.jms.Queue queue = getQueue(queueName); }
properties.put(CMQC.CHANNEL_PROPERTY, channel);
properties.put(CMQC.TRANSPORT_PROPERTY, CMQC.TRANSPORT_MQSERIES_CLIENT);
TextMessage message = jmsContext.createTextMessage(payload); if (user != null && !user.isBlank()) {
properties.put(CMQC.USER_ID_PROPERTY, user);
// Set JMS properties }
if (properties != null) { if (password != null && !password.isBlank()) {
for (Map.Entry<String, String> entry : properties.entrySet()) { properties.put(CMQC.PASSWORD_PROPERTY, password);
try { }
if (entry.getKey().equals(ImqRequest.PROP_JMS_CORRELATION_ID)) { if (sslSocketFactory != null) {
message.setJMSCorrelationID(entry.getValue()); properties.put(CMQC.SSL_SOCKET_FACTORY_PROPERTY, sslSocketFactory);
continue; }
} else if (entry.getKey().equals(ImqRequest.PROP_JMS_TYPE)) { if (sslCipherSuite != null && !sslCipherSuite.isBlank()) {
message.setJMSType(entry.getValue()); properties.put(CMQC.SSL_CIPHER_SUITE_PROPERTY, sslCipherSuite);
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 { return properties;
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);
} }
/** /**
* Send a message as BytesMessage with specific encoding and CCSID. * Send a JSON or XML message as MQ string message.
*/
private void sendTextMessage(String queueName, String payload, Map<String, String> properties) {
sendMessage(queueName, payload.getBytes(UTF_8), UTF_8, 1208, CMQC.MQFMT_STRING, properties, "JSON/XML");
}
/**
* Send a message as raw bytes with specific encoding and CCSID.
*/ */
private void sendBytesMessage(String queueName, String payload, Charset charset, int ccsid, private void sendBytesMessage(String queueName, String payload, Charset charset, int ccsid,
Map<String, String> properties) { Map<String, String> properties) {
javax.jms.Queue queue = getQueue(queueName); sendMessage(queueName, payload.getBytes(charset), charset, ccsid, CMQC.MQFMT_NONE, properties,
charset.displayName());
}
BytesMessage message = jmsContext.createBytesMessage(); private void sendMessage(String queueName, byte[] payload, Charset charset, int ccsid, String mqFormat,
Map<String, String> properties, String logFormat) {
// Convert payload to bytes using specified charset MQQueue queue = null;
byte[] bytes = payload.getBytes(charset);
try { try {
message.writeBytes(bytes); int openOptions = CMQC.MQOO_OUTPUT | CMQC.MQOO_FAIL_IF_QUIESCING;
message.setIntProperty("CCSID", ccsid);
} catch (JMSException e) {
throw new MessagingDestinationException("Failed to create bytes message", e);
}
// Set JMS properties queue = openQueue(queueName, openOptions);
if (properties != null) {
for (Map.Entry<String, String> entry : properties.entrySet()) { MQMessage message = new MQMessage();
try { message.format = mqFormat;
if (entry.getKey().equals(ImqRequest.PROP_JMS_CORRELATION_ID)) { message.characterSet = ccsid;
message.setJMSCorrelationID(entry.getValue()); message.write(payload);
continue; if (!CMQC.MQFMT_STRING.equals(mqFormat)) {
} else if (entry.getKey().equals(ImqRequest.PROP_JMS_TYPE)) { message.setIntProperty("CCSID", ccsid);
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);
}
} }
MQPutMessageOptions putOptions = new MQPutMessageOptions();
putOptions.options = CMQC.MQPMO_NO_SYNCPOINT | CMQC.MQPMO_FAIL_IF_QUIESCING;
applyMessageProperties(message, properties);
queue.put(message, putOptions);
} catch (MQException | IOException e) {
throw new MessagingDestinationException("Failed to send message to queue: " + queueName, e);
} finally {
closeQueue(queue, queueName);
} }
try { LOG.debug("Sent {} message ({}) to queue: {}", logFormat, charset, queueName);
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);
} }
/** /**
@ -220,7 +195,7 @@ public class IbmMqConnector implements Connector {
* @param queueName Queue name * @param queueName Queue name
* @param payload Message payload * @param payload Message payload
* @param format Message format (JSON, XML, EBCDIC_870, UTF8_1208) * @param format Message format (JSON, XML, EBCDIC_870, UTF8_1208)
* @param properties JMS properties to set * @param properties message properties to set
*/ */
public void send(String queueName, String payload, MqMessageFormat format, Map<String, String> properties) { public void send(String queueName, String payload, MqMessageFormat format, Map<String, String> properties) {
switch (format) { switch (format) {
@ -234,56 +209,99 @@ public class IbmMqConnector implements Connector {
* Receive a message from a queue with timeout. * Receive a message from a queue with timeout.
* *
* @param queueName Queue name * @param queueName Queue name
* @param messageSelector JMS message selector (optional) * @param messageSelector JMS-style message selector (optional). Equality
* predicates joined by AND are evaluated client-side.
* @param format Expected message format * @param format Expected message format
* @param timeout Timeout duration * @param timeout Timeout duration
* @return Received message * @return Received message
*/ */
public ReceivedMessage receive(String queueName, String messageSelector, MqMessageFormat format, public ReceivedMessage receive(String queueName, String messageSelector, MqMessageFormat format, Duration timeout) {
java.time.Duration timeout) { if (messageSelector == null || messageSelector.isBlank()) {
long timeoutMs = timeout.toMillis(); return receiveNext(queueName, format, timeout);
}
javax.jms.Queue queue = getQueue(queueName); return receiveMatching(queueName, messageSelector, format, timeout);
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;
private ReceivedMessage receiveNext(String queueName, MqMessageFormat format, Duration timeout) {
MQQueue queue = null;
try { try {
while (remainingTime > 0 && !messageFound.get()) { queue = openQueue(queueName, CMQC.MQOO_INPUT_SHARED | CMQC.MQOO_FAIL_IF_QUIESCING);
Message message = consumer.receive(remainingTime); MQMessage message = new MQMessage();
MQGetMessageOptions getOptions = new MQGetMessageOptions();
getOptions.options = CMQC.MQGMO_WAIT | CMQC.MQGMO_NO_SYNCPOINT | CMQC.MQGMO_FAIL_IF_QUIESCING;
getOptions.waitInterval = toWaitInterval(timeout.toMillis());
if (message != null) { queue.get(message, getOptions);
received = decodeMessage(message, queueName, format); return decodeMessage(message, queueName, format);
messageFound.set(true); } catch (MQException e) {
} else { if (e.reasonCode == CMQC.MQRC_NO_MSG_AVAILABLE) {
// 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 throw new MessagingTimeoutException("No message matching filter found on queue '" + queueName
+ "' within " + timeout.toMillis() + "ms"); + "' 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); throw new MessagingDestinationException("Failed to receive message from queue: " + queueName, e);
} finally { } finally {
try { closeQueue(queue, queueName);
consumer.close(); }
} catch (JMSRuntimeException e) { }
LOG.warn("Failed to close consumer", e);
private ReceivedMessage receiveMatching(String queueName, String messageSelector, MqMessageFormat format,
Duration timeout) {
long deadline = System.currentTimeMillis() + timeout.toMillis();
long pollInterval = DEFAULT_POLL_INTERVAL_MS;
Selector selector = Selector.parse(messageSelector);
while (System.currentTimeMillis() < deadline) {
ReceivedMessage received = tryReceiveMatching(queueName, format, selector);
if (received != null) {
return received;
} }
sleepQuietly(Math.min(pollInterval, Math.max(1, deadline - System.currentTimeMillis())));
pollInterval = Math.min(pollInterval * 2, DEFAULT_MAX_POLL_INTERVAL_MS);
}
throw new MessagingTimeoutException("No message matching filter found on queue '" + queueName + "' within "
+ timeout.toMillis() + "ms");
}
private ReceivedMessage tryReceiveMatching(String queueName, MqMessageFormat format, Selector selector) {
MQQueue queue = null;
try {
queue = openQueue(queueName,
CMQC.MQOO_BROWSE | CMQC.MQOO_INPUT_SHARED | CMQC.MQOO_FAIL_IF_QUIESCING);
MQGetMessageOptions browseOptions = new MQGetMessageOptions();
browseOptions.options = CMQC.MQGMO_BROWSE_FIRST | CMQC.MQGMO_NO_SYNCPOINT | CMQC.MQGMO_FAIL_IF_QUIESCING;
while (true) {
MQMessage browsedMessage = new MQMessage();
try {
queue.get(browsedMessage, browseOptions);
} catch (MQException e) {
if (e.reasonCode == CMQC.MQRC_NO_MSG_AVAILABLE) {
return null;
}
throw e;
}
ReceivedMessage browsed = decodeMessage(browsedMessage, queueName, format);
if (selector.matches(browsed.getHeaders())) {
MQMessage removedMessage = new MQMessage();
MQGetMessageOptions removeOptions = new MQGetMessageOptions();
removeOptions.options = CMQC.MQGMO_MSG_UNDER_CURSOR | CMQC.MQGMO_NO_SYNCPOINT
| CMQC.MQGMO_FAIL_IF_QUIESCING;
queue.get(removedMessage, removeOptions);
return decodeMessage(removedMessage, queueName, format);
}
browseOptions.options = CMQC.MQGMO_BROWSE_NEXT | CMQC.MQGMO_NO_SYNCPOINT
| CMQC.MQGMO_FAIL_IF_QUIESCING;
}
} catch (MQException | RuntimeException e) {
throw new MessagingDestinationException("Failed to receive message from queue: " + queueName, e);
} finally {
closeQueue(queue, queueName);
} }
} }
@ -291,7 +309,8 @@ public class IbmMqConnector implements Connector {
* Browse a queue (non-destructive read). * Browse a queue (non-destructive read).
* *
* @param queueName Queue name * @param queueName Queue name
* @param messageSelector JMS message selector (optional) * @param messageSelector JMS-style message selector (optional). Equality
* predicates joined by AND are evaluated client-side.
* @param format Expected message format * @param format Expected message format
* @param maxMessages Maximum number of messages to browse * @param maxMessages Maximum number of messages to browse
* @return List of received messages * @return List of received messages
@ -299,170 +318,268 @@ public class IbmMqConnector implements Connector {
public List<ReceivedMessage> browse(String queueName, String messageSelector, MqMessageFormat format, public List<ReceivedMessage> browse(String queueName, String messageSelector, MqMessageFormat format,
int maxMessages) { int maxMessages) {
List<ReceivedMessage> messages = new ArrayList<>(); List<ReceivedMessage> messages = new ArrayList<>();
javax.jms.Queue queue = getQueue(queueName); Selector selector = Selector.parse(messageSelector);
MQQueue queue = null;
try (javax.jms.QueueBrowser browser = (messageSelector == null || messageSelector.isBlank()) try {
? jmsContext.createBrowser(queue) queue = openQueue(queueName, CMQC.MQOO_BROWSE | CMQC.MQOO_FAIL_IF_QUIESCING);
: jmsContext.createBrowser(queue, messageSelector)) {
Enumeration<?> enumeration = browser.getEnumeration(); MQGetMessageOptions browseOptions = new MQGetMessageOptions();
int count = 0; browseOptions.options = CMQC.MQGMO_BROWSE_FIRST | CMQC.MQGMO_NO_SYNCPOINT | CMQC.MQGMO_FAIL_IF_QUIESCING;
while (enumeration.hasMoreElements() && count < maxMessages) { while (messages.size() < maxMessages) {
Message message = (Message) enumeration.nextElement(); MQMessage message = new MQMessage();
if (message != null) { try {
ReceivedMessage received = decodeMessage(message, queueName, format); queue.get(message, browseOptions);
messages.add(received); } catch (MQException e) {
count++; if (e.reasonCode == CMQC.MQRC_NO_MSG_AVAILABLE) {
break;
}
throw e;
} }
ReceivedMessage received = decodeMessage(message, queueName, format);
if (selector.matches(received.getHeaders())) {
messages.add(received);
}
browseOptions.options = CMQC.MQGMO_BROWSE_NEXT | CMQC.MQGMO_NO_SYNCPOINT
| CMQC.MQGMO_FAIL_IF_QUIESCING;
} }
return messages; return messages;
} catch (JMSException e) { } catch (MQException e) {
throw new MessagingDestinationException("Failed to browse queue: " + queueName, e); throw new MessagingDestinationException("Failed to browse queue: " + queueName, e);
} finally {
closeQueue(queue, queueName);
} }
} }
/** /**
* Decode a JMS message to ReceivedMessage. * Decode an IBM MQ message to ReceivedMessage.
*/ */
private ReceivedMessage decodeMessage(Message jmsMessage, String queueName, MqMessageFormat format) { private ReceivedMessage decodeMessage(MQMessage mqMessage, String queueName, MqMessageFormat format) {
long timestamp; long timestamp = mqMessage.putDateTime != null ? mqMessage.putDateTime.getTimeInMillis()
try { : System.currentTimeMillis();
timestamp = jmsMessage.getJMSTimestamp();
} catch (JMSException e) {
timestamp = System.currentTimeMillis();
}
if (timestamp == 0) { if (timestamp == 0) {
timestamp = System.currentTimeMillis(); timestamp = System.currentTimeMillis();
} }
Map<String, String> headers = new HashMap<>();
extractMqHeadersAndProperties(mqMessage, headers, queueName);
byte[] data = readMessageBody(mqMessage);
String body; String body;
MessageContentType contentType; MessageContentType contentType;
Map<String, String> headers = new HashMap<>();
// Extract JMS properties as headers switch (format) {
extractJmsProperties(jmsMessage, headers); case XML -> {
body = new String(data, charsetFor(mqMessage.characterSet, UTF_8));
if (jmsMessage instanceof TextMessage textMessage) { contentType = MessageContentType.XML;
try { }
body = textMessage.getText(); case JSON -> {
} catch (JMSException e) { body = new String(data, charsetFor(mqMessage.characterSet, UTF_8));
throw new RuntimeException("Failed to read text message body", e); contentType = MessageContentType.JSON;
} }
contentType = switch (format) { case EBCDIC_870 -> {
case XML -> MessageContentType.XML; body = new String(data, EBCDIC_870);
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; contentType = MessageContentType.RAW_TEXT;
} else { }
try { case UTF8_1208 -> {
throw new IllegalArgumentException("Unsupported message type: " + jmsMessage.getJMSType()); body = new String(data, UTF_8);
} catch (JMSException e) { contentType = MessageContentType.RAW_TEXT;
throw new IllegalArgumentException("Unsupported message type", e); }
} default -> {
body = new String(data, UTF_8);
contentType = MessageContentType.RAW_TEXT;
}
} }
return new ReceivedMessage(body, contentType, headers, timestamp, queueName, null); return new ReceivedMessage(body, contentType, headers, timestamp, queueName, null);
} }
/** private byte[] readMessageBody(MQMessage message) {
* Decode BytesMessage body based on CCSID.
*/
private String decodeBytesMessage(BytesMessage bytesMessage, int ccsid) {
try { try {
long bodyLength; message.seek(0);
try { int length = message.getMessageLength();
bodyLength = bytesMessage.getBodyLength(); byte[] data = new byte[length];
} catch (JMSException e) { message.readFully(data);
throw new RuntimeException("Failed to get message body length", e); return data;
} } catch (EOFException e) {
byte[] data = new byte[(int) bodyLength]; throw new RuntimeException("Failed to seek message body", e);
bytesMessage.readBytes(data); } catch (IOException e) {
throw new RuntimeException("Failed to read message body", e);
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. * Extract MQ headers and message properties as headers.
*/ */
@SuppressWarnings("unchecked") private void extractMqHeadersAndProperties(MQMessage message, Map<String, String> headers, String queueName) {
private void extractJmsProperties(Message message, Map<String, String> headers) { headers.put("JMSMessageID", toJmsId(message.messageId));
try { headers.put("JMSCorrelationID", toJmsId(message.correlationId));
// Common JMS headers headers.put("JMSType", getStringProperty(message, ImqRequest.PROP_JMS_TYPE, ""));
headers.put("JMSMessageID", message.getJMSMessageID()); headers.put("JMSDestination", queueName);
try { headers.put("JMSDeliveryMode", String.valueOf(message.persistence));
headers.put("JMSType", message.getJMSType() != null ? message.getJMSType() : ""); headers.put("JMSPriority", String.valueOf(message.priority));
} catch (JMSException e) { headers.put("JMSTimestamp",
headers.put("JMSType", ""); message.putDateTime != null ? String.valueOf(message.putDateTime.getTimeInMillis()) : "");
} headers.put("MQFormat", message.format != null ? message.format.trim() : "");
try { headers.put("MQCharacterSet", String.valueOf(message.characterSet));
headers.put("JMSDestination", headers.put("MQEncoding", String.valueOf(message.encoding));
message.getJMSDestination() != null ? message.getJMSDestination().toString() : ""); headers.put("MQBackoutCount", String.valueOf(message.backoutCount));
} catch (JMSException e) { headers.put("MQReplyToQueue", message.replyToQueueName != null ? message.replyToQueueName.trim() : "");
headers.put("JMSDestination", ""); headers.put("MQReplyToQueueManager",
} message.replyToQueueManagerName != null ? message.replyToQueueManagerName.trim() : "");
try { headers.put("MQUserId", message.userId != null ? message.userId.trim() : "");
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> propertyNames = (Enumeration<String>) message.getPropertyNames(); try {
while (propertyNames.hasMoreElements()) { propertyNames = message.getPropertyNames("%");
String propName = propertyNames.nextElement(); } catch (MQException e) {
LOG.warn("Failed to extract MQ properties", e);
return;
}
while (propertyNames.hasMoreElements()) {
String propName = propertyNames.nextElement();
try {
Object propValue = message.getObjectProperty(propName); Object propValue = message.getObjectProperty(propName);
if (propValue != null) { if (propValue != null) {
headers.put(propName, propValue.toString()); headers.put(propName, propValue.toString());
} }
} catch (MQException e) {
LOG.warn("Failed to extract MQ property: {}", propName, e);
} }
} catch (JMSException e) {
LOG.warn("Failed to extract JMS properties", e);
} }
} }
/** private void applyMessageProperties(MQMessage message, Map<String, String> properties) {
* Get Queue object from queue name. if (properties == null) {
*/ return;
private javax.jms.Queue getQueue(String queueName) { }
return jmsContext.createQueue(queueName);
for (Map.Entry<String, String> entry : properties.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
try {
if (ImqRequest.PROP_JMS_CORRELATION_ID.equals(key)) {
message.correlationId = toMqId(value);
message.setStringProperty(key, value);
} else if (ImqRequest.PROP_JMS_MESSAGE_ID.equals(key)) {
message.messageId = toMqId(value);
} else if (ImqRequest.PROP_JMS_TYPE.equals(key)) {
message.setStringProperty(ImqRequest.PROP_JMS_TYPE, value);
} else {
message.setStringProperty(key, value);
}
} catch (MQException e) {
LOG.warn("Failed to set property: {}", key, e);
}
}
}
private String getStringProperty(MQMessage message, String property, String defaultValue) {
try {
String value = message.getStringProperty(property);
return value != null ? value : defaultValue;
} catch (MQException e) {
return defaultValue;
}
}
private MQQueue openQueue(String queueName, int openOptions) throws MQException {
ensureConnected();
return mqQueueManager.accessQueue(queueName, openOptions);
}
private void ensureConnected() {
if (mqQueueManager == null || !mqQueueManager.isConnected()) {
connect();
}
}
private void closeQueue(MQQueue queue, String queueName) {
if (queue == null) {
return;
}
try {
queue.close();
} catch (MQException e) {
LOG.warn("Failed to close IBM MQ queue: {}", queueName, e);
}
}
private int toWaitInterval(long timeoutMs) {
if (timeoutMs <= 0) {
return 0;
}
return timeoutMs > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) timeoutMs;
}
private Charset charsetFor(int ccsid, Charset defaultCharset) {
return switch (ccsid) {
case 870 -> EBCDIC_870;
case 1208 -> UTF_8;
default -> defaultCharset;
};
}
private byte[] toMqId(String value) {
if (value == null || value.isBlank()) {
return CMQC.MQMI_NONE.clone();
}
if (value.regionMatches(true, 0, JMS_ID_PREFIX, 0, JMS_ID_PREFIX.length())) {
String hex = value.substring(JMS_ID_PREFIX.length());
if (hex.length() == CMQC.MQ_MSG_ID_LENGTH * 2 && hex.matches("[0-9A-Fa-f]+")) {
return hexToBytes(hex);
}
}
byte[] id = CMQC.MQMI_NONE.clone();
byte[] source = value.getBytes(UTF_8);
System.arraycopy(source, 0, id, 0, Math.min(source.length, id.length));
return id;
}
private String toJmsId(byte[] value) {
if (value == null || value.length == 0 || Arrays.equals(value, CMQC.MQMI_NONE)) {
return "";
}
StringBuilder builder = new StringBuilder(JMS_ID_PREFIX);
for (byte b : value) {
builder.append(String.format(Locale.ROOT, "%02X", b));
}
return builder.toString();
}
private byte[] hexToBytes(String hex) {
byte[] data = new byte[hex.length() / 2];
for (int i = 0; i < data.length; i++) {
int index = i * 2;
data[i] = (byte) Integer.parseInt(hex.substring(index, index + 2), 16);
}
return data;
}
private void sleepQuietly(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new MessagingDestinationException("Interrupted while waiting for IBM MQ message", e);
}
} }
@Override @Override
public void close() { public void close() {
if (jmsContext != null) { if (mqQueueManager != null && mqQueueManager.isConnected()) {
try { try {
jmsContext.close(); mqQueueManager.disconnect();
LOG.info("Closed connection to IBM MQ: {}", queueManager); LOG.info("Closed connection to IBM MQ: {}", queueManager);
} catch (Exception e) { } catch (MQException e) {
LOG.error("Failed to close IBM MQ connection", e); LOG.error("Failed to close IBM MQ connection", e);
} }
} }
@ -477,7 +594,7 @@ public class IbmMqConnector implements Connector {
throw new IllegalStateException("Keystore not found: " + keystorePath); throw new IllegalStateException("Keystore not found: " + keystorePath);
} }
keyStore.load(ksStream, keystorePassword.toCharArray()); keyStore.load(ksStream, keystorePassword.toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(keyStore, keystorePassword.toCharArray()); kmf.init(keyStore, keystorePassword.toCharArray());
@ -499,4 +616,34 @@ public class IbmMqConnector implements Connector {
return sslContext.getSocketFactory(); return sslContext.getSocketFactory();
} }
private record Selector(Map<String, String> expectedValues) {
private static Selector parse(String selector) {
if (selector == null || selector.isBlank()) {
return new Selector(Map.of());
}
Map<String, String> predicates = new HashMap<>();
for (String predicate : selector.split("(?i)\\s+AND\\s+")) {
Matcher matcher = SELECTOR_EQUALS_PATTERN.matcher(predicate);
if (!matcher.matches()) {
throw new IllegalArgumentException(
"Unsupported IBM MQ selector expression. Supported format: property = 'value' joined by AND. Selector: "
+ selector);
}
predicates.put(matcher.group(1), matcher.group(2).replace("''", "'"));
}
return new Selector(predicates);
}
private boolean matches(Map<String, String> headers) {
for (Map.Entry<String, String> expectedValue : expectedValues.entrySet()) {
if (!expectedValue.getValue().equals(headers.get(expectedValue.getKey()))) {
return false;
}
}
return true;
}
}
} }

View File

@ -1,348 +1,369 @@
package cz.moneta.test.harness.connectors.messaging; package cz.moneta.test.harness.connectors.messaging;
import java.nio.charset.StandardCharsets; import cz.moneta.test.harness.connectors.messaging.kafkautils.CustomKafkaAvroDeserializer;
import java.time.Duration; import cz.moneta.test.harness.connectors.messaging.kafkautils.JsonToAvroConverter;
import java.util.ArrayList; import cz.moneta.test.harness.support.messaging.exception.MessagingConnectionException;
import java.util.Collections; import cz.moneta.test.harness.support.messaging.exception.MessagingDestinationException;
import java.util.HashMap; import cz.moneta.test.harness.support.messaging.exception.MessagingSchemaException;
import java.util.List; import cz.moneta.test.harness.support.messaging.exception.MessagingTimeoutException;
import java.util.Map; import cz.moneta.test.harness.support.messaging.kafka.MessageContentType;
import java.util.Properties; import cz.moneta.test.harness.support.messaging.kafka.ReceivedMessage;
import java.util.concurrent.ExecutionException; import io.confluent.kafka.schemaregistry.client.CachedSchemaRegistryClient;
import java.util.function.Predicate; import io.confluent.kafka.schemaregistry.client.SchemaRegistryClientConfig;
import io.confluent.kafka.serializers.AbstractKafkaSchemaSerDeConfig;
import org.apache.avro.generic.GenericRecord; import io.confluent.kafka.serializers.KafkaAvroSerializer;
import org.apache.kafka.clients.consumer.ConsumerConfig; import org.apache.avro.generic.GenericRecord;
import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecords; import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.KafkaConsumer; import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.clients.producer.ProducerConfig; import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord; import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.TopicPartition; import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.common.header.Headers; import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.common.serialization.StringDeserializer; import org.apache.kafka.common.header.Headers;
import org.apache.kafka.common.serialization.StringSerializer; import org.apache.kafka.common.serialization.StringDeserializer;
import org.slf4j.Logger; import org.apache.kafka.common.serialization.StringSerializer;
import org.slf4j.LoggerFactory; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import cz.moneta.test.harness.messaging.exception.MessagingConnectionException;
import cz.moneta.test.harness.messaging.exception.MessagingDestinationException; import java.nio.charset.StandardCharsets;
import cz.moneta.test.harness.messaging.exception.MessagingSchemaException; import java.time.Duration;
import cz.moneta.test.harness.messaging.exception.MessagingTimeoutException; import java.util.*;
import cz.moneta.test.harness.support.messaging.kafka.MessageContentType; import java.util.concurrent.ExecutionException;
import cz.moneta.test.harness.support.messaging.kafka.ReceivedMessage; import java.util.function.Predicate;
import io.confluent.kafka.schemaregistry.client.CachedSchemaRegistryClient;
import io.confluent.kafka.serializers.KafkaAvroDeserializer; /**
import io.confluent.kafka.serializers.KafkaAvroSerializer; * Kafka connector for sending and receiving messages.
* Supports Avro serialization with Confluent Schema Registry.
/** * <p>
* Kafka connector for sending and receiving messages. * Uses manual partition assignment (no consumer group) for test isolation.
* Supports Avro serialization with Confluent Schema Registry. * Each receive operation creates a new consumer to avoid offset sharing.
* <p> */
* Uses manual partition assignment (no consumer group) for test isolation. public class KafkaConnector implements cz.moneta.test.harness.connectors.Connector {
* Each receive operation creates a new consumer to avoid offset sharing.
*/ private static final Logger LOG = LogManager.getLogger(KafkaConnector.class);
public class KafkaConnector implements cz.moneta.test.harness.connectors.Connector {
private final Properties producerConfig;
private static final Logger LOG = LoggerFactory.getLogger(KafkaConnector.class); private final Properties consumerConfig;
private final String schemaRegistryUrl;
private final Properties producerConfig; private final CachedSchemaRegistryClient schemaRegistryClient;
private final Properties consumerConfig; private KafkaProducer<String, GenericRecord> producer;
private final String schemaRegistryUrl;
private final CachedSchemaRegistryClient schemaRegistryClient; /**
private KafkaProducer<String, GenericRecord> producer; * Creates a new KafkaConnector.
*
/** * @param bootstrapServers Kafka bootstrap servers
* Creates a new KafkaConnector. * @param apiKey Kafka API key
* * @param apiSecret Kafka API secret
* @param bootstrapServers Kafka bootstrap servers * @param schemaRegistryUrl Schema Registry URL
* @param apiKey Kafka API key * @param schemaRegistryApiKey Schema Registry API key
* @param apiSecret Kafka API secret * @param schemaRegistryApiSecret Schema Registry API secret
* @param schemaRegistryUrl Schema Registry URL */
* @param schemaRegistryApiKey Schema Registry API key public KafkaConnector(String bootstrapServers,
* @param schemaRegistryApiSecret Schema Registry API secret String apiKey,
*/ String apiSecret,
public KafkaConnector(String bootstrapServers, String schemaRegistryUrl,
String apiKey, String schemaRegistryApiKey,
String apiSecret, String schemaRegistryApiSecret) {
String schemaRegistryUrl, this.schemaRegistryUrl = schemaRegistryUrl;
String schemaRegistryApiKey,
String schemaRegistryApiSecret) { HashMap<String, String> schemaRegistryProps = new HashMap<>();
this.schemaRegistryUrl = schemaRegistryUrl; schemaRegistryProps.put(SchemaRegistryClientConfig.BASIC_AUTH_CREDENTIALS_SOURCE, "USER_INFO");
this.schemaRegistryClient = new CachedSchemaRegistryClient( schemaRegistryProps.put(SchemaRegistryClientConfig.USER_INFO_CONFIG, schemaRegistryApiKey + ":" + schemaRegistryApiSecret);
Collections.singletonList(schemaRegistryUrl), 100, new HashMap<>()); this.schemaRegistryClient = new CachedSchemaRegistryClient(
Collections.singletonList(schemaRegistryUrl), 100, schemaRegistryProps);
this.producerConfig = createProducerConfig(bootstrapServers, apiKey, apiSecret);
this.consumerConfig = createConsumerConfig(bootstrapServers, schemaRegistryApiKey, schemaRegistryApiSecret); this.producerConfig = createProducerConfig(bootstrapServers, apiKey, apiSecret, schemaRegistryApiKey, schemaRegistryApiSecret);
} this.consumerConfig = createConsumerConfig(bootstrapServers, apiKey, apiSecret, schemaRegistryApiKey, schemaRegistryApiSecret);
}
/**
* Creates producer configuration. /**
*/ * Creates producer configuration.
private Properties createProducerConfig(String bootstrapServers, String apiKey, String apiSecret) { */
Properties config = new Properties(); private Properties createProducerConfig(String bootstrapServers, String apiKey, String apiSecret,
config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers); String schemaRegistryApiKey, String schemaRegistryApiSecret) {
config.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); Properties config = new Properties();
config.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, KafkaAvroSerializer.class); config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
config.put("schema.registry.url", schemaRegistryUrl); config.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
config.put(ProducerConfig.ACKS_CONFIG, "all"); config.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, KafkaAvroSerializer.class);
config.put(ProducerConfig.LINGER_MS_CONFIG, 1); config.put(ProducerConfig.ACKS_CONFIG, "all");
config.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384); 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(AbstractKafkaSchemaSerDeConfig.AUTO_REGISTER_SCHEMAS, false);
// config.put("sasl.mechanism", "PLAIN"); config.put(AbstractKafkaSchemaSerDeConfig.USE_LATEST_VERSION, true);
// config.put("sasl.jaas.config", config.put(AbstractKafkaSchemaSerDeConfig.SCHEMA_REGISTRY_URL_CONFIG, schemaRegistryUrl);
// "org.apache.kafka.common.security.plain.PlainLoginModule required " + config.put(SchemaRegistryClientConfig.BASIC_AUTH_CREDENTIALS_SOURCE, "USER_INFO");
// "username=\"" + apiKey + "\" password=\"" + apiSecret + "\";"); config.put(SchemaRegistryClientConfig.USER_INFO_CONFIG, schemaRegistryApiKey + ":" + schemaRegistryApiSecret);
// SSL configuration // SASL/PLAIN authentication
// config.put("ssl.endpoint.identification.algorithm", "https"); config.put("security.protocol", "SASL_SSL");
config.put("sasl.mechanism", "PLAIN");
return config; config.put("sasl.jaas.config",
} "org.apache.kafka.common.security.plain.PlainLoginModule required " +
"username=\"" + apiKey + "\" password=\"" + apiSecret + "\";");
/**
* Creates consumer configuration. // SSL configuration
*/ config.put("ssl.endpoint.identification.algorithm", "https");
private Properties createConsumerConfig(String bootstrapServers, String apiKey, String apiSecret) {
Properties config = new Properties(); return config;
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"); * Creates consumer configuration.
config.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false); */
private Properties createConsumerConfig(String bootstrapServers, String apiKey, String apiSecret,
// SASL/PLAIN authentication String schemaRegistryApiKey, String schemaRegistryApiSecret) {
config.put("security.protocol", "SASL_SSL"); Properties config = new Properties();
config.put("sasl.mechanism", "PLAIN"); config.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
config.put("sasl.jaas.config", config.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
"org.apache.kafka.common.security.plain.PlainLoginModule required " + config.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, CustomKafkaAvroDeserializer.class);
"username=\"" + apiKey + "\" password=\"" + apiSecret + "\";"); config.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "none");
config.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
// Schema Registry for deserialization
config.put("schema.registry.url", schemaRegistryUrl);
config.put("specific.avro.reader", false); // SASL/PLAIN authentication
config.put("security.protocol", "SASL_SSL");
// SSL configuration config.put("sasl.mechanism", "PLAIN");
config.put("ssl.endpoint.identification.algorithm", "https"); config.put("sasl.jaas.config",
"org.apache.kafka.common.security.plain.PlainLoginModule required " +
return config; "username=\"" + apiKey + "\" password=\"" + apiSecret + "\";");
}
// Schema Registry for deserialization
/**
* Sends a message to a Kafka topic. config.put(AbstractKafkaSchemaSerDeConfig.SCHEMA_REGISTRY_URL_CONFIG, schemaRegistryUrl);
*/ config.put(AbstractKafkaSchemaSerDeConfig.AUTO_REGISTER_SCHEMAS, false);
public void send(String topic, String key, String jsonPayload, Map<String, String> headers) { config.put(AbstractKafkaSchemaSerDeConfig.USE_LATEST_VERSION, true);
try { config.put(SchemaRegistryClientConfig.BASIC_AUTH_CREDENTIALS_SOURCE, "USER_INFO");
org.apache.avro.Schema schema = getSchemaForTopic(topic); config.put(SchemaRegistryClientConfig.USER_INFO_CONFIG, schemaRegistryApiKey + ":" + schemaRegistryApiSecret);
GenericRecord record = jsonToAvro(jsonPayload, schema); config.put("specific.avro.reader", false);
ProducerRecord<String, GenericRecord> producerRecord = // SSL configuration
new ProducerRecord<>(topic, key, record); config.put("ssl.endpoint.identification.algorithm", "https");
// Add headers return config;
if (headers != null) { }
Headers kafkaHeaders = producerRecord.headers();
headers.forEach((k, v) -> /**
kafkaHeaders.add(k, v.getBytes(StandardCharsets.UTF_8))); * Sends a message to a Kafka topic.
} */
public void send(String topic, String key, String jsonPayload, Map<String, String> headers) {
// Send and wait for confirmation try {
getProducer().send(producerRecord, (metadata, exception) -> { org.apache.avro.Schema schema = getSchemaForTopic(topic);
if (exception != null) { GenericRecord record = jsonToAvro(jsonPayload, schema);
LOG.error("Failed to send message", exception);
} else { ProducerRecord<String, GenericRecord> producerRecord =
LOG.debug("Message sent to topic {} partition {} offset {}", new ProducerRecord<>(topic, key, record);
metadata.topic(), metadata.partition(), metadata.offset());
} // Add headers
}).get(10, java.util.concurrent.TimeUnit.SECONDS); if (headers != null) {
Headers kafkaHeaders = producerRecord.headers();
} catch (ExecutionException e) { headers.forEach((k, v) ->
throw new MessagingConnectionException( kafkaHeaders.add(k, v.getBytes(StandardCharsets.UTF_8)));
"Failed to send message to topic " + topic, e.getCause()); }
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // Send and wait for confirmation
throw new MessagingConnectionException( getProducer().send(producerRecord, (metadata, exception) -> {
"Interrupted while sending message to topic " + topic, e); if (exception != null) {
} catch (MessagingSchemaException e) { LOG.error("Failed to send message", exception);
throw e; } else {
} catch (Exception e) { LOG.debug("Message sent to topic {} partition {} offset {}",
throw new MessagingSchemaException( metadata.topic(), metadata.partition(), metadata.offset());
"Failed to convert JSON to Avro for topic " + topic, e); }
} }).get(10, java.util.concurrent.TimeUnit.SECONDS);
}
} catch (ExecutionException e) {
/** throw new MessagingConnectionException(
* Receives a message from a Kafka topic matching the filter. "Failed to send message to topic " + topic, e.getCause());
*/ } catch (InterruptedException e) {
public List<ReceivedMessage> receive(String topic, Thread.currentThread().interrupt();
Predicate<ReceivedMessage> filter, throw new MessagingConnectionException(
Duration timeout) { "Interrupted while sending message to topic " + topic, e);
KafkaConsumer<String, GenericRecord> consumer = null; } catch (MessagingSchemaException e) {
try { throw e;
consumer = new KafkaConsumer<>(consumerConfig); } catch (Exception e) {
throw new MessagingSchemaException(
// Get partitions for the topic "Failed to convert JSON to Avro for topic " + topic, e);
List<TopicPartition> partitions = getPartitionsForTopic(consumer, topic); }
if (partitions.isEmpty()) { }
throw new MessagingDestinationException(
"Topic '" + topic + "' does not exist or has no partitions"); /**
} * Receives a message from a Kafka topic matching the filter.
*/
// Assign partitions and seek to end public List<ReceivedMessage> receive(String topic,
consumer.assign(partitions); Predicate<ReceivedMessage> filter,
// consumer.seekToBeginning(partitions); Duration timeout, boolean startFromBeginning) {
consumer.seekToBeginning(partitions); KafkaConsumer<String, GenericRecord> consumer = null;
try {
// Poll loop with exponential backoff consumer = new KafkaConsumer<>(consumerConfig);
long startTime = System.currentTimeMillis();
Duration pollInterval = Duration.ofMillis(100); // Get partitions for the topic
Duration maxPollInterval = Duration.ofSeconds(1); List<TopicPartition> partitions = getPartitionsForTopic(consumer, topic);
if (partitions.isEmpty()) {
while (Duration.ofMillis(System.currentTimeMillis() - startTime).compareTo(timeout) < 0) { throw new MessagingDestinationException(
ConsumerRecords<String, GenericRecord> records = consumer.poll(pollInterval); "Topic '" + topic + "' does not exist or has no partitions");
}
for (ConsumerRecord<String, GenericRecord> record : records) {
ReceivedMessage message = convertToReceivedMessage(record, topic); consumer.assign(partitions);
if (filter.test(message)) { if (startFromBeginning) {
LOG.debug("Found matching message on topic {} partition {} offset {}", consumer.seekToBeginning(partitions);
record.topic(), record.partition(), record.offset()); } else {
return Collections.singletonList(message); consumer.seekToEnd(partitions);
} }
}
// Poll loop with exponential backoff
// Exponential backoff long startTime = System.currentTimeMillis();
pollInterval = Duration.ofMillis( Duration pollInterval = Duration.ofMillis(100);
Math.min(pollInterval.toMillis() * 2, maxPollInterval.toMillis())); Duration maxPollInterval = Duration.ofSeconds(1);
}
while (Duration.ofMillis(System.currentTimeMillis() - startTime).compareTo(timeout) < 0) {
throw new MessagingTimeoutException( ConsumerRecords<String, GenericRecord> records = consumer.poll(pollInterval);
"No message matching filter found on topic '" + topic + "' within " + timeout);
for (ConsumerRecord<String, GenericRecord> record : records) {
} finally { ReceivedMessage message = convertToReceivedMessage(record, topic);
if (consumer != null) { if (filter.test(message)) {
consumer.close(); LOG.debug("Found matching message on topic {} partition {} offset {}",
} record.topic(), record.partition(), record.offset());
} return Collections.singletonList(message);
} }
}
/**
* Gets partitions for a topic. // Exponential backoff
*/ pollInterval = Duration.ofMillis(
private List<TopicPartition> getPartitionsForTopic(KafkaConsumer<?, ?> consumer, String topic) { Math.min(pollInterval.toMillis() * 2, maxPollInterval.toMillis()));
List<TopicPartition> partitions = new ArrayList<>(); }
List<org.apache.kafka.common.PartitionInfo> partitionInfos = consumer.partitionsFor(topic);
if (partitionInfos != null) { throw new MessagingTimeoutException(
for (org.apache.kafka.common.PartitionInfo partitionInfo : partitionInfos) { "No message matching filter found on topic '" + topic + "' within " + timeout);
partitions.add(new TopicPartition(topic, partitionInfo.partition()));
} } finally {
} if (consumer != null) {
return partitions; consumer.close();
} }
}
/** }
* Saves current offsets for a topic.
*/ /**
public Map<TopicPartition, Long> saveOffsets(String topic) { * Gets partitions for a topic.
return new HashMap<>(); */
} private List<TopicPartition> getPartitionsForTopic(KafkaConsumer<?, ?> consumer, String topic) {
List<TopicPartition> partitions = new ArrayList<>();
/** List<org.apache.kafka.common.PartitionInfo> partitionInfos = consumer.partitionsFor(topic);
* Closes the connector and releases resources. if (partitionInfos != null) {
*/ for (org.apache.kafka.common.PartitionInfo partitionInfo : partitionInfos) {
@Override partitions.add(new TopicPartition(topic, partitionInfo.partition()));
public void close() { }
if (producer != null) { }
producer.close(); return partitions;
} }
}
/**
/** * Saves current offsets for a topic.
* Gets or creates the producer (singleton, thread-safe). */
*/ public Map<TopicPartition, Long> saveOffsets(String topic) {
private KafkaProducer<String, GenericRecord> getProducer() { return new HashMap<>();
if (producer == null) { }
synchronized (this) {
if (producer == null) { /**
producer = new KafkaProducer<>(producerConfig); * Closes the connector and releases resources.
} */
} @Override
} public void close() {
return producer; if (producer != null) {
} producer.close();
}
/** }
* Retrieves schema from Schema Registry based on topic name.
*/ /**
private org.apache.avro.Schema getSchemaForTopic(String topic) { * Gets or creates the producer (singleton, thread-safe).
String subject = topic + "-value"; */
try { private KafkaProducer<String, GenericRecord> getProducer() {
io.confluent.kafka.schemaregistry.client.SchemaMetadata metadata = if (producer == null) {
schemaRegistryClient.getLatestSchemaMetadata(subject); synchronized (this) {
return new org.apache.avro.Schema.Parser().parse(metadata.getSchema()); if (producer == null) {
} catch (Exception e) { producer = new KafkaProducer<>(producerConfig);
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."); return producer;
} }
throw new MessagingSchemaException(
"Failed to retrieve schema for topic '" + topic + "'", e); /**
} * Retrieves schema from Schema Registry based on topic name.
} */
private org.apache.avro.Schema getSchemaForTopic(String topic) {
/** String subject = topic + "-value";
* Converts JSON string to Avro GenericRecord. try {
*/ io.confluent.kafka.schemaregistry.client.SchemaMetadata metadata =
private GenericRecord jsonToAvro(String json, org.apache.avro.Schema schema) { schemaRegistryClient.getLatestSchemaMetadata(subject);
try { return new org.apache.avro.Schema.Parser().parse(metadata.getSchema());
GenericRecord genericRecord = JsonToAvroConverter.processJson(json, schema); } catch (Exception e) {
return genericRecord; if (e.getMessage() != null && e.getMessage().contains("404")) {
} catch (Exception e) { throw new MessagingSchemaException(
throw new MessagingSchemaException("Failed to convert JSON to Avro: " + e.getMessage(), e); "Schema not found for subject '" + subject + "' in Schema Registry. " +
} "Make sure the topic exists and schema is registered.");
} }
throw new MessagingSchemaException(
/** "Failed to retrieve schema for topic '" + topic + "'", e);
* Converts Kafka ConsumerRecord to ReceivedMessage. }
*/ }
private ReceivedMessage convertToReceivedMessage(ConsumerRecord<String, GenericRecord> record, String topic) {
try { /**
String jsonBody = avroToJson(record.value()); * Converts JSON string to Avro GenericRecord.
*/
Map<String, String> headers = new HashMap<>(); private GenericRecord jsonToAvro(String json, org.apache.avro.Schema schema) {
Headers kafkaHeaders = record.headers(); try {
for (org.apache.kafka.common.header.Header header : kafkaHeaders) { GenericRecord genericRecord = JsonToAvroConverter.processJson(json, schema);
if (header.value() != null) { return genericRecord;
headers.put(header.key(), new String(header.value(), StandardCharsets.UTF_8)); } catch (Exception e) {
} throw new MessagingSchemaException("Failed to convert JSON to Avro: " + e.getMessage(), e);
} }
}
return ReceivedMessage.builder()
.body(jsonBody) /**
.contentType(MessageContentType.JSON) * Converts Kafka ConsumerRecord to ReceivedMessage.
.headers(headers) */
.timestamp(record.timestamp()) private ReceivedMessage convertToReceivedMessage(ConsumerRecord<String, GenericRecord> record, String topic) {
.source(topic) try {
.key(record.key()) String jsonBody;
.build(); if (null != record.value()) {
jsonBody = avroToJson(record.value());
} catch (Exception e) { } else {
LOG.error("Failed to convert Avro record to ReceivedMessage", e); jsonBody = "";
throw new RuntimeException("Failed to convert message", e); }
} Map<String, String> headers = new HashMap<>();
} Headers kafkaHeaders = record.headers();
for (org.apache.kafka.common.header.Header header : kafkaHeaders) {
/** if (header.value() != null) {
* Converts Avro GenericRecord to JSON string. headers.put(header.key(), new String(header.value(), StandardCharsets.UTF_8));
*/ }
private String avroToJson(GenericRecord record) { }
try {
return record.toString(); return ReceivedMessage.builder()
} catch (Exception e) { .body(jsonBody)
throw new RuntimeException("Failed to convert Avro to JSON: " + e.getMessage(), e); .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();
} catch (Exception e) {
throw new RuntimeException("Failed to convert Avro to JSON: " + e.getMessage(), e);
}
}
}

View File

@ -0,0 +1,54 @@
package cz.moneta.test.harness.connectors.messaging.kafkautils;
import cz.moneta.test.harness.connectors.messaging.KafkaConnector;
import io.confluent.kafka.serializers.KafkaAvroDeserializer;
import org.apache.avro.Schema;
import org.apache.kafka.common.errors.SerializationException;
import org.apache.kafka.common.header.Headers;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class CustomKafkaAvroDeserializer extends KafkaAvroDeserializer {
private static final Logger LOG = LogManager.getLogger(KafkaConnector.class);
@Override
public Object deserialize(String topic, byte[] bytes) {
try {
return super.deserialize(topic, bytes);
} catch (SerializationException e) {
LOG.error("Message deserialization by avro schema failed", e);
return null;
}
}
@Override
public Object deserialize(String topic, Headers headers, byte[] bytes) {
try {
return super.deserialize(topic, headers, bytes);
} catch (SerializationException e) {
LOG.error("Message deserialization by avro schema failed", e);
return null;
}
}
@Override
public Object deserialize(String topic, byte[] bytes, Schema readerSchema) {
try {
return super.deserialize(topic, bytes, readerSchema);
} catch (SerializationException e) {
LOG.error("Message deserialization by avro schema failed", e);
return null;
}
}
@Override
public Object deserialize(String topic, Headers headers, byte[] bytes, Schema readerSchema) {
try {
return super.deserialize(topic, headers, bytes, readerSchema);
} catch (SerializationException e) {
LOG.error("Message deserialization by avro schema failed", e);
return null;
}
}
}

View File

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

View File

@ -1,338 +1,338 @@
package cz.moneta.test.harness.connectors.mobile; package cz.moneta.test.harness.connectors.mobile;
import cz.moneta.test.harness.connectors.Connector; import cz.moneta.test.harness.connectors.Connector;
import cz.moneta.test.harness.context.StoreAccessor; import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.smartbanka.MobilePlatform; import cz.moneta.test.harness.endpoints.smartbanka.MobilePlatform;
import cz.moneta.test.harness.exception.HarnessException; import cz.moneta.test.harness.exception.HarnessException;
import cz.moneta.test.harness.support.mobile.MobileLookup; import cz.moneta.test.harness.support.mobile.MobileLookup;
import cz.moneta.test.harness.support.web.TextContainer; import cz.moneta.test.harness.support.web.TextContainer;
import cz.moneta.test.harness.support.web.Until; import cz.moneta.test.harness.support.web.Until;
import io.appium.java_client.AppiumDriver; import io.appium.java_client.AppiumDriver;
import io.appium.java_client.MobileBy; import io.appium.java_client.MobileBy;
import io.appium.java_client.remote.MobileCapabilityType; import io.appium.java_client.remote.MobileCapabilityType;
import io.appium.java_client.touch.offset.PointOption; import io.appium.java_client.touch.offset.PointOption;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.openqa.selenium.*; import org.openqa.selenium.*;
import org.openqa.selenium.remote.CapabilityType; import org.openqa.selenium.remote.CapabilityType;
import org.openqa.selenium.remote.DesiredCapabilities; import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.support.ui.ExpectedCondition; import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.ExpectedConditions; import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.FluentWait; import org.openqa.selenium.support.ui.FluentWait;
import org.openqa.selenium.support.ui.WebDriverWait; import org.openqa.selenium.support.ui.WebDriverWait;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.time.Duration; import java.time.Duration;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Stream; import java.util.stream.Stream;
import static io.appium.java_client.touch.offset.PointOption.point; import static io.appium.java_client.touch.offset.PointOption.point;
import static java.time.Duration.ofMillis; import static java.time.Duration.ofMillis;
import static org.apache.commons.collections.CollectionUtils.isNotEmpty; import static org.apache.commons.collections.CollectionUtils.isNotEmpty;
import static org.openqa.selenium.support.ui.ExpectedConditions.alertIsPresent; import static org.openqa.selenium.support.ui.ExpectedConditions.alertIsPresent;
public abstract class AppiumMobileConnector<A extends AppiumDriver> implements Connector { public abstract class AppiumMobileConnector<A extends AppiumDriver> implements Connector {
private static final Logger logger = LogManager.getLogger(AppiumMobileConnector.class); private static final Logger logger = LogManager.getLogger(AppiumMobileConnector.class);
private static final int LAZY_ELEMENT_RENDER_TIMEOUT = 5; private static final int LAZY_ELEMENT_RENDER_TIMEOUT = 5;
protected String deviceName; protected String deviceName;
protected String udid; protected String udid;
protected String bundleId; protected String bundleId;
protected String appBinaryPath; protected String appBinaryPath;
protected String host; protected String host;
protected final StoreAccessor store; protected final StoreAccessor store;
private final Function<String, By> defaultLookup; private final Function<String, By> defaultLookup;
protected abstract A getDriver(); protected abstract A getDriver();
public abstract void captureVideo(String snapshotFileName); public abstract void captureVideo(String snapshotFileName);
public abstract void resetApp(); public abstract void resetApp();
public abstract void close(); public abstract void close();
public AppiumMobileConnector(Function<String, By> defaultLookup, StoreAccessor store, String host, String appBinaryPath) { public AppiumMobileConnector(Function<String, By> defaultLookup, StoreAccessor store, String host, String appBinaryPath) {
this.store = store; this.store = store;
this.host = host; this.host = host;
this.defaultLookup = defaultLookup; this.defaultLookup = defaultLookup;
this.appBinaryPath = appBinaryPath; this.appBinaryPath = appBinaryPath;
} }
public String getDeviceName() { public String getDeviceName() {
return deviceName; return deviceName;
} }
public StoreAccessor getStore() { public StoreAccessor getStore() {
return store; return store;
} }
protected void setDesiredCapabilities(Platform platformName, String deviceName, String udid, String automationName, DesiredCapabilities capabilities) { protected void setDesiredCapabilities(Platform platformName, String deviceName, String udid, String automationName, DesiredCapabilities capabilities) {
capabilities.setCapability(CapabilityType.PLATFORM_NAME, platformName); capabilities.setCapability(CapabilityType.PLATFORM_NAME, platformName);
// capabilities.setCapability(MobileCapabilityType.DEVICE_NAME, deviceName); // capabilities.setCapability(MobileCapabilityType.DEVICE_NAME, deviceName);
// capabilities.setCapability(MobileCapabilityType.NEW_COMMAND_TIMEOUT, 150); // capabilities.setCapability(MobileCapabilityType.NEW_COMMAND_TIMEOUT, 150);
// capabilities.setCapability(MobileCapabilityType.NO_RESET, true); // capabilities.setCapability(MobileCapabilityType.NO_RESET, true);
// capabilities.setCapability(MobileCapabilityType.UDID, udid); // capabilities.setCapability(MobileCapabilityType.UDID, udid);
// capabilities.setCapability(MobileCapabilityType.AUTOMATION_NAME, automationName); // capabilities.setCapability(MobileCapabilityType.AUTOMATION_NAME, automationName);
// capabilities.setCapability(MobileCapabilityType.APP, appBinaryPath); // capabilities.setCapability(MobileCapabilityType.APP, appBinaryPath);
} }
protected Optional<A> launchExistingApp(A driver) { protected Optional<A> launchExistingApp(A driver) {
// return Optional.of(driver) // return Optional.of(driver)
// .map(app -> { // .map(app -> {
// driver.launchApp(); // driver.launchApp();
// return driver; // return driver;
// }); // });
return Optional.of(driver); return Optional.of(driver);
// TODO: actually not supported // TODO: actually not supported
} }
private void installApp(String path, A driver) { private void installApp(String path, A driver) {
// Optional.of(bundleId) // Optional.of(bundleId)
// .filter(app -> !driver.isAppInstalled(app)) // .filter(app -> !driver.isAppInstalled(app))
// .map(installApp -> { // .map(installApp -> {
// logger.info("App was not found on the device " + deviceName + " and will be installed"); // logger.info("App was not found on the device " + deviceName + " and will be installed");
// try { // try {
// driver.installApp(path); // driver.installApp(path);
// } catch (WebDriverException e) { // } catch (WebDriverException e) {
// throw new HarnessException("The application at " + path + " does not exist or is not accessible. " + // throw new HarnessException("The application at " + path + " does not exist or is not accessible. " +
// "Please, check if the path is correct"); // "Please, check if the path is correct");
// } // }
// return driver; // return driver;
// }); // });
// TODO: actually not supported // TODO: actually not supported
} }
protected void mobileLanguage(DesiredCapabilities capabilities, String ENPlatformLocale, String CZPlatformLocale) { protected void mobileLanguage(DesiredCapabilities capabilities, String ENPlatformLocale, String CZPlatformLocale) {
String languageKey = store.getConfig("appium.language", "cs"); String languageKey = store.getConfig("appium.language", "cs");
switch (languageKey) { switch (languageKey) {
case "en": case "en":
capabilities.setCapability(MobileCapabilityType.LANGUAGE, "en"); capabilities.setCapability(MobileCapabilityType.LANGUAGE, "en");
capabilities.setCapability(MobileCapabilityType.LOCALE, ENPlatformLocale); capabilities.setCapability(MobileCapabilityType.LOCALE, ENPlatformLocale);
break; break;
case "cs": case "cs":
capabilities.setCapability(MobileCapabilityType.LANGUAGE, "cs"); capabilities.setCapability(MobileCapabilityType.LANGUAGE, "cs");
capabilities.setCapability(MobileCapabilityType.LOCALE, CZPlatformLocale); capabilities.setCapability(MobileCapabilityType.LOCALE, CZPlatformLocale);
break; break;
default: default:
throw new HarnessException("Please, define the appium.language parameter. Available language is cs or en"); throw new HarnessException("Please, define the appium.language parameter. Available language is cs or en");
} }
} }
protected Boolean checkVideoRecord() { protected Boolean checkVideoRecord() {
return Boolean.parseBoolean(store.getConfig("appium.video.record", "false")); return Boolean.parseBoolean(store.getConfig("appium.video.record", "false"));
} }
public void click(String path, MobileLookup mobileLookup) { public void click(String path, MobileLookup mobileLookup) {
// new TouchAction<>(getDriver()) // new TouchAction<>(getDriver())
// .tap(tapOptions().withElement(element(waitForLazyElement(path, mobileLookup)))) // .tap(tapOptions().withElement(element(waitForLazyElement(path, mobileLookup))))
// .perform(); // .perform();
// TODO: actually not supported // TODO: actually not supported
} }
protected WebElement waitForLazyElement(String path, MobileLookup mobileLookup) { protected WebElement waitForLazyElement(String path, MobileLookup mobileLookup) {
doWaitForLazyElement(path, mobileLookup); doWaitForLazyElement(path, mobileLookup);
return getDriver().findElement(resolveMobileLookup(mobileLookup, path)); return getDriver().findElement(resolveMobileLookup(mobileLookup, path));
} }
List<WebElement> waitForLazyElements(String path, MobileLookup mobileLookup) { List<WebElement> waitForLazyElements(String path, MobileLookup mobileLookup) {
doWaitForLazyElement(path, mobileLookup); doWaitForLazyElement(path, mobileLookup);
return getDriver().findElements(resolveMobileLookup(mobileLookup, path)); return getDriver().findElements(resolveMobileLookup(mobileLookup, path));
} }
private void doWaitForLazyElement(String path, MobileLookup mobileLookup) { private void doWaitForLazyElement(String path, MobileLookup mobileLookup) {
new FluentWait<>(getDriver()) new FluentWait<>(getDriver())
.withTimeout(Duration.ofSeconds(LAZY_ELEMENT_RENDER_TIMEOUT)) .withTimeout(Duration.ofSeconds(LAZY_ELEMENT_RENDER_TIMEOUT))
.pollingEvery(ofMillis(300)) .pollingEvery(ofMillis(300))
.ignoring(NoSuchElementException.class) .ignoring(NoSuchElementException.class)
.ignoring(StaleElementReferenceException.class) .ignoring(StaleElementReferenceException.class)
.withMessage("Element/s not found within " + LAZY_ELEMENT_RENDER_TIMEOUT .withMessage("Element/s not found within " + LAZY_ELEMENT_RENDER_TIMEOUT
+ " seconds timeout. If this error occurs, you might want to consider using explicit @AndroidWait or @IosWait annotation. " + path) + " seconds timeout. If this error occurs, you might want to consider using explicit @AndroidWait or @IosWait annotation. " + path)
.until(driver -> !driver.findElements(resolveMobileLookup(mobileLookup, path)).isEmpty()); .until(driver -> !driver.findElements(resolveMobileLookup(mobileLookup, path)).isEmpty());
} }
private Alert waitForAlert(int timeout) { private Alert waitForAlert(int timeout) {
return new FluentWait<>(getDriver()) return new FluentWait<>(getDriver())
.withTimeout(Duration.ofSeconds(timeout)) .withTimeout(Duration.ofSeconds(timeout))
.pollingEvery(Duration.ofMillis(300)) .pollingEvery(Duration.ofMillis(300))
.withMessage("Alert is not present after " + timeout + " seconds timeout.") .withMessage("Alert is not present after " + timeout + " seconds timeout.")
.until(driver -> alertIsPresent().apply(getDriver())); .until(driver -> alertIsPresent().apply(getDriver()));
} }
public void acceptAlert(int timeout) { public void acceptAlert(int timeout) {
waitForAlert(timeout).accept(); waitForAlert(timeout).accept();
} }
public void dismissAlert(int timeout) { public void dismissAlert(int timeout) {
waitForAlert(timeout).dismiss(); waitForAlert(timeout).dismiss();
} }
public String getText(String path, MobileLookup mobileLookup) { public String getText(String path, MobileLookup mobileLookup) {
return waitForLazyElement(path, mobileLookup).getText(); return waitForLazyElement(path, mobileLookup).getText();
} }
public void waitForElements(int timeoutSeconds, MobileLookup mobileLookup, Until until, String... elementsToCheck) { public void waitForElements(int timeoutSeconds, MobileLookup mobileLookup, Until until, String... elementsToCheck) {
WebDriverWait wait = new WebDriverWait(getDriver(), Duration.ofSeconds(timeoutSeconds)); WebDriverWait wait = new WebDriverWait(getDriver(), Duration.ofSeconds(timeoutSeconds));
Arrays.stream(elementsToCheck) Arrays.stream(elementsToCheck)
.forEach(path -> wait.until(resolveUntil(until).apply(resolveMobileLookup(mobileLookup, path)))); .forEach(path -> wait.until(resolveUntil(until).apply(resolveMobileLookup(mobileLookup, path))));
} }
public void scrollRight() { public void scrollRight() {
horizontalScroll(0.2, 0.8, 0.5); horizontalScroll(0.2, 0.8, 0.5);
} }
public void scrollLeft() { public void scrollLeft() {
horizontalScroll(0.8, 0.2, 0.5); horizontalScroll(0.8, 0.2, 0.5);
} }
public void swipeViewLeft() { public void swipeViewLeft() {
horizontalScroll(0.9, 0.02, 0.88); horizontalScroll(0.9, 0.02, 0.88);
} }
public void swipeViewRight() { public void swipeViewRight() {
horizontalScroll(0.02, 0.9, 0.88); horizontalScroll(0.02, 0.9, 0.88);
} }
private void horizontalScroll(double startPercentage, double endPercentage, double anchorPercentage) { private void horizontalScroll(double startPercentage, double endPercentage, double anchorPercentage) {
Dimension size = getDriver().manage().window().getSize(); Dimension size = getDriver().manage().window().getSize();
int anchor = (int) (size.height * anchorPercentage); int anchor = (int) (size.height * anchorPercentage);
int startPoint = (int) (size.width * startPercentage); int startPoint = (int) (size.width * startPercentage);
int endPoint = (int) (size.width * endPercentage); int endPoint = (int) (size.width * endPercentage);
doScroll(point(startPoint, anchor), point(endPoint, anchor)); doScroll(point(startPoint, anchor), point(endPoint, anchor));
} }
public void scrollDown() { public void scrollDown() {
verticalScroll(0.5, 0.2); verticalScroll(0.5, 0.2);
} }
public void scrollUp() { public void scrollUp() {
verticalScroll(0.2, 0.5); verticalScroll(0.2, 0.5);
} }
public void scrollLeftUntil(String path, MobileLookup mobileLookup) { public void scrollLeftUntil(String path, MobileLookup mobileLookup) {
doScrollUntil(path, mobileLookup, this::scrollLeft); doScrollUntil(path, mobileLookup, this::scrollLeft);
} }
public void scrollRightUntil(String path, MobileLookup mobileLookup) { public void scrollRightUntil(String path, MobileLookup mobileLookup) {
doScrollUntil(path, mobileLookup, this::scrollRight); doScrollUntil(path, mobileLookup, this::scrollRight);
} }
public void scrollDownUntil(String path, MobileLookup mobileLookup) { public void scrollDownUntil(String path, MobileLookup mobileLookup) {
doScrollUntil(path, mobileLookup, this::scrollDown); doScrollUntil(path, mobileLookup, this::scrollDown);
} }
public void scrollUpUntil(String path, MobileLookup mobileLookup) { public void scrollUpUntil(String path, MobileLookup mobileLookup) {
doScrollUntil(path, mobileLookup, this::scrollUp); doScrollUntil(path, mobileLookup, this::scrollUp);
} }
public void swipeFromToElement(String source, String target, MobileLookup mobileLookup) { public void swipeFromToElement(String source, String target, MobileLookup mobileLookup) {
// Point fromElementPosition = waitForLazyElement(source, mobileLookup).getCenter(); // Point fromElementPosition = waitForLazyElement(source, mobileLookup).getCenter();
// Point toElementPosition = getDriver().findElement(resolveMobileLookup(mobileLookup, target)).getCenter(); // Point toElementPosition = getDriver().findElement(resolveMobileLookup(mobileLookup, target)).getCenter();
// doScroll(point(fromElementPosition.x, fromElementPosition.y), point(toElementPosition.x, toElementPosition.y)); // doScroll(point(fromElementPosition.x, fromElementPosition.y), point(toElementPosition.x, toElementPosition.y));
// TODO: actually not supported // TODO: actually not supported
} }
private void doScrollUntil(String path, MobileLookup mobileLookup, Runnable scroll) { private void doScrollUntil(String path, MobileLookup mobileLookup, Runnable scroll) {
Stream.generate(() -> scroll) Stream.generate(() -> scroll)
.limit(10) .limit(10)
.peek(Runnable::run) .peek(Runnable::run)
.filter(r -> isNotEmpty(getDriver().findElements(resolveMobileLookup(mobileLookup, path)))) .filter(r -> isNotEmpty(getDriver().findElements(resolveMobileLookup(mobileLookup, path))))
.findFirst() .findFirst()
.orElseThrow(() -> .orElseThrow(() ->
new HarnessException(String.format("Element '%s' not present after 10 swipes", path))); new HarnessException(String.format("Element '%s' not present after 10 swipes", path)));
} }
private void verticalScroll(double startPercentage, double endPercentage) { private void verticalScroll(double startPercentage, double endPercentage) {
Dimension size = getDriver().manage().window().getSize(); Dimension size = getDriver().manage().window().getSize();
int anchor = (int) (size.width * 0.3); int anchor = (int) (size.width * 0.3);
int startPoint = (int) (size.height * startPercentage); int startPoint = (int) (size.height * startPercentage);
int endPoint = (int) (size.height * endPercentage); int endPoint = (int) (size.height * endPercentage);
doScroll(point(anchor, startPoint), point(anchor, endPoint)); doScroll(point(anchor, startPoint), point(anchor, endPoint));
} }
private void doScroll(PointOption from, PointOption to) { private void doScroll(PointOption from, PointOption to) {
// new TouchAction(getDriver()) // new TouchAction(getDriver())
// .press(from) // .press(from)
// .waitAction(waitOptions(ofMillis(1000))) // .waitAction(waitOptions(ofMillis(1000)))
// .moveTo(to) // .moveTo(to)
// .release().perform(); // .release().perform();
// TODO: acctualy not supported // TODO: acctualy not supported
} }
public void takeSnapshot(String snapshotFileName) { public void takeSnapshot(String snapshotFileName) {
String snapshotsPath = store.getConfig("appium.snapshots.path", "target/screenshots"); String snapshotsPath = store.getConfig("appium.snapshots.path", "target/screenshots");
File file = Paths.get(snapshotsPath, snapshotFileName).toFile(); File file = Paths.get(snapshotsPath, snapshotFileName).toFile();
try { try {
FileUtils.writeByteArrayToFile(file, getDriver().getScreenshotAs(OutputType.BYTES)); FileUtils.writeByteArrayToFile(file, getDriver().getScreenshotAs(OutputType.BYTES));
} catch (IOException e) { } catch (IOException e) {
throw new IllegalStateException("Unable to write " + snapshotFileName + " to " + snapshotsPath, e); throw new IllegalStateException("Unable to write " + snapshotFileName + " to " + snapshotsPath, e);
} }
} }
public void saveSource(String sourceFileName) { public void saveSource(String sourceFileName) {
String sourcesPath = store.getConfig("appium.view.sources.path", "target/sources"); String sourcesPath = store.getConfig("appium.view.sources.path", "target/sources");
File file = Paths.get(sourcesPath, sourceFileName).toFile(); File file = Paths.get(sourcesPath, sourceFileName).toFile();
try { try {
FileUtils.writeByteArrayToFile(file, getDriver().getPageSource().getBytes()); FileUtils.writeByteArrayToFile(file, getDriver().getPageSource().getBytes());
} catch (IOException e) { } catch (IOException e) {
throw new IllegalStateException("Unable to write " + sourceFileName + " to " + sourcesPath, e); throw new IllegalStateException("Unable to write " + sourceFileName + " to " + sourcesPath, e);
} }
} }
public void type(TextContainer input, String text, boolean clear, MobileLookup mobileLookup) { public void type(TextContainer input, String text, boolean clear, MobileLookup mobileLookup) {
if (clear) { if (clear) {
clearInput(input, mobileLookup); clearInput(input, mobileLookup);
} }
waitForLazyElement(input.getPath(), mobileLookup).sendKeys(text); waitForLazyElement(input.getPath(), mobileLookup).sendKeys(text);
} }
private void clearInput(TextContainer input, MobileLookup mobileLookup) { private void clearInput(TextContainer input, MobileLookup mobileLookup) {
waitForLazyElement(input.getPath(), mobileLookup).clear(); waitForLazyElement(input.getPath(), mobileLookup).clear();
} }
public WebElementsCheck elementsCheck() { public WebElementsCheck elementsCheck() {
return new WebElementsCheck(this); return new WebElementsCheck(this);
} }
private By resolveMobileLookup(MobileLookup mobileLookup, String path) { private By resolveMobileLookup(MobileLookup mobileLookup, String path) {
if (MobilePlatform.isAndroid(getStore())) { if (MobilePlatform.isAndroid(getStore())) {
path = mobileLookup.equals(MobileLookup.ANDROID_TEXT) ? "new UiSelector().textContains(\"" + path + "\")" : path; path = mobileLookup.equals(MobileLookup.ANDROID_TEXT) ? "new UiSelector().textContains(\"" + path + "\")" : path;
path = mobileLookup.equals(MobileLookup.ID) ? bundleId + ":id/" + path : path; path = mobileLookup.equals(MobileLookup.ID) ? bundleId + ":id/" + path : path;
} }
return resolveLookup(mobileLookup).apply(path); return resolveLookup(mobileLookup).apply(path);
} }
private Function<String, By> resolveLookup(MobileLookup mobileLookup) { private Function<String, By> resolveLookup(MobileLookup mobileLookup) {
switch (mobileLookup) { switch (mobileLookup) {
case XPATH: case XPATH:
return MobileBy::xpath; return MobileBy::xpath;
case ID: case ID:
return MobileBy::id; return MobileBy::id;
case IOS_NAME: case IOS_NAME:
return MobileBy::name; return MobileBy::name;
case ANDROID_TEXT: case ANDROID_TEXT:
return MobileBy::AndroidUIAutomator; return MobileBy::AndroidUIAutomator;
default: default:
return defaultLookup; return defaultLookup;
} }
} }
private Function<By, ExpectedCondition<?>> resolveUntil(Until until) { private Function<By, ExpectedCondition<?>> resolveUntil(Until until) {
switch (until) { switch (until) {
case VISIBLE: case VISIBLE:
return ExpectedConditions::visibilityOfElementLocated; return ExpectedConditions::visibilityOfElementLocated;
case PRESENT_IN_DOM: case PRESENT_IN_DOM:
return ExpectedConditions::presenceOfElementLocated; return ExpectedConditions::presenceOfElementLocated;
case GONE: case GONE:
return ExpectedConditions::invisibilityOfElementLocated; return ExpectedConditions::invisibilityOfElementLocated;
default: default:
return ExpectedConditions::presenceOfElementLocated; return ExpectedConditions::presenceOfElementLocated;
} }
} }
} }

View File

@ -1,15 +1,15 @@
package cz.moneta.test.harness.connectors.mobile; package cz.moneta.test.harness.connectors.mobile;
import cz.moneta.test.harness.endpoints.smartbanka.MobilePlatform; import cz.moneta.test.harness.endpoints.smartbanka.MobilePlatform;
import org.openqa.selenium.Platform; import org.openqa.selenium.Platform;
public class UnsupportedPlatformException extends AssertionError { public class UnsupportedPlatformException extends AssertionError {
public UnsupportedPlatformException(MobilePlatform platform) { public UnsupportedPlatformException(MobilePlatform platform) {
super("Method is supported only for " + platform + ", please check your mobile platform or use another method"); super("Method is supported only for " + platform + ", please check your mobile platform or use another method");
} }
public UnsupportedPlatformException(String message) { public UnsupportedPlatformException(String message) {
super(message); super(message);
} }
} }

View File

@ -1,28 +1,28 @@
package cz.moneta.test.harness.connectors.mobile; package cz.moneta.test.harness.connectors.mobile;
import cz.moneta.test.harness.support.mobile.MobileLookup; import cz.moneta.test.harness.support.mobile.MobileLookup;
import org.openqa.selenium.WebElement; import org.openqa.selenium.WebElement;
import java.util.List; import java.util.List;
public class WebElementsCheck { public class WebElementsCheck {
private final AppiumMobileConnector connector; private final AppiumMobileConnector connector;
public WebElementsCheck(AppiumMobileConnector connector) { public WebElementsCheck(AppiumMobileConnector connector) {
this.connector = connector; this.connector = connector;
} }
public void checkElementContent(String path, String content, MobileLookup mobileLookup) { public void checkElementContent(String path, String content, MobileLookup mobileLookup) {
List<WebElement> elements = connector.waitForLazyElements(path, mobileLookup); List<WebElement> elements = connector.waitForLazyElements(path, mobileLookup);
elements.stream() elements.stream()
.map(WebElement::getText) .map(WebElement::getText)
.filter(text -> text.contains(content)) .filter(text -> text.contains(content))
.findFirst() .findFirst()
.orElseThrow(() -> new AssertionError(String.format("Cannot find element that contains text %s", content))); .orElseThrow(() -> new AssertionError(String.format("Cannot find element that contains text %s", content)));
} }
public void checkElementPresent(String path, MobileLookup mobileLookup) { public void checkElementPresent(String path, MobileLookup mobileLookup) {
connector.waitForLazyElement(path, mobileLookup); connector.waitForLazyElement(path, mobileLookup);
} }
} }

View File

@ -1,123 +1,123 @@
package cz.moneta.test.harness.connectors.mobile.android; package cz.moneta.test.harness.connectors.mobile.android;
import cz.moneta.test.harness.connectors.mobile.AppiumMobileConnector; import cz.moneta.test.harness.connectors.mobile.AppiumMobileConnector;
import cz.moneta.test.harness.context.StoreAccessor; import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.exception.HarnessException; import cz.moneta.test.harness.exception.HarnessException;
import io.appium.java_client.android.AndroidDriver; import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.android.nativekey.AndroidKey; import io.appium.java_client.android.nativekey.AndroidKey;
import io.appium.java_client.android.nativekey.KeyEvent; import io.appium.java_client.android.nativekey.KeyEvent;
import io.appium.java_client.remote.AutomationName; import io.appium.java_client.remote.AutomationName;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.openqa.selenium.By; import org.openqa.selenium.By;
import org.openqa.selenium.Platform; import org.openqa.selenium.Platform;
import org.openqa.selenium.WebDriverException; import org.openqa.selenium.WebDriverException;
import org.openqa.selenium.remote.DesiredCapabilities; import org.openqa.selenium.remote.DesiredCapabilities;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.ConnectException; import java.net.ConnectException;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.Arrays; import java.util.Arrays;
import java.util.Base64; import java.util.Base64;
import java.util.Optional; import java.util.Optional;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Stream; import java.util.stream.Stream;
public class AndroidConnector extends AppiumMobileConnector<AndroidDriver> { public class AndroidConnector extends AppiumMobileConnector<AndroidDriver> {
private final AndroidDriver driver; private final AndroidDriver driver;
public AndroidConnector(Function<String, By> defaultLookup, StoreAccessor store, String host, String deviceName, String udid, String appPackage, String appBinaryPath) { public AndroidConnector(Function<String, By> defaultLookup, StoreAccessor store, String host, String deviceName, String udid, String appPackage, String appBinaryPath) {
super(defaultLookup, store, host, appBinaryPath); super(defaultLookup, store, host, appBinaryPath);
this.deviceName = deviceName; this.deviceName = deviceName;
this.udid = udid; this.udid = udid;
this.bundleId = appPackage; this.bundleId = appPackage;
this.driver = initDriver(); this.driver = initDriver();
} }
@Override @Override
protected AndroidDriver getDriver() { protected AndroidDriver getDriver() {
return driver; return driver;
} }
private AndroidDriver initDriver() { private AndroidDriver initDriver() {
return startDriver() return startDriver()
.map(this::startRecording) .map(this::startRecording)
.flatMap(d -> .flatMap(d ->
Stream.<Supplier<Optional<AndroidDriver>>>of( Stream.<Supplier<Optional<AndroidDriver>>>of(
() -> launchExistingApp(d)) () -> launchExistingApp(d))
.map(Supplier::get) .map(Supplier::get)
.filter(Optional::isPresent) .filter(Optional::isPresent)
.map(Optional::get) .map(Optional::get)
.findFirst()) .findFirst())
.orElseThrow(() -> new HarnessException("App was not started")); .orElseThrow(() -> new HarnessException("App was not started"));
} }
private Optional<AndroidDriver> startDriver() { private Optional<AndroidDriver> startDriver() {
return Optional.ofNullable(host) return Optional.ofNullable(host)
.map(cs -> { .map(cs -> {
DesiredCapabilities capabilities = new DesiredCapabilities(); DesiredCapabilities capabilities = new DesiredCapabilities();
mobileLanguage(capabilities, "US", "CZ"); mobileLanguage(capabilities, "US", "CZ");
setDesiredCapabilities(Platform.ANDROID, deviceName, udid, getUiAutomator(), capabilities); setDesiredCapabilities(Platform.ANDROID, deviceName, udid, getUiAutomator(), capabilities);
try { try {
return new AndroidDriver(new URL(cs), capabilities); return new AndroidDriver(new URL(cs), capabilities);
} catch (MalformedURLException e) { } catch (MalformedURLException e) {
throw new HarnessException(String.format("Appium connection to ANDROID device is invalid: %s", cs), e); throw new HarnessException(String.format("Appium connection to ANDROID device is invalid: %s", cs), e);
} catch (WebDriverException e) { } catch (WebDriverException e) {
if (e.getCause() instanceof ConnectException) { if (e.getCause() instanceof ConnectException) {
throw new HarnessException(String.format("Cannot connect to Appium on %s. Is your Appium Desktop running?", cs), e); throw new HarnessException(String.format("Cannot connect to Appium on %s. Is your Appium Desktop running?", cs), e);
} else { } else {
throw new HarnessException("Android driver didn't start, please check the connect to server", e); throw new HarnessException("Android driver didn't start, please check the connect to server", e);
} }
} }
}); });
} }
@Override @Override
public void captureVideo(String videoFileName) { public void captureVideo(String videoFileName) {
if (!checkVideoRecord()) { if (!checkVideoRecord()) {
return; return;
} }
String videoPath = store.getConfig("appium.video.path", "target/videos"); String videoPath = store.getConfig("appium.video.path", "target/videos");
File file = Paths.get(videoPath, videoFileName).toFile(); File file = Paths.get(videoPath, videoFileName).toFile();
try { try {
FileUtils.writeByteArrayToFile(file, Base64.getDecoder().decode(driver.stopRecordingScreen())); FileUtils.writeByteArrayToFile(file, Base64.getDecoder().decode(driver.stopRecordingScreen()));
} catch (IOException e) { } catch (IOException e) {
throw new IllegalStateException("Unable to write " + videoFileName + " to " + videoPath, e); throw new IllegalStateException("Unable to write " + videoFileName + " to " + videoPath, e);
} }
} }
@Override @Override
public void resetApp() { public void resetApp() {
driver.resetApp(); driver.resetApp();
driver.activateApp(bundleId); driver.activateApp(bundleId);
} }
@Override @Override
public void close() { public void close() {
driver.quit(); driver.quit();
} }
public void hideAndroidKeyboard() { public void hideAndroidKeyboard() {
driver.hideKeyboard(); driver.hideKeyboard();
} }
private AndroidDriver startRecording(AndroidDriver driver) { private AndroidDriver startRecording(AndroidDriver driver) {
if (checkVideoRecord()) { if (checkVideoRecord()) {
driver.startRecordingScreen(); driver.startRecordingScreen();
} }
return driver; return driver;
} }
private String getUiAutomator() { private String getUiAutomator() {
boolean uiAutomator = Boolean.parseBoolean(store.getConfig("appium.android.uiAutomator", "false")); boolean uiAutomator = Boolean.parseBoolean(store.getConfig("appium.android.uiAutomator", "false"));
return uiAutomator ? AutomationName.ANDROID_UIAUTOMATOR2 : null; return uiAutomator ? AutomationName.ANDROID_UIAUTOMATOR2 : null;
} }
public void pressAndroidKeys(AndroidKey... keys) { public void pressAndroidKeys(AndroidKey... keys) {
Arrays.stream(keys) Arrays.stream(keys)
.forEach(key -> getDriver().pressKey(new KeyEvent(key))); .forEach(key -> getDriver().pressKey(new KeyEvent(key)));
} }
} }

View File

@ -1,19 +1,19 @@
package cz.moneta.test.harness.connectors.mobile.ios; package cz.moneta.test.harness.connectors.mobile.ios;
public enum HideIosKeyboardButton { public enum HideIosKeyboardButton {
DONE("Done"), DONE("Done"),
GO("Go"), GO("Go"),
ENTER("Enter"), ENTER("Enter"),
RETURN("Return"), RETURN("Return"),
NONE(""); NONE("");
private final String value; private final String value;
HideIosKeyboardButton(String value) { HideIosKeyboardButton(String value) {
this.value = value; this.value = value;
} }
public String getValue() { public String getValue() {
return value; return value;
} }
} }

View File

@ -1,134 +1,134 @@
package cz.moneta.test.harness.connectors.mobile.ios; package cz.moneta.test.harness.connectors.mobile.ios;
import cz.moneta.test.harness.connectors.mobile.AppiumMobileConnector; import cz.moneta.test.harness.connectors.mobile.AppiumMobileConnector;
import cz.moneta.test.harness.context.StoreAccessor; import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.exception.HarnessException; import cz.moneta.test.harness.exception.HarnessException;
import cz.moneta.test.harness.support.mobile.MobileLookup; import cz.moneta.test.harness.support.mobile.MobileLookup;
import io.appium.java_client.ios.IOSDriver; import io.appium.java_client.ios.IOSDriver;
import io.appium.java_client.remote.AutomationName; import io.appium.java_client.remote.AutomationName;
import io.appium.java_client.remote.IOSMobileCapabilityType; import io.appium.java_client.remote.IOSMobileCapabilityType;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.openqa.selenium.*; import org.openqa.selenium.*;
import org.openqa.selenium.remote.DesiredCapabilities; import org.openqa.selenium.remote.DesiredCapabilities;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.ConnectException; import java.net.ConnectException;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.Base64; import java.util.Base64;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Stream; import java.util.stream.Stream;
public class IosConnector extends AppiumMobileConnector<IOSDriver> { public class IosConnector extends AppiumMobileConnector<IOSDriver> {
private IOSDriver iosDriver; private IOSDriver iosDriver;
public IosConnector(Function<String, By> defaultLookup, StoreAccessor store, String host, String deviceName, String udid, String bundleId, String appBinaryPath) { public IosConnector(Function<String, By> defaultLookup, StoreAccessor store, String host, String deviceName, String udid, String bundleId, String appBinaryPath) {
super(defaultLookup, store, host, appBinaryPath); super(defaultLookup, store, host, appBinaryPath);
this.udid = udid; this.udid = udid;
this.bundleId = bundleId; this.bundleId = bundleId;
this.deviceName = deviceName; this.deviceName = deviceName;
this.iosDriver = initDriver(); this.iosDriver = initDriver();
} }
@Override @Override
protected IOSDriver getDriver() { protected IOSDriver getDriver() {
return iosDriver; return iosDriver;
} }
private IOSDriver initDriver() { private IOSDriver initDriver() {
return startDriver() return startDriver()
.map(this::startRecording) .map(this::startRecording)
.flatMap(d -> Stream.<Supplier<Optional<IOSDriver>>>of( .flatMap(d -> Stream.<Supplier<Optional<IOSDriver>>>of(
() -> launchExistingApp(d)) () -> launchExistingApp(d))
.map(Supplier::get) .map(Supplier::get)
.filter(Optional::isPresent) .filter(Optional::isPresent)
.map(Optional::get) .map(Optional::get)
.findFirst()) .findFirst())
.orElseThrow(() -> new HarnessException("App was not started")); .orElseThrow(() -> new HarnessException("App was not started"));
} }
private Optional<IOSDriver> startDriver() { private Optional<IOSDriver> startDriver() {
return Optional.ofNullable(host) return Optional.ofNullable(host)
.map(cs -> { .map(cs -> {
DesiredCapabilities capabilities = new DesiredCapabilities(); DesiredCapabilities capabilities = new DesiredCapabilities();
setDesiredCapabilities(Platform.IOS, deviceName, udid, AutomationName.IOS_XCUI_TEST, capabilities); setDesiredCapabilities(Platform.IOS, deviceName, udid, AutomationName.IOS_XCUI_TEST, capabilities);
mobileLanguage(capabilities, "en_US", "cs_CZ"); mobileLanguage(capabilities, "en_US", "cs_CZ");
capabilities.setCapability(IOSMobileCapabilityType.BUNDLE_ID, this.bundleId); capabilities.setCapability(IOSMobileCapabilityType.BUNDLE_ID, this.bundleId);
try { try {
return new IOSDriver(new URL(cs), capabilities); return new IOSDriver(new URL(cs), capabilities);
} catch (MalformedURLException e) { } catch (MalformedURLException e) {
throw new HarnessException(String.format("Appium connection to IOS device is invalid: %s", cs), e); throw new HarnessException(String.format("Appium connection to IOS device is invalid: %s", cs), e);
} catch (WebDriverException e) { } catch (WebDriverException e) {
if (e.getCause() instanceof ConnectException) { if (e.getCause() instanceof ConnectException) {
throw new HarnessException(String.format("Cannot connect to Appium on %s. Is your Appium Desktop running?", cs), e); throw new HarnessException(String.format("Cannot connect to Appium on %s. Is your Appium Desktop running?", cs), e);
} else { } else {
throw new HarnessException("Ios driver didn't start, please check the connect to server", e); throw new HarnessException("Ios driver didn't start, please check the connect to server", e);
} }
} }
}); });
} }
@Override @Override
public void captureVideo(String videoFileName) { public void captureVideo(String videoFileName) {
if (!checkVideoRecord()) { if (!checkVideoRecord()) {
return; return;
} }
String videoPath = store.getConfig("appium.video.path", "target/videos"); String videoPath = store.getConfig("appium.video.path", "target/videos");
File file = Paths.get(videoPath, videoFileName).toFile(); File file = Paths.get(videoPath, videoFileName).toFile();
try { try {
FileUtils.writeByteArrayToFile(file, Base64.getDecoder().decode(iosDriver.stopRecordingScreen())); FileUtils.writeByteArrayToFile(file, Base64.getDecoder().decode(iosDriver.stopRecordingScreen()));
} catch (IOException e) { } catch (IOException e) {
throw new IllegalStateException("Unable to write " + videoFileName + " to " + videoPath, e); throw new IllegalStateException("Unable to write " + videoFileName + " to " + videoPath, e);
} }
} }
private IOSDriver startRecording(IOSDriver driver) { private IOSDriver startRecording(IOSDriver driver) {
if (checkVideoRecord()) { if (checkVideoRecord()) {
driver.startRecordingScreen(); driver.startRecordingScreen();
} }
return driver; return driver;
} }
@Override @Override
public void resetApp() { public void resetApp() {
iosDriver.removeApp(bundleId); iosDriver.removeApp(bundleId);
iosDriver.installApp(appBinaryPath); iosDriver.installApp(appBinaryPath);
iosDriver.activateApp(bundleId); iosDriver.activateApp(bundleId);
} }
@Override @Override
public void close() { public void close() {
iosDriver.closeApp(); iosDriver.closeApp();
} }
public void hideIosKeyboard(HideIosKeyboardButton hideIosKeyboardButton) { public void hideIosKeyboard(HideIosKeyboardButton hideIosKeyboardButton) {
click(hideIosKeyboardButton.getValue(), MobileLookup.ID); click(hideIosKeyboardButton.getValue(), MobileLookup.ID);
} }
public void executeScrollScript(String name, String direction) { public void executeScrollScript(String name, String direction) {
Map<String, Object> args = new HashMap<>(); Map<String, Object> args = new HashMap<>();
args.put("direction", direction); args.put("direction", direction);
args.put("name", name); args.put("name", name);
Optional.of(getDriver()) Optional.of(getDriver())
.filter(JavascriptExecutor.class::isInstance) .filter(JavascriptExecutor.class::isInstance)
.map(JavascriptExecutor.class::cast) .map(JavascriptExecutor.class::cast)
.ifPresent(script -> { .ifPresent(script -> {
try { try {
script.executeScript("mobile: scroll", args); script.executeScript("mobile: scroll", args);
} catch (NoSuchElementException e) { } catch (NoSuchElementException e) {
throw new HarnessException("Element " + name + " is not present"); throw new HarnessException("Element " + name + " is not present");
} }
}); });
} }
//TODO add in the future //TODO add in the future
public void pressIosKeys() { public void pressIosKeys() {
} }
} }

View File

@ -1,62 +1,62 @@
package cz.moneta.test.harness.connectors.rest; package cz.moneta.test.harness.connectors.rest;
import cz.moneta.test.harness.connectors.Connector; import cz.moneta.test.harness.connectors.Connector;
import org.apache.logging.log4j.jul.LogManager; import org.apache.logging.log4j.jul.LogManager;
import org.glassfish.jersey.SslConfigurator; import org.glassfish.jersey.SslConfigurator;
import org.glassfish.jersey.client.ClientConfig; import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.client.HttpUrlConnectorProvider; import org.glassfish.jersey.client.HttpUrlConnectorProvider;
import org.glassfish.jersey.jackson.JacksonFeature; import org.glassfish.jersey.jackson.JacksonFeature;
import org.glassfish.jersey.jackson.internal.jackson.jaxrs.json.JacksonJsonProvider; import org.glassfish.jersey.jackson.internal.jackson.jaxrs.json.JacksonJsonProvider;
import org.glassfish.jersey.logging.LoggingFeature; import org.glassfish.jersey.logging.LoggingFeature;
import org.glassfish.jersey.media.multipart.MultiPartFeature; import org.glassfish.jersey.media.multipart.MultiPartFeature;
import jakarta.ws.rs.client.Client; import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.client.ClientBuilder; import jakarta.ws.rs.client.ClientBuilder;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.security.KeyStore; import java.security.KeyStore;
import java.security.KeyStoreException; import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.logging.Level; import java.util.logging.Level;
import static cz.moneta.test.harness.constants.HarnessConfigConstants.DEFAULT_REST_READ_TIMEOUT; import static cz.moneta.test.harness.constants.HarnessConfigConstants.DEFAULT_REST_READ_TIMEOUT;
public abstract class BaseRestConnector implements Connector { public abstract class BaseRestConnector implements Connector {
protected Client createHttpClient(String loggerName) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException { protected Client createHttpClient(String loggerName) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
return createHttpClient(loggerName, DEFAULT_REST_READ_TIMEOUT); return createHttpClient(loggerName, DEFAULT_REST_READ_TIMEOUT);
} }
protected Client createHttpClient(String loggerName, long readTimeout) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException { protected Client createHttpClient(String loggerName, long readTimeout) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
KeyStore trustedStore = KeyStore.getInstance(KeyStore.getDefaultType()); KeyStore trustedStore = KeyStore.getInstance(KeyStore.getDefaultType());
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
trustedStore.load(getInputStream("keystores/mb_root"), "changeit".toCharArray()); trustedStore.load(getInputStream("keystores/mb_root"), "changeit".toCharArray());
keyStore.load(getInputStream("keystores/api_gw_client.p12"), "changeit".toCharArray()); keyStore.load(getInputStream("keystores/api_gw_client.p12"), "changeit".toCharArray());
ClientConfig config = new ClientConfig() ClientConfig config = new ClientConfig()
.register(JacksonFeature.class) .register(JacksonFeature.class)
.register(JacksonJsonProvider.class) .register(JacksonJsonProvider.class)
.register(MultiPartFeature.class) .register(MultiPartFeature.class)
.register(new LoggingFeature(LogManager.getLogManager().getLogger(loggerName), .register(new LoggingFeature(LogManager.getLogManager().getLogger(loggerName),
Level.FINE, LoggingFeature.Verbosity.PAYLOAD_ANY, 1024 * 1024)) Level.FINE, LoggingFeature.Verbosity.PAYLOAD_ANY, 1024 * 1024))
.property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true); // TODO: test it, this line may affect all REST calls .property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true); // TODO: test it, this line may affect all REST calls
return ClientBuilder.newBuilder() return ClientBuilder.newBuilder()
.withConfig(config) .withConfig(config)
.connectTimeout(10, TimeUnit.SECONDS) .connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(readTimeout, TimeUnit.SECONDS) .readTimeout(readTimeout, TimeUnit.SECONDS)
.sslContext(SslConfigurator.newInstance() .sslContext(SslConfigurator.newInstance()
.trustStore(trustedStore) .trustStore(trustedStore)
.keyStore(keyStore) .keyStore(keyStore)
.keyStorePassword("changeit".toCharArray()) .keyStorePassword("changeit".toCharArray())
.createSSLContext()) .createSSLContext())
.build(); .build();
} }
public InputStream getInputStream(String name) { public InputStream getInputStream(String name) {
return this.getClass().getClassLoader().getResourceAsStream(name); return this.getClass().getClassLoader().getResourceAsStream(name);
} }
} }

View File

@ -1,38 +1,38 @@
package cz.moneta.test.harness.connectors.rest; package cz.moneta.test.harness.connectors.rest;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import jakarta.ws.rs.InternalServerErrorException; import jakarta.ws.rs.InternalServerErrorException;
import jakarta.ws.rs.client.Entity; import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response;
import java.util.Map; import java.util.Map;
/** /**
* Rest connector returning extendable object. Now the response contains body and response headers. * Rest connector returning extendable object. Now the response contains body and response headers.
*/ */
public class ExtendedRestConnector extends SimpleRestConnector implements RestConnector { public class ExtendedRestConnector extends SimpleRestConnector implements RestConnector {
public ExtendedRestConnector(String endpointUrl, String loggerName) { public ExtendedRestConnector(String endpointUrl, String loggerName) {
super(endpointUrl, loggerName); super(endpointUrl, loggerName);
} }
public ExtendedRestConnector(String endpointUrl, String loggerName, long readTimeout) { public ExtendedRestConnector(String endpointUrl, String loggerName, long readTimeout) {
super(endpointUrl, loggerName, readTimeout); super(endpointUrl, loggerName, readTimeout);
} }
@Override @Override
public <T> Pair<Integer, ExtendedRestResponse<T>> postExtended(String path, Entity<?> request, public <T> Pair<Integer, ExtendedRestResponse<T>> postExtended(String path, Entity<?> request,
Class<T> responseType, Class<T> responseType,
Map<String, Object> headers) { Map<String, Object> headers) {
try { try {
Response response = doPost(path, request, headers); Response response = doPost(path, request, headers);
return Pair.of(response.getStatus(), new ExtendedRestResponse<>(response.readEntity(responseType), return Pair.of(response.getStatus(), new ExtendedRestResponse<>(response.readEntity(responseType),
response.getHeaders())); response.getHeaders()));
} catch (InternalServerErrorException serverError) { } catch (InternalServerErrorException serverError) {
return throwWithResponseText(serverError); return throwWithResponseText(serverError);
} }
} }
} }

View File

@ -1,25 +1,25 @@
package cz.moneta.test.harness.connectors.rest; package cz.moneta.test.harness.connectors.rest;
import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.core.MultivaluedMap;
/** /**
* Extendable response object from {@link ExtendedRestConnector}. * Extendable response object from {@link ExtendedRestConnector}.
*/ */
public class ExtendedRestResponse<T> { public class ExtendedRestResponse<T> {
private T responseBody; private T responseBody;
private MultivaluedMap<String, Object> responseHeaders; private MultivaluedMap<String, Object> responseHeaders;
public ExtendedRestResponse(T responseBody, MultivaluedMap<String, Object> responseHeaders) { public ExtendedRestResponse(T responseBody, MultivaluedMap<String, Object> responseHeaders) {
this.responseBody = responseBody; this.responseBody = responseBody;
this.responseHeaders = responseHeaders; this.responseHeaders = responseHeaders;
} }
public T getResponseBody() { public T getResponseBody() {
return responseBody; return responseBody;
} }
public MultivaluedMap<String, Object> getResponseHeaders() { public MultivaluedMap<String, Object> getResponseHeaders() {
return responseHeaders; return responseHeaders;
} }
} }

View File

@ -1,40 +1,40 @@
package cz.moneta.test.harness.connectors.rest; package cz.moneta.test.harness.connectors.rest;
import java.util.Map; import java.util.Map;
public class RemoteRestCallRequest { public class RemoteRestCallRequest {
private String path; private String path;
private Object request; private Object request;
private Map<String, Object> getProperties; private Map<String, Object> getProperties;
private Map<String, Object> headers; private Map<String, Object> headers;
public RemoteRestCallRequest() {} public RemoteRestCallRequest() {}
public RemoteRestCallRequest(String path, Map<String, Object> getProperties, Map<String, Object> headers) { public RemoteRestCallRequest(String path, Map<String, Object> getProperties, Map<String, Object> headers) {
this.path = path; this.path = path;
this.getProperties = getProperties; this.getProperties = getProperties;
this.headers = headers; this.headers = headers;
} }
public RemoteRestCallRequest(String path, Object request, Map<String, Object> headers) { public RemoteRestCallRequest(String path, Object request, Map<String, Object> headers) {
this.path = path; this.path = path;
this.request = request; this.request = request;
this.headers = headers; this.headers = headers;
} }
public String getPath() { public String getPath() {
return path; return path;
} }
public Object getRequest() { public Object getRequest() {
return request; return request;
} }
public Map<String, Object> getGetProperties() { public Map<String, Object> getGetProperties() {
return getProperties; return getProperties;
} }
public Map<String, Object> getHeaders() { public Map<String, Object> getHeaders() {
return headers; return headers;
} }
} }

View File

@ -1,12 +1,12 @@
package cz.moneta.test.harness.connectors.rest; package cz.moneta.test.harness.connectors.rest;
/** /**
* Response handler which enables response verification and post-processing * Response handler which enables response verification and post-processing
* *
* @param <I> original request invocation (i.e. the request call) * @param <I> original request invocation (i.e. the request call)
* @param <RESP> the original response against which the handling is performed * @param <RESP> the original response against which the handling is performed
* @see cz.moneta.test.harness.connectors.wso2.TokenRenewalResponseHandler * @see cz.moneta.test.harness.connectors.wso2.TokenRenewalResponseHandler
*/ */
public interface ResponseHandler<I, RESP> { public interface ResponseHandler<I, RESP> {
RESP handle(I invocation, RESP response); RESP handle(I invocation, RESP response);
} }

View File

@ -1,40 +1,40 @@
package cz.moneta.test.harness.connectors.rest; package cz.moneta.test.harness.connectors.rest;
import cz.moneta.test.harness.performance.PerformanceAware; import cz.moneta.test.harness.performance.PerformanceAware;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import jakarta.ws.rs.client.Entity; import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.client.Invocation; import jakarta.ws.rs.client.Invocation;
import jakarta.ws.rs.core.GenericType; import jakarta.ws.rs.core.GenericType;
import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.Map; import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
public interface RestConnector extends PerformanceAware { public interface RestConnector extends PerformanceAware {
<T> Pair<Integer, T> get(String path, Map<String, Object> properties, GenericType<T> responseType, Map<String, Object> headers); <T> Pair<Integer, T> get(String path, Map<String, Object> properties, GenericType<T> responseType, Map<String, Object> headers);
<T> Pair<Integer, T> post(String path, Entity<?> request, GenericType<T> responseType, Map<String, Object> headers); <T> Pair<Integer, T> post(String path, Entity<?> request, GenericType<T> responseType, Map<String, Object> headers);
<T> Pair<Integer, T> delete(String path, Map<String, Object> properties, GenericType<T> responseType, Map<String, Object> headers); <T> Pair<Integer, T> delete(String path, Map<String, Object> properties, GenericType<T> responseType, Map<String, Object> headers);
<T> Pair<Integer, T> patch(String path, Entity<?> request, GenericType<T> repsonseType, Map<String, Object> headers); <T> Pair<Integer, T> patch(String path, Entity<?> request, GenericType<T> repsonseType, Map<String, Object> headers);
RestConnector registerResponseHandler( RestConnector registerResponseHandler(
ResponseHandler<Pair<Invocation.Builder, Function<Invocation.Builder, Response>>, Response> responseHandler); ResponseHandler<Pair<Invocation.Builder, Function<Invocation.Builder, Response>>, Response> responseHandler);
/** /**
* Return response body with response headers. * Return response body with response headers.
*/ */
default <T> Pair<Integer, ExtendedRestResponse<T>> postExtended( default <T> Pair<Integer, ExtendedRestResponse<T>> postExtended(
String path, Entity<?> request, String path, Entity<?> request,
Class<T> responseType, Map<String, Class<T> responseType, Map<String,
Object> headers) { Object> headers) {
throw new IllegalStateException("Not implemented"); throw new IllegalStateException("Not implemented");
} }
@Override @Override
default String getPerformanceReportKey(Object proxy, Method method, Object[] args) { default String getPerformanceReportKey(Object proxy, Method method, Object[] args) {
return (String) args[0]; return (String) args[0];
} }
} }

View File

@ -1,155 +1,155 @@
package cz.moneta.test.harness.connectors.rest; package cz.moneta.test.harness.connectors.rest;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import jakarta.ws.rs.InternalServerErrorException; import jakarta.ws.rs.InternalServerErrorException;
import jakarta.ws.rs.ProcessingException; import jakarta.ws.rs.ProcessingException;
import jakarta.ws.rs.client.Client; import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.client.Entity; import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.client.Invocation; import jakarta.ws.rs.client.Invocation;
import jakarta.ws.rs.client.WebTarget; import jakarta.ws.rs.client.WebTarget;
import jakarta.ws.rs.core.CacheControl; import jakarta.ws.rs.core.CacheControl;
import jakarta.ws.rs.core.GenericType; import jakarta.ws.rs.core.GenericType;
import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Function; import java.util.function.Function;
import static cz.moneta.test.harness.constants.HarnessConfigConstants.DEFAULT_REST_READ_TIMEOUT; import static cz.moneta.test.harness.constants.HarnessConfigConstants.DEFAULT_REST_READ_TIMEOUT;
import static cz.moneta.test.harness.support.rest.RestUtils.toMultiMap; import static cz.moneta.test.harness.support.rest.RestUtils.toMultiMap;
public class SimpleRestConnector extends BaseRestConnector implements RestConnector { public class SimpleRestConnector extends BaseRestConnector implements RestConnector {
protected WebTarget target; protected WebTarget target;
protected List<ResponseHandler<Pair<Invocation.Builder, Function<Invocation.Builder, Response>>, Response>> responseHandlers = new ArrayList<>(); protected List<ResponseHandler<Pair<Invocation.Builder, Function<Invocation.Builder, Response>>, Response>> responseHandlers = new ArrayList<>();
public SimpleRestConnector(String endpointUrl, String loggerName) { public SimpleRestConnector(String endpointUrl, String loggerName) {
this(endpointUrl, loggerName, DEFAULT_REST_READ_TIMEOUT); this(endpointUrl, loggerName, DEFAULT_REST_READ_TIMEOUT);
} }
public SimpleRestConnector(String endpointUrl, String loggerName, long readTimeout) { public SimpleRestConnector(String endpointUrl, String loggerName, long readTimeout) {
try { try {
Client client = createHttpClient(loggerName, readTimeout); Client client = createHttpClient(loggerName, readTimeout);
target = client.target(endpointUrl); target = client.target(endpointUrl);
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException("REST connector is not configured properly", e); throw new RuntimeException("REST connector is not configured properly", e);
} }
} }
@Override @Override
public <T> Pair<Integer, T> get(String path, Map<String, Object> properties, GenericType<T> responseType, Map<String, Object> headers) { public <T> Pair<Integer, T> get(String path, Map<String, Object> properties, GenericType<T> responseType, Map<String, Object> headers) {
try { try {
Invocation.Builder invocation = properties.entrySet().stream() Invocation.Builder invocation = properties.entrySet().stream()
.reduce(target, (t, e) -> t.queryParam(e.getKey(), e.getValue()), (t1, t2) -> t1) .reduce(target, (t, e) -> t.queryParam(e.getKey(), e.getValue()), (t1, t2) -> t1)
.path(path) .path(path)
.request() .request()
.headers(toMultiMap(headers)) .headers(toMultiMap(headers))
.cacheControl(CacheControl.valueOf("no-cache")); .cacheControl(CacheControl.valueOf("no-cache"));
Response response = responseHandlers.stream() Response response = responseHandlers.stream()
.reduce( .reduce(
invocation.get(), invocation.get(),
(resp, h) -> h.handle(Pair.of(invocation, Invocation.Builder::get), resp), (resp, h) -> h.handle(Pair.of(invocation, Invocation.Builder::get), resp),
(h1, h2) -> h1); (h1, h2) -> h1);
return Pair.of(response.getStatus(), response.readEntity(responseType)); return Pair.of(response.getStatus(), response.readEntity(responseType));
} catch (InternalServerErrorException serverError) { } catch (InternalServerErrorException serverError) {
return throwWithResponseText(serverError); return throwWithResponseText(serverError);
} catch (ProcessingException e) { } catch (ProcessingException e) {
throw new RuntimeException("Error during HTTP connection " + e.getMessage(), e); throw new RuntimeException("Error during HTTP connection " + e.getMessage(), e);
} }
} }
@Override @Override
public <T> Pair<Integer, T> post(String path, Entity<?> request, GenericType<T> responseType, Map<String, Object> headers) { public <T> Pair<Integer, T> post(String path, Entity<?> request, GenericType<T> responseType, Map<String, Object> headers) {
try { try {
Response response = doPost(path, request, headers); Response response = doPost(path, request, headers);
return Pair.of(response.getStatus(), response.readEntity(responseType)); return Pair.of(response.getStatus(), response.readEntity(responseType));
} catch (InternalServerErrorException serverError) { } catch (InternalServerErrorException serverError) {
return throwWithResponseText(serverError); return throwWithResponseText(serverError);
} catch (ProcessingException e) { } catch (ProcessingException e) {
throw new RuntimeException("Error during HTTP connection " + e.getMessage(), e); throw new RuntimeException("Error during HTTP connection " + e.getMessage(), e);
} }
} }
protected Response doPost(String path, Entity<?> request, Map<String, Object> headers) { protected Response doPost(String path, Entity<?> request, Map<String, Object> headers) {
Invocation.Builder invocation = target.path(path) Invocation.Builder invocation = target.path(path)
.request() .request()
.headers(toMultiMap(headers)) .headers(toMultiMap(headers))
.cacheControl(CacheControl.valueOf("no-cache")); .cacheControl(CacheControl.valueOf("no-cache"));
return responseHandlers.stream() return responseHandlers.stream()
.reduce( .reduce(
invocation.post(request), invocation.post(request),
(resp, h) -> h.handle(Pair.of(invocation, i -> i.post(request)), resp), (resp, h) -> h.handle(Pair.of(invocation, i -> i.post(request)), resp),
(h1, h2) -> h1); (h1, h2) -> h1);
} }
@Override @Override
public <T> Pair<Integer, T> delete(String path, Map<String, Object> properties, GenericType<T> responseType, Map<String, Object> headers) { public <T> Pair<Integer, T> delete(String path, Map<String, Object> properties, GenericType<T> responseType, Map<String, Object> headers) {
try { try {
Invocation.Builder invocation = properties.entrySet().stream() Invocation.Builder invocation = properties.entrySet().stream()
.reduce(target, (t, e) -> t.queryParam(e.getKey(), e.getValue()), (t1, t2) -> t1) .reduce(target, (t, e) -> t.queryParam(e.getKey(), e.getValue()), (t1, t2) -> t1)
.path(path) .path(path)
.request() .request()
.headers(toMultiMap(headers)) .headers(toMultiMap(headers))
.cacheControl(CacheControl.valueOf("no-cache")); .cacheControl(CacheControl.valueOf("no-cache"));
Response response = responseHandlers.stream() Response response = responseHandlers.stream()
.reduce( .reduce(
invocation.delete(), invocation.delete(),
(resp, h) -> h.handle(Pair.of(invocation, Invocation.Builder::delete), resp), (resp, h) -> h.handle(Pair.of(invocation, Invocation.Builder::delete), resp),
(h1, h2) -> h1); (h1, h2) -> h1);
return Pair.of(response.getStatus(), response.readEntity(responseType)); return Pair.of(response.getStatus(), response.readEntity(responseType));
} catch (InternalServerErrorException serverError) { } catch (InternalServerErrorException serverError) {
return throwWithResponseText(serverError); return throwWithResponseText(serverError);
} catch (ProcessingException e) { } catch (ProcessingException e) {
throw new RuntimeException("Error during HTTP connection " + e.getMessage(), e); throw new RuntimeException("Error during HTTP connection " + e.getMessage(), e);
} }
} }
@Override @Override
public <T> Pair<Integer, T> patch(String path, Entity<?> request, GenericType<T> responseType, Map<String, Object> headers) { public <T> Pair<Integer, T> patch(String path, Entity<?> request, GenericType<T> responseType, Map<String, Object> headers) {
try { try {
Invocation.Builder invocation = target.path(path) Invocation.Builder invocation = target.path(path)
.request() .request()
.headers(toMultiMap(headers)) .headers(toMultiMap(headers))
.cacheControl(CacheControl.valueOf("no-cache")); .cacheControl(CacheControl.valueOf("no-cache"));
Response response = responseHandlers.stream() Response response = responseHandlers.stream()
.reduce( .reduce(
invocation.method("PATCH", request), invocation.method("PATCH", request),
(resp, h) -> h.handle(Pair.of(invocation, i -> i.method("PATCH", request)), resp), (resp, h) -> h.handle(Pair.of(invocation, i -> i.method("PATCH", request)), resp),
(h1, h2) -> h1); (h1, h2) -> h1);
return Pair.of(response.getStatus(), response.readEntity(responseType)); return Pair.of(response.getStatus(), response.readEntity(responseType));
} catch (InternalServerErrorException serverError) { } catch (InternalServerErrorException serverError) {
return throwWithResponseText(serverError); return throwWithResponseText(serverError);
} catch (ProcessingException e) { } catch (ProcessingException e) {
throw new RuntimeException("Error during HTTP connection " + e.getMessage(), e); throw new RuntimeException("Error during HTTP connection " + e.getMessage(), e);
} }
} }
@Override @Override
public RestConnector registerResponseHandler(ResponseHandler<Pair<Invocation.Builder, public RestConnector registerResponseHandler(ResponseHandler<Pair<Invocation.Builder,
Function<Invocation.Builder, Response>>, Response> responseHandler) { Function<Invocation.Builder, Response>>, Response> responseHandler) {
responseHandlers.add(responseHandler); responseHandlers.add(responseHandler);
return this; return this;
} }
protected <T> T throwWithResponseText(InternalServerErrorException serverError) { protected <T> T throwWithResponseText(InternalServerErrorException serverError) {
String response = "N/A"; String response = "N/A";
try { try {
response = IOUtils.toString((InputStream) serverError.getResponse().getEntity(), Charset.defaultCharset()); response = IOUtils.toString((InputStream) serverError.getResponse().getEntity(), Charset.defaultCharset());
} catch (IOException ignore) { } catch (IOException ignore) {
} }
throw new AssertionError(String.format("Request failed. HTTP status: %d, response: %s", serverError.getResponse().getStatus(), response)); throw new AssertionError(String.format("Request failed. HTTP status: %d, response: %s", serverError.getResponse().getStatus(), response));
} }
} }

View File

@ -1,66 +1,66 @@
package cz.moneta.test.harness.connectors.web; package cz.moneta.test.harness.connectors.web;
import org.openqa.selenium.Alert; import org.openqa.selenium.Alert;
import org.openqa.selenium.TimeoutException; import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.support.ui.FluentWait; import org.openqa.selenium.support.ui.FluentWait;
import java.time.Duration; import java.time.Duration;
import java.util.function.Consumer; import java.util.function.Consumer;
import static org.openqa.selenium.support.ui.ExpectedConditions.alertIsPresent; import static org.openqa.selenium.support.ui.ExpectedConditions.alertIsPresent;
import static org.openqa.selenium.support.ui.ExpectedConditions.not; import static org.openqa.selenium.support.ui.ExpectedConditions.not;
public class Alerts { public class Alerts {
private final SeleniumWebConnector connector; private final SeleniumWebConnector connector;
public Alerts(SeleniumWebConnector connector) { public Alerts(SeleniumWebConnector connector) {
this.connector = connector; this.connector = connector;
} }
public void dismissAlert(int timeout) { public void dismissAlert(int timeout) {
handleAlert(timeout, Alert::dismiss); handleAlert(timeout, Alert::dismiss);
} }
public void acceptAlert(int timeout) { public void acceptAlert(int timeout) {
handleAlert(timeout, Alert::accept); handleAlert(timeout, Alert::accept);
} }
public boolean isAlertPresent(int timeout) { public boolean isAlertPresent(int timeout) {
try { try {
waitForAlert(timeout); waitForAlert(timeout);
return true; return true;
} catch (TimeoutException e) { } catch (TimeoutException e) {
return false; return false;
} }
} }
private void handleAlert(int timeout, Consumer<Alert> action) { private void handleAlert(int timeout, Consumer<Alert> action) {
waitForAlert(timeout); waitForAlert(timeout);
action.accept(connector.getDriver() action.accept(connector.getDriver()
.switchTo() .switchTo()
.alert()); .alert());
new FluentWait<>(connector.getDriver()) new FluentWait<>(connector.getDriver())
.withTimeout(Duration.ofSeconds(timeout)) .withTimeout(Duration.ofSeconds(timeout))
.pollingEvery(Duration.ofMillis(300)) .pollingEvery(Duration.ofMillis(300))
.withMessage("Alert is still present after " + timeout + " seconds timeout.") .withMessage("Alert is still present after " + timeout + " seconds timeout.")
.until(driver -> not(alertIsPresent())).apply(connector.getDriver()); .until(driver -> not(alertIsPresent())).apply(connector.getDriver());
} }
public String getAlertText(int timeout) { public String getAlertText(int timeout) {
waitForAlert(timeout); waitForAlert(timeout);
return connector.getDriver() return connector.getDriver()
.switchTo() .switchTo()
.alert() .alert()
.getText(); .getText();
} }
private void waitForAlert(int timeout) { private void waitForAlert(int timeout) {
new FluentWait<>(connector.getDriver()) new FluentWait<>(connector.getDriver())
.withTimeout(Duration.ofSeconds(timeout)) .withTimeout(Duration.ofSeconds(timeout))
.pollingEvery(Duration.ofMillis(300)) .pollingEvery(Duration.ofMillis(300))
.withMessage("Alert is not present after " + timeout + " seconds timeout.") .withMessage("Alert is not present after " + timeout + " seconds timeout.")
.until(driver -> alertIsPresent().apply(connector.getDriver())); .until(driver -> alertIsPresent().apply(connector.getDriver()));
} }
} }

View File

@ -1,67 +1,67 @@
package cz.moneta.test.harness.connectors.web; package cz.moneta.test.harness.connectors.web;
import cz.moneta.test.harness.support.web.Clickable; import cz.moneta.test.harness.support.web.Clickable;
import cz.moneta.test.harness.support.web.Lookup; import cz.moneta.test.harness.support.web.Lookup;
import org.openqa.selenium.JavascriptExecutor; import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebElement; import org.openqa.selenium.WebElement;
import org.openqa.selenium.interactions.Actions; import org.openqa.selenium.interactions.Actions;
import org.openqa.selenium.support.ui.ExpectedConditions; import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait; import org.openqa.selenium.support.ui.WebDriverWait;
import java.time.Duration; import java.time.Duration;
import java.util.Optional; import java.util.Optional;
public class Clicks { public class Clicks {
private final SeleniumWebConnector connector; private final SeleniumWebConnector connector;
private static final int CLICKABLE_TIMEOUT = 5; private static final int CLICKABLE_TIMEOUT = 5;
private final static String DOUBLE_CLICK_JS = "var evt = document.createEvent('MouseEvents');" private final static String DOUBLE_CLICK_JS = "var evt = document.createEvent('MouseEvents');"
+ "evt.initMouseEvent('dblclick',true, true, window, 10, 0, 0, 0, 0, false, false, false, false, 0,null);" + "evt.initMouseEvent('dblclick',true, true, window, 10, 0, 0, 0, 0, false, false, false, false, 0,null);"
+ "arguments[0].dispatchEvent(evt);"; + "arguments[0].dispatchEvent(evt);";
private final static String JS_CLICK_SCRIPT = "console.debug('jsClick start'); " + private final static String JS_CLICK_SCRIPT = "console.debug('jsClick start'); " +
"setTimeout(function(elem){ elem.click();}, 0, arguments[0]); " + "setTimeout(function(elem){ elem.click();}, 0, arguments[0]); " +
"console.debug('jsClick end');"; "console.debug('jsClick end');";
public Clicks(SeleniumWebConnector connector) { public Clicks(SeleniumWebConnector connector) {
this.connector = connector; this.connector = connector;
} }
public void click(Clickable clickable, Lookup lookup) { public void click(Clickable clickable, Lookup lookup) {
WebElement element = connector.waits().waitForLazyElement(clickable.getPath(), lookup); WebElement element = connector.waits().waitForLazyElement(clickable.getPath(), lookup);
isClickable(element).click(); isClickable(element).click();
} }
protected WebElement isClickable(WebElement webElement) { protected WebElement isClickable(WebElement webElement) {
WebDriverWait wait = new WebDriverWait(connector.getDriver(), Duration.ofSeconds(CLICKABLE_TIMEOUT)); WebDriverWait wait = new WebDriverWait(connector.getDriver(), Duration.ofSeconds(CLICKABLE_TIMEOUT));
return wait.until(ExpectedConditions.elementToBeClickable(webElement)); return wait.until(ExpectedConditions.elementToBeClickable(webElement));
} }
protected WebElement isClickable(Clickable clickable, Lookup lookup) { protected WebElement isClickable(Clickable clickable, Lookup lookup) {
WebDriverWait wait = new WebDriverWait(connector.getDriver(), Duration.ofSeconds(CLICKABLE_TIMEOUT)); WebDriverWait wait = new WebDriverWait(connector.getDriver(), Duration.ofSeconds(CLICKABLE_TIMEOUT));
return wait.until(ExpectedConditions.elementToBeClickable(connector.resolveLookup(lookup).apply(clickable.getPath()))); return wait.until(ExpectedConditions.elementToBeClickable(connector.resolveLookup(lookup).apply(clickable.getPath())));
} }
public void doubleClick(Clickable clickable, Lookup lookup) { public void doubleClick(Clickable clickable, Lookup lookup) {
Actions actions = new Actions(connector.getDriver()); Actions actions = new Actions(connector.getDriver());
WebElement element = connector.waits().waitForLazyElement(clickable.getPath(), lookup); WebElement element = connector.waits().waitForLazyElement(clickable.getPath(), lookup);
actions.doubleClick(element); actions.doubleClick(element);
} }
public void jsDoubleClick(Clickable clickable, Lookup lookup) { public void jsDoubleClick(Clickable clickable, Lookup lookup) {
Optional.of(connector.getDriver()) Optional.of(connector.getDriver())
.filter(JavascriptExecutor.class::isInstance) .filter(JavascriptExecutor.class::isInstance)
.map(JavascriptExecutor.class::cast) .map(JavascriptExecutor.class::cast)
.ifPresent(e -> e.executeScript(DOUBLE_CLICK_JS, .ifPresent(e -> e.executeScript(DOUBLE_CLICK_JS,
connector.waits().waitForLazyElement(clickable.getPath(), lookup))); connector.waits().waitForLazyElement(clickable.getPath(), lookup)));
} }
public void jsClick(Clickable clickable, Lookup lookup) { public void jsClick(Clickable clickable, Lookup lookup) {
Optional.of(connector.getDriver()) Optional.of(connector.getDriver())
.filter(JavascriptExecutor.class::isInstance) .filter(JavascriptExecutor.class::isInstance)
.map(JavascriptExecutor.class::cast) .map(JavascriptExecutor.class::cast)
.ifPresent(e -> e.executeScript(JS_CLICK_SCRIPT, .ifPresent(e -> e.executeScript(JS_CLICK_SCRIPT,
connector.waits().waitForLazyElement(clickable.getPath(), lookup))); connector.waits().waitForLazyElement(clickable.getPath(), lookup)));
} }
} }

View File

@ -1,30 +1,30 @@
package cz.moneta.test.harness.connectors.web; package cz.moneta.test.harness.connectors.web;
import org.openqa.selenium.Cookie; import org.openqa.selenium.Cookie;
import java.util.Date; import java.util.Date;
import java.util.Optional; import java.util.Optional;
public class Cookies { public class Cookies {
private final SeleniumWebConnector connector; private final SeleniumWebConnector connector;
public Cookies(SeleniumWebConnector connector) { public Cookies(SeleniumWebConnector connector) {
this.connector = connector; this.connector = connector;
} }
public String getCookie(String name) { public String getCookie(String name) {
return Optional.ofNullable(connector.getDriver().manage().getCookieNamed(name)) return Optional.ofNullable(connector.getDriver().manage().getCookieNamed(name))
.map(Cookie::getValue) .map(Cookie::getValue)
.orElse(null); .orElse(null);
} }
public void addCookie(String name, String value, String domain, String path, Date expiry, boolean isSecure, boolean isHttpOnly) { public void addCookie(String name, String value, String domain, String path, Date expiry, boolean isSecure, boolean isHttpOnly) {
Cookie cookie = new Cookie(name, value, domain, path, expiry, isSecure, isHttpOnly); Cookie cookie = new Cookie(name, value, domain, path, expiry, isSecure, isHttpOnly);
connector.getDriver().manage().addCookie(cookie); connector.getDriver().manage().addCookie(cookie);
} }
public void addCookie(Cookie cookie) { public void addCookie(Cookie cookie) {
connector.getDriver().manage().addCookie(cookie); connector.getDriver().manage().addCookie(cookie);
} }
} }

View File

@ -1,255 +1,255 @@
package cz.moneta.test.harness.connectors.web; package cz.moneta.test.harness.connectors.web;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
import cz.moneta.test.harness.connectors.rest.SimpleRestConnector; import cz.moneta.test.harness.connectors.rest.SimpleRestConnector;
import cz.moneta.test.harness.exception.HarnessConfigurationException; import cz.moneta.test.harness.exception.HarnessConfigurationException;
import cz.moneta.test.harness.exception.HarnessException; import cz.moneta.test.harness.exception.HarnessException;
import cz.moneta.test.harness.support.selenium.ClasspathFileDetector; import cz.moneta.test.harness.support.selenium.ClasspathFileDetector;
import jakarta.ws.rs.client.Entity; import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.GenericType; import jakarta.ws.rs.core.GenericType;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.openqa.selenium.*; import org.openqa.selenium.*;
import org.openqa.selenium.logging.*; import org.openqa.selenium.logging.*;
import org.openqa.selenium.manager.SeleniumManager; import org.openqa.selenium.manager.SeleniumManager;
import org.openqa.selenium.remote.RemoteWebDriver; import org.openqa.selenium.remote.RemoteWebDriver;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.*; import java.util.*;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import static cz.moneta.test.harness.constants.HarnessConfigConstants.SELENIUM_DOWNLOAD_DIRECTORY; import static cz.moneta.test.harness.constants.HarnessConfigConstants.SELENIUM_DOWNLOAD_DIRECTORY;
public class DriverUtils { public class DriverUtils {
protected static final Set<String> LOG_TYPES = Stream.of(LogType.BROWSER, LogType.CLIENT, LogType.DRIVER, protected static final Set<String> LOG_TYPES = Stream.of(LogType.BROWSER, LogType.CLIENT, LogType.DRIVER,
LogType.PERFORMANCE, LogType.PROFILER, LogType.SERVER).collect(Collectors.toSet()); LogType.PERFORMANCE, LogType.PROFILER, LogType.SERVER).collect(Collectors.toSet());
private static final Logger logger = LogManager.getLogger(DriverUtils.class); private static final Logger logger = LogManager.getLogger(DriverUtils.class);
private final SeleniumWebConnector connector; private final SeleniumWebConnector connector;
public DriverUtils(SeleniumWebConnector connector) { public DriverUtils(SeleniumWebConnector connector) {
this.connector = connector; this.connector = connector;
} }
public String takeSnapshot(String snapshotFileName) { public String takeSnapshot(String snapshotFileName) {
String snapshotsPath = connector.getStore().getConfig("selenium.snapshots.path", "target/screenshots"); String snapshotsPath = connector.getStore().getConfig("selenium.snapshots.path", "target/screenshots");
File file = Paths.get(snapshotsPath, snapshotFileName).toFile(); File file = Paths.get(snapshotsPath, snapshotFileName).toFile();
try { try {
FileUtils.writeByteArrayToFile(file, ((TakesScreenshot) connector.getDriver()).getScreenshotAs(OutputType.BYTES)); FileUtils.writeByteArrayToFile(file, ((TakesScreenshot) connector.getDriver()).getScreenshotAs(OutputType.BYTES));
} catch (IOException e) { } catch (IOException e) {
throw new IllegalStateException("Unable to write snapshot " + snapshotFileName + " to " + snapshotsPath, e); throw new IllegalStateException("Unable to write snapshot " + snapshotFileName + " to " + snapshotsPath, e);
} catch (WebDriverException e2) { // An unexpected error swallows why the test failed } catch (WebDriverException e2) { // An unexpected error swallows why the test failed
logger.error("Error during getting snapshot: " + e2.getMessage()); logger.error("Error during getting snapshot: " + e2.getMessage());
e2.printStackTrace(); e2.printStackTrace();
} }
return file.getAbsolutePath(); return file.getAbsolutePath();
} }
public String captureLog(String logFileName, String logType) { public String captureLog(String logFileName, String logType) {
String path = connector.getStore().getConfig("selenium.logs.path", "target/logs"); String path = connector.getStore().getConfig("selenium.logs.path", "target/logs");
Logs logs = connector.getDriver() Logs logs = connector.getDriver()
.manage() .manage()
.logs(); .logs();
LogEntries logEntriesForType = null; LogEntries logEntriesForType = null;
try { try {
logEntriesForType = logs.get(logType); logEntriesForType = logs.get(logType);
} catch (Exception wde) { } catch (Exception wde) {
logger.warn(() -> "We were not able to get log entries for log type: " + logType); logger.warn(() -> "We were not able to get log entries for log type: " + logType);
} }
if (logEntriesForType != null) { if (logEntriesForType != null) {
List<LogEntry> logEntries = logEntriesForType.getAll(); List<LogEntry> logEntries = logEntriesForType.getAll();
try { try {
File file = Paths.get(path, logFileName, ".log").toFile(); File file = Paths.get(path, logFileName, ".log").toFile();
FileUtils.writeLines(file, logEntries); FileUtils.writeLines(file, logEntries);
return file.getAbsolutePath(); return file.getAbsolutePath();
} catch (IOException e) { } catch (IOException e) {
throw new IllegalStateException("Unable to write log " + logFileName + " to " + path, e); throw new IllegalStateException("Unable to write log " + logFileName + " to " + path, e);
} }
} }
return null; return null;
} }
public void captureDom(String domFileName) { public void captureDom(String domFileName) {
String path = connector.getStore().getConfig("selenium.dom.path", "target/dom"); String path = connector.getStore().getConfig("selenium.dom.path", "target/dom");
File file = Paths.get(path, domFileName).toFile(); File file = Paths.get(path, domFileName).toFile();
try { try {
FileUtils.write(file, connector.getDriver().getPageSource()); FileUtils.write(file, connector.getDriver().getPageSource());
} catch (IOException e) { } catch (IOException e) {
throw new IllegalStateException("Unable to write " + domFileName + " to " + path, e); throw new IllegalStateException("Unable to write " + domFileName + " to " + path, e);
} catch (WebDriverException e2) { // An unexpected error swallows why the test failed } catch (WebDriverException e2) { // An unexpected error swallows why the test failed
logger.error("Error during getting DOM: " + e2.getMessage()); logger.error("Error during getting DOM: " + e2.getMessage());
e2.printStackTrace(); e2.printStackTrace();
} }
} }
/** /**
* Add platform/browser/driver independent capabilities. * Add platform/browser/driver independent capabilities.
* *
* @return DesiredCapabilities with default capabilities added * @return DesiredCapabilities with default capabilities added
*/ */
protected void addDefaultDesiredCapabilities(MutableCapabilities capabilities) { protected void addDefaultDesiredCapabilities(MutableCapabilities capabilities) {
LoggingPreferences logs = new LoggingPreferences(); LoggingPreferences logs = new LoggingPreferences();
LOG_TYPES.forEach(lt -> logs.enable(lt, Level.ALL)); LOG_TYPES.forEach(lt -> logs.enable(lt, Level.ALL));
//capabilities.setCapability(CapabilityType.LOGGING_PREFS, logs); // TODO: vyřešit //capabilities.setCapability(CapabilityType.LOGGING_PREFS, logs); // TODO: vyřešit
java.util.logging.Logger.getLogger(RemoteWebDriver.class.getName()).setLevel(Level.INFO); java.util.logging.Logger.getLogger(RemoteWebDriver.class.getName()).setLevel(Level.INFO);
java.util.logging.Logger.getLogger(SeleniumManager.class.getName()).setLevel(Level.INFO); java.util.logging.Logger.getLogger(SeleniumManager.class.getName()).setLevel(Level.INFO);
} }
/** /**
* Allows to configure additional capabilities. * Allows to configure additional capabilities.
* *
* @param capabilities collection of capabilities to be added to * @param capabilities collection of capabilities to be added to
* @param key key to use for capabilities lookup in configuration * @param key key to use for capabilities lookup in configuration
*/ */
protected void addAdditionalDesiredCapabilities(MutableCapabilities capabilities, String key) { protected void addAdditionalDesiredCapabilities(MutableCapabilities capabilities, String key) {
String additionalCapabilities = connector.getStore().getConfig(key); String additionalCapabilities = connector.getStore().getConfig(key);
if (additionalCapabilities != null) { if (additionalCapabilities != null) {
Arrays.stream(additionalCapabilities.split(",")).forEach( Arrays.stream(additionalCapabilities.split(",")).forEach(
(item) -> { (item) -> {
String[] keyValue = item.split("="); String[] keyValue = item.split("=");
String value = keyValue[1]; String value = keyValue[1];
if (!keyValue[0].equals("applicationName")) { if (!keyValue[0].equals("applicationName")) {
if ("true".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value)) { if ("true".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value)) {
capabilities.setCapability(keyValue[0], Boolean.parseBoolean(value)); capabilities.setCapability(keyValue[0], Boolean.parseBoolean(value));
} else { } else {
capabilities.setCapability(keyValue[0], value); capabilities.setCapability(keyValue[0], value);
} }
} }
} }
); );
} }
} }
WebDriver initializeRemoteWebDriver(String hubUrl) { WebDriver initializeRemoteWebDriver(String hubUrl) {
try { try {
// TODO: timeout připojení k browseru // TODO: timeout připojení k browseru
// HttpClient.Factory factory = HttpClient.Factory.createDefault(); // HttpClient.Factory factory = HttpClient.Factory.createDefault();
// HttpClient.Builder builder = factory.builder() // HttpClient.Builder builder = factory.builder()
// .connectionTimeout(Duration.ofSeconds(10)); // .connectionTimeout(Duration.ofSeconds(10));
// HttpClient.Factory clientFactory = new HttpClient.Factory() { // HttpClient.Factory clientFactory = new HttpClient.Factory() {
// @Override // @Override
// public HttpClient.Builder builder() { // public HttpClient.Builder builder() {
// return new HttpClient.Builder() { // return new HttpClient.Builder() {
// @Override // @Override
// public HttpClient createClient(URL url) { // public HttpClient createClient(URL url) {
// return builder.createClient(url); // return builder.createClient(url);
// } // }
// }; // };
// } // }
// @Override // @Override
// public void cleanupIdleClients() { // public void cleanupIdleClients() {
// factory.cleanupIdleClients(); // factory.cleanupIdleClients();
// } // }
// }; // };
// HttpCommandExecutor executor = new HttpCommandExecutor(new HashMap<>(), new URL(hubUrl), clientFactory); // HttpCommandExecutor executor = new HttpCommandExecutor(new HashMap<>(), new URL(hubUrl), clientFactory);
// RemoteWebDriver remoteWebDriver = new RemoteWebDriver(executor, connector.getDesiredRemoteCapabilities()); // RemoteWebDriver remoteWebDriver = new RemoteWebDriver(executor, connector.getDesiredRemoteCapabilities());
RemoteWebDriver remoteWebDriver = new RemoteWebDriver(new URL(hubUrl), connector.getDesiredRemoteCapabilities()); RemoteWebDriver remoteWebDriver = new RemoteWebDriver(new URL(hubUrl), connector.getDesiredRemoteCapabilities());
remoteWebDriver.setFileDetector(new ClasspathFileDetector()); remoteWebDriver.setFileDetector(new ClasspathFileDetector());
String sessionId = remoteWebDriver.getSessionId().toString(); String sessionId = remoteWebDriver.getSessionId().toString();
logger.info(() -> String.format("New Selenium web driver session %s started at node %s", sessionId, getNodeUrl(hubUrl, sessionId))); logger.info(() -> String.format("New Selenium web driver session %s started at node %s", sessionId, getNodeUrl(hubUrl, sessionId)));
return remoteWebDriver; return remoteWebDriver;
} catch (Exception e) { } catch (Exception e) {
logger.error(() -> "Failed to initialize remote web driver with url " + hubUrl, e); logger.error(() -> "Failed to initialize remote web driver with url " + hubUrl, e);
return null; return null;
} }
} }
private String getNodeUrl(String hubUrl, String sessionId) { private String getNodeUrl(String hubUrl, String sessionId) {
HttpURLConnection gridApiConnection = null; HttpURLConnection gridApiConnection = null;
try { try {
URL url = new URL(hubUrl); URL url = new URL(hubUrl);
URL gridGraphQLUrl = new URL(String.format("http://%s:%d/graphql", url.getHost(), url.getPort())); URL gridGraphQLUrl = new URL(String.format("http://%s:%d/graphql", url.getHost(), url.getPort()));
Map<String, Object> headers = new HashMap<>(); Map<String, Object> headers = new HashMap<>();
headers.put("Content-Type", "application/json"); headers.put("Content-Type", "application/json");
Pair<Integer, String> response = new SimpleRestConnector(gridGraphQLUrl.toString(), "SeleniumGraphQL") Pair<Integer, String> response = new SimpleRestConnector(gridGraphQLUrl.toString(), "SeleniumGraphQL")
.post("", .post("",
Entity.json("{\"query\":\"{ session (id: \\\"" + sessionId + "\\\") { uri, nodeUri } } \"}"), Entity.json("{\"query\":\"{ session (id: \\\"" + sessionId + "\\\") { uri, nodeUri } } \"}"),
new GenericType<>(String.class), new GenericType<>(String.class),
headers); headers);
JsonObject gridResponse = JsonParser.parseString(response.getRight().toString()).getAsJsonObject(); JsonObject gridResponse = JsonParser.parseString(response.getRight().toString()).getAsJsonObject();
String nodeUrlString = gridResponse.getAsJsonObject("data").getAsJsonObject("session").get("uri").getAsString(); String nodeUrlString = gridResponse.getAsJsonObject("data").getAsJsonObject("session").get("uri").getAsString();
URL nodeUrl = new URL(nodeUrlString); URL nodeUrl = new URL(nodeUrlString);
this.connector.setNodeHost(nodeUrl.getHost()); this.connector.setNodeHost(nodeUrl.getHost());
return nodeUrlString; return nodeUrlString;
} catch (Exception e) { } catch (Exception e) {
throw new HarnessException(String.format("Failed to get node info for session %s", sessionId), e); throw new HarnessException(String.format("Failed to get node info for session %s", sessionId), e);
} finally { } finally {
if (gridApiConnection != null) { if (gridApiConnection != null) {
try { try {
gridApiConnection.disconnect(); gridApiConnection.disconnect();
} catch (Exception e) { } catch (Exception e) {
throw new HarnessException("Could not close connection to grid API.", e); throw new HarnessException("Could not close connection to grid API.", e);
} }
} }
} }
} }
public boolean checkIfFileExists(String directory, String filename, int waitInSeconds, boolean deleteFile, boolean useDirectoryListMethod) { public boolean checkIfFileExists(String directory, String filename, int waitInSeconds, boolean deleteFile, boolean useDirectoryListMethod) {
boolean fileExists = false; boolean fileExists = false;
// If no directory set as input parameter then get it from config file or System properties. // If no directory set as input parameter then get it from config file or System properties.
if (directory == null) { if (directory == null) {
directory = Stream.of(Optional.ofNullable(System.getProperty(SELENIUM_DOWNLOAD_DIRECTORY)), directory = Stream.of(Optional.ofNullable(System.getProperty(SELENIUM_DOWNLOAD_DIRECTORY)),
Optional.ofNullable(connector.getStore().getConfig(SELENIUM_DOWNLOAD_DIRECTORY))) Optional.ofNullable(connector.getStore().getConfig(SELENIUM_DOWNLOAD_DIRECTORY)))
.filter(Optional::isPresent) .filter(Optional::isPresent)
.map(Optional::get) .map(Optional::get)
.findFirst() .findFirst()
.orElseThrow(() -> new HarnessConfigurationException(("You need to set download directory or configure " + SELENIUM_DOWNLOAD_DIRECTORY + " parameter!"))); .orElseThrow(() -> new HarnessConfigurationException(("You need to set download directory or configure " + SELENIUM_DOWNLOAD_DIRECTORY + " parameter!")));
} }
// File which we try to find // File which we try to find
File fileToCheck = FileUtils.getFile(directory, filename); File fileToCheck = FileUtils.getFile(directory, filename);
File folderToCheck = new File(directory); File folderToCheck = new File(directory);
try { try {
// Checking if file is downloaded. Try it every second to "waitInSeconds" limit. // Checking if file is downloaded. Try it every second to "waitInSeconds" limit.
for (int i = 0; i <= waitInSeconds; i++) { for (int i = 0; i <= waitInSeconds; i++) {
// Simulating wait for file download. // Simulating wait for file download.
Thread.sleep(TimeUnit.SECONDS.toMillis(1)); Thread.sleep(TimeUnit.SECONDS.toMillis(1));
if (useDirectoryListMethod) { if (useDirectoryListMethod) {
logger.debug("Checking if file: {} present in directory list: {}.", filename, directory); logger.debug("Checking if file: {} present in directory list: {}.", filename, directory);
fileExists = Arrays.stream(folderToCheck.list()).anyMatch(filename::equals); fileExists = Arrays.stream(folderToCheck.list()).anyMatch(filename::equals);
} else { } else {
logger.debug("Checking if file: {} exists in directory: {}.", filename, directory); logger.debug("Checking if file: {} exists in directory: {}.", filename, directory);
fileExists = (fileToCheck.exists() || fileToCheck.isFile()); fileExists = (fileToCheck.exists() || fileToCheck.isFile());
} }
if (fileExists) break; if (fileExists) break;
} }
// When file exists - log it and delete file // When file exists - log it and delete file
if (fileExists) { if (fileExists) {
logger.debug("Found file: {}.", fileToCheck.getAbsolutePath()); logger.debug("Found file: {}.", fileToCheck.getAbsolutePath());
logger.info("Found expected file: {} in directory {}.", filename, directory); logger.info("Found expected file: {} in directory {}.", filename, directory);
// For cleaning purposes. (Does not delete all files, ex. in case of wrong filename the downloaded file will remain in directory.) // For cleaning purposes. (Does not delete all files, ex. in case of wrong filename the downloaded file will remain in directory.)
if (deleteFile) { if (deleteFile) {
logger.debug("Deleting file: {}.", fileToCheck.getAbsolutePath()); logger.debug("Deleting file: {}.", fileToCheck.getAbsolutePath());
Files.delete(fileToCheck.toPath()); Files.delete(fileToCheck.toPath());
} }
} else { } else {
throw new HarnessException(String.format("Could not find expected file: %s in directory %s withing the specified timeout", filename, directory)); throw new HarnessException(String.format("Could not find expected file: %s in directory %s withing the specified timeout", filename, directory));
} }
} catch (Exception e) { } catch (Exception e) {
throw new HarnessException(String.format("Something went wrong: Could not find expected file: %s in directory %s.", filename, directory), e); throw new HarnessException(String.format("Something went wrong: Could not find expected file: %s in directory %s.", filename, directory), e);
} }
return fileExists; return fileExists;
} }
} }

View File

@ -1,99 +1,99 @@
package cz.moneta.test.harness.connectors.web; package cz.moneta.test.harness.connectors.web;
import cz.moneta.test.harness.support.web.Lookup; import cz.moneta.test.harness.support.web.Lookup;
import cz.moneta.test.harness.support.web.Until; import cz.moneta.test.harness.support.web.Until;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.openqa.selenium.JavascriptExecutor; import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.TimeoutException; import org.openqa.selenium.TimeoutException;
import org.openqa.selenium.WebElement; import org.openqa.selenium.WebElement;
import org.openqa.selenium.interactions.Actions; import org.openqa.selenium.interactions.Actions;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
public class ElementsActions { public class ElementsActions {
private static final int DEFAULT_TIMEOUT_FOR_WAIT = 30; private static final int DEFAULT_TIMEOUT_FOR_WAIT = 30;
private static final Logger logger = LogManager.getLogger(ElementsActions.class); private static final Logger logger = LogManager.getLogger(ElementsActions.class);
private final SeleniumWebConnector connector; private final SeleniumWebConnector connector;
private final String JS = "var evObj = document.createEvent('MouseEvents');" private final String JS = "var evObj = document.createEvent('MouseEvents');"
+ "evObj.initMouseEvent(\"mouseover\",true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);" + "evObj.initMouseEvent(\"mouseover\",true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);"
+ "arguments[0].dispatchEvent(evObj);"; + "arguments[0].dispatchEvent(evObj);";
public ElementsActions(SeleniumWebConnector connector) { public ElementsActions(SeleniumWebConnector connector) {
this.connector = connector; this.connector = connector;
} }
public void moveToElement(String path, Lookup lookup) { public void moveToElement(String path, Lookup lookup) {
Actions actions = new Actions(connector.getDriver()); Actions actions = new Actions(connector.getDriver());
actions.moveToElement(waitForLazyElement(path, lookup)) actions.moveToElement(waitForLazyElement(path, lookup))
.build() .build()
.perform(); .perform();
} }
public void moveToElementAndClick(String hoverElementPath, String clickElementPath, Lookup lookup) { public void moveToElementAndClick(String hoverElementPath, String clickElementPath, Lookup lookup) {
Optional.of(connector.getDriver()) Optional.of(connector.getDriver())
.filter(JavascriptExecutor.class::isInstance) .filter(JavascriptExecutor.class::isInstance)
.map(JavascriptExecutor.class::cast) .map(JavascriptExecutor.class::cast)
.ifPresent(e -> { .ifPresent(e -> {
e.executeScript(JS, waitForLazyElement(hoverElementPath, lookup)); e.executeScript(JS, waitForLazyElement(hoverElementPath, lookup));
connector.clicks().click(() -> clickElementPath, lookup); connector.clicks().click(() -> clickElementPath, lookup);
}); });
} }
public String getText(String path, Lookup lookup) { public String getText(String path, Lookup lookup) {
return waitForLazyElement(path, lookup).getText(); return waitForLazyElement(path, lookup).getText();
} }
public boolean isElementVisible(String path, Lookup lookup) { public boolean isElementVisible(String path, Lookup lookup) {
return isElementVisible(DEFAULT_TIMEOUT_FOR_WAIT, path, lookup); return isElementVisible(DEFAULT_TIMEOUT_FOR_WAIT, path, lookup);
} }
public boolean isElementVisible(int timeout, String path, Lookup lookup) { public boolean isElementVisible(int timeout, String path, Lookup lookup) {
try { try {
waitForElement(timeout, path, lookup, Until.VISIBLE); waitForElement(timeout, path, lookup, Until.VISIBLE);
return true; return true;
} catch (TimeoutException e) { } catch (TimeoutException e) {
logger.warn("Element {} is not visible after {}s.", () -> path, () -> timeout); logger.warn("Element {} is not visible after {}s.", () -> path, () -> timeout);
return false; return false;
} }
} }
public void dragAndDropElement(String source, String target, Lookup lookup) { public void dragAndDropElement(String source, String target, Lookup lookup) {
Actions actions = new Actions(connector.getDriver()); Actions actions = new Actions(connector.getDriver());
actions.dragAndDrop(waitForLazyElement(source, lookup), findElement(target, lookup)) actions.dragAndDrop(waitForLazyElement(source, lookup), findElement(target, lookup))
.build() .build()
.perform(); .perform();
} }
public void dragAndDropBy(String source, int xOffset, int yOffset, Lookup lookup) { public void dragAndDropBy(String source, int xOffset, int yOffset, Lookup lookup) {
Actions actions = new Actions(connector.getDriver()); Actions actions = new Actions(connector.getDriver());
actions.dragAndDropBy(waitForLazyElement(source, lookup), xOffset, yOffset) actions.dragAndDropBy(waitForLazyElement(source, lookup), xOffset, yOffset)
.build() .build()
.perform(); .perform();
} }
private WebElement waitForLazyElement(String path, Lookup lookup) { private WebElement waitForLazyElement(String path, Lookup lookup) {
return connector.waits().waitForLazyElement(path, lookup); return connector.waits().waitForLazyElement(path, lookup);
} }
private WebElement findElement(String path, Lookup lookup) { private WebElement findElement(String path, Lookup lookup) {
return connector.getDriver() return connector.getDriver()
.findElement(connector.resolveLookup(lookup).apply(path)); .findElement(connector.resolveLookup(lookup).apply(path));
} }
private void waitForElement(int timeout, String path, Lookup lookup, Until until) { private void waitForElement(int timeout, String path, Lookup lookup, Until until) {
connector.waits().waitForElements(timeout, lookup, until, path); connector.waits().waitForElements(timeout, lookup, until, path);
} }
public void scrollIntoView(String path, Lookup lookup) { public void scrollIntoView(String path, Lookup lookup) {
WebElement element = connector.getDriver().findElement(connector.resolveLookup(lookup).apply(path)); WebElement element = connector.getDriver().findElement(connector.resolveLookup(lookup).apply(path));
connector.scrolling().scrollIntoView(element); connector.scrolling().scrollIntoView(element);
} }
public List<WebElement> findElements(String path, Lookup lookup) { public List<WebElement> findElements(String path, Lookup lookup) {
return connector.getDriver().findElements(connector.resolveLookup(lookup).apply(path)); return connector.getDriver().findElements(connector.resolveLookup(lookup).apply(path));
} }
} }

View File

@ -1,37 +1,37 @@
package cz.moneta.test.harness.connectors.web; package cz.moneta.test.harness.connectors.web;
import cz.moneta.test.harness.support.web.Lookup; import cz.moneta.test.harness.support.web.Lookup;
import org.openqa.selenium.WebElement; import org.openqa.selenium.WebElement;
import java.util.List; import java.util.List;
public class ElementsChecks { public class ElementsChecks {
private final SeleniumWebConnector connector; private final SeleniumWebConnector connector;
public ElementsChecks(SeleniumWebConnector connector) { public ElementsChecks(SeleniumWebConnector connector) {
this.connector = connector; this.connector = connector;
} }
public boolean isElementPresent(String path, Lookup lookup) { public boolean isElementPresent(String path, Lookup lookup) {
return !connector.getDriver().findElements(connector.resolveLookup(lookup).apply(path)).isEmpty(); return !connector.getDriver().findElements(connector.resolveLookup(lookup).apply(path)).isEmpty();
} }
public boolean isElementEnabled(String path, Lookup lookup) { public boolean isElementEnabled(String path, Lookup lookup) {
return connector.waits().waitForLazyElement(path, lookup).isEnabled(); return connector.waits().waitForLazyElement(path, lookup).isEnabled();
} }
public void isAtLeastOneElementPresent(String xpath, Lookup lookup) { public void isAtLeastOneElementPresent(String xpath, Lookup lookup) {
connector.waits() connector.waits()
.waitForLazyElement(xpath, lookup); .waitForLazyElement(xpath, lookup);
} }
public void checkElementContent(String path, String content, Lookup lookup) { public void checkElementContent(String path, String content, Lookup lookup) {
List<WebElement> elements = connector.waits().waitForLazyElements(path, lookup); List<WebElement> elements = connector.waits().waitForLazyElements(path, lookup);
elements.stream() elements.stream()
.map(WebElement::getText) .map(WebElement::getText)
.filter(text -> text.contains(content)) .filter(text -> text.contains(content))
.findFirst() .findFirst()
.orElseThrow(() -> new AssertionError(String.format("Cannot find element that contains text %s", content))); .orElseThrow(() -> new AssertionError(String.format("Cannot find element that contains text %s", content)));
} }
} }

View File

@ -1,19 +1,19 @@
package cz.moneta.test.harness.connectors.web; package cz.moneta.test.harness.connectors.web;
import cz.moneta.test.harness.support.web.Lookup; import cz.moneta.test.harness.support.web.Lookup;
public class Frames { public class Frames {
private final SeleniumWebConnector connector; private final SeleniumWebConnector connector;
public Frames(SeleniumWebConnector connector) { public Frames(SeleniumWebConnector connector) {
this.connector = connector; this.connector = connector;
} }
public void switchToFrame(String frame, Lookup lookup) { public void switchToFrame(String frame, Lookup lookup) {
connector connector
.getDriver() .getDriver()
.switchTo().frame(connector.waits().waitForLazyElement(frame, lookup)); .switchTo().frame(connector.waits().waitForLazyElement(frame, lookup));
} }
} }

View File

@ -1,40 +1,40 @@
package cz.moneta.test.harness.connectors.web; package cz.moneta.test.harness.connectors.web;
public class Navigations { public class Navigations {
private final SeleniumWebConnector connector; private final SeleniumWebConnector connector;
public Navigations(SeleniumWebConnector connector) { public Navigations(SeleniumWebConnector connector) {
this.connector = connector; this.connector = connector;
} }
public void switchToDefaultContent() { public void switchToDefaultContent() {
connector.getDriver() connector.getDriver()
.switchTo() .switchTo()
.defaultContent(); .defaultContent();
} }
public void navigateTo(String url) { public void navigateTo(String url) {
connector.getDriver() connector.getDriver()
.navigate() .navigate()
.to(url); .to(url);
} }
public void navigateRefresh() { public void navigateRefresh() {
connector.getDriver() connector.getDriver()
.navigate() .navigate()
.refresh(); .refresh();
} }
public void navigateBack() { public void navigateBack() {
connector.getDriver() connector.getDriver()
.navigate() .navigate()
.back(); .back();
} }
public void navigateForward() { public void navigateForward() {
connector.getDriver() connector.getDriver()
.navigate() .navigate()
.forward(); .forward();
} }
} }

View File

@ -1,24 +1,24 @@
package cz.moneta.test.harness.connectors.web; package cz.moneta.test.harness.connectors.web;
import org.openqa.selenium.JavascriptExecutor; import org.openqa.selenium.JavascriptExecutor;
import java.util.Optional; import java.util.Optional;
public class Scripts { public class Scripts {
private final SeleniumWebConnector connector; private final SeleniumWebConnector connector;
public Scripts(SeleniumWebConnector connector) { public Scripts(SeleniumWebConnector connector) {
this.connector = connector; this.connector = connector;
} }
public Object executeScript(String script, Object[] args) { public Object executeScript(String script, Object[] args) {
JavascriptExecutor executor = Optional.of(connector.getDriver()) JavascriptExecutor executor = Optional.of(connector.getDriver())
.filter(JavascriptExecutor.class::isInstance) .filter(JavascriptExecutor.class::isInstance)
.map(JavascriptExecutor.class::cast) .map(JavascriptExecutor.class::cast)
.orElseThrow(() -> new IllegalStateException("This driver does not support JavaScript!")); .orElseThrow(() -> new IllegalStateException("This driver does not support JavaScript!"));
return args == null return args == null
? executor.executeScript(script) ? executor.executeScript(script)
: executor.executeScript(script, args); : executor.executeScript(script, args);
} }
} }

View File

@ -1,16 +1,16 @@
package cz.moneta.test.harness.connectors.web; package cz.moneta.test.harness.connectors.web;
import org.openqa.selenium.WebElement; import org.openqa.selenium.WebElement;
public class Scrolling { public class Scrolling {
private final SeleniumWebConnector connector; private final SeleniumWebConnector connector;
public Scrolling(SeleniumWebConnector connector) { public Scrolling(SeleniumWebConnector connector) {
this.connector = connector; this.connector = connector;
} }
public void scrollIntoView(WebElement element) { public void scrollIntoView(WebElement element) {
connector.scripts().executeScript("arguments[0].scrollIntoView(true);", new Object[]{element}); connector.scripts().executeScript("arguments[0].scrollIntoView(true);", new Object[]{element});
} }
} }

View File

@ -1,80 +1,80 @@
package cz.moneta.test.harness.connectors.web; package cz.moneta.test.harness.connectors.web;
import cz.moneta.test.harness.support.web.Lookup; import cz.moneta.test.harness.support.web.Lookup;
import org.openqa.selenium.WebElement; import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.Select; import org.openqa.selenium.support.ui.Select;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class Selects { public class Selects {
private final SeleniumWebConnector connector; private final SeleniumWebConnector connector;
public Selects(SeleniumWebConnector connector) { public Selects(SeleniumWebConnector connector) {
this.connector = connector; this.connector = connector;
} }
public void selectByVisibleText(String path, String text, Lookup lookup) { public void selectByVisibleText(String path, String text, Lookup lookup) {
WebElement element = connector.waits() WebElement element = connector.waits()
.waitForLazyElement(path, lookup); .waitForLazyElement(path, lookup);
connector.scrolling().scrollIntoView(element); connector.scrolling().scrollIntoView(element);
new Select(element).selectByVisibleText(text); new Select(element).selectByVisibleText(text);
} }
public void selectByValue(String path, String value, Lookup lookup) { public void selectByValue(String path, String value, Lookup lookup) {
WebElement element = connector.waits() WebElement element = connector.waits()
.waitForLazyElement(path, lookup); .waitForLazyElement(path, lookup);
connector.scrolling().scrollIntoView(element); connector.scrolling().scrollIntoView(element);
connector.elementsActions().moveToElement(path, lookup); connector.elementsActions().moveToElement(path, lookup);
new Select(element).selectByValue(value); new Select(element).selectByValue(value);
} }
public void selectByOptionOrder(String path, int optionOrder, Lookup lookup) { public void selectByOptionOrder(String path, int optionOrder, Lookup lookup) {
WebElement element = connector.waits() WebElement element = connector.waits()
.waitForLazyElement(path, lookup); .waitForLazyElement(path, lookup);
connector.scrolling().scrollIntoView(element); connector.scrolling().scrollIntoView(element);
new Select(element).selectByIndex(--optionOrder); new Select(element).selectByIndex(--optionOrder);
} }
/** /**
* Select option by text part of option value * Select option by text part of option value
* <p> * <p>
* In some cases is necessary to select option only by part of value in option. This method accepts path to element * In some cases is necessary to select option only by part of value in option. This method accepts path to element
* lookup strategy value and list of text parts of options. * lookup strategy value and list of text parts of options.
* </p> * </p>
* <p> * <p>
* In this method is created in xpath for select one value. Used strategy: create xpath with contains separated by * In this method is created in xpath for select one value. Used strategy: create xpath with contains separated by
* OR. * OR.
* </p> * </p>
* <p> * <p>
* Example: * Example:
* <pre> * <pre>
* From ["ValueA", "ValueB"] will create xpath: * From ["ValueA", "ValueB"] will create xpath:
* "//option[contains(text(),'ValueA') or contains(text(),'ValueB')]" * "//option[contains(text(),'ValueA') or contains(text(),'ValueB')]"
* </pre> * </pre>
* </p> * </p>
* *
* @param path - path to select element * @param path - path to select element
* @param lookup - lookup strategy for select element * @param lookup - lookup strategy for select element
* @param textOptionsParts - list of option text parts to look for * @param textOptionsParts - list of option text parts to look for
*/ */
public void selectByContainingTexts(String path, Lookup lookup, List<String> textOptionsParts) { public void selectByContainingTexts(String path, Lookup lookup, List<String> textOptionsParts) {
List<String> optionXpaths = textOptionsParts.stream() List<String> optionXpaths = textOptionsParts.stream()
.map(text -> String.format("contains(text(),'%s')", text)) .map(text -> String.format("contains(text(),'%s')", text))
.collect(Collectors.toList()); .collect(Collectors.toList());
String joinedContains = String.join(" or ", optionXpaths); String joinedContains = String.join(" or ", optionXpaths);
String optionXpath = String.format("//option[%s]", joinedContains); String optionXpath = String.format("//option[%s]", joinedContains);
Waits waits = connector.waits(); Waits waits = connector.waits();
waits.waitForLazyElement(path, lookup) waits.waitForLazyElement(path, lookup)
.findElements(waits.byLocator(Lookup.XPATH, optionXpath)) .findElements(waits.byLocator(Lookup.XPATH, optionXpath))
.get(0) .get(0)
.click(); .click();
} }
public String getFirstSelectedOptionText(String path, Lookup lookup) { public String getFirstSelectedOptionText(String path, Lookup lookup) {
Select select = new Select(connector.waits().waitForLazyElement(path, lookup)); Select select = new Select(connector.waits().waitForLazyElement(path, lookup));
return select.getFirstSelectedOption().getText(); return select.getFirstSelectedOption().getText();
} }
} }

View File

@ -1,92 +1,92 @@
package cz.moneta.test.harness.connectors.web; package cz.moneta.test.harness.connectors.web;
import cz.moneta.test.harness.constants.HarnessConfigConstants; import cz.moneta.test.harness.constants.HarnessConfigConstants;
import cz.moneta.test.harness.context.StoreAccessor; import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.exception.HarnessException; import cz.moneta.test.harness.exception.HarnessException;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import org.openqa.selenium.By; import org.openqa.selenium.By;
import org.openqa.selenium.Capabilities; import org.openqa.selenium.Capabilities;
import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver; import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeOptions; import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.remote.DesiredCapabilities; import org.openqa.selenium.remote.DesiredCapabilities;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.function.Function; import java.util.function.Function;
import static cz.moneta.test.harness.constants.HarnessConfigConstants.SELENIUM_DOWNLOAD_DIRECTORY; import static cz.moneta.test.harness.constants.HarnessConfigConstants.SELENIUM_DOWNLOAD_DIRECTORY;
import static cz.moneta.test.harness.constants.HarnessConfigConstants.SELENIUM_GRID_PLATFORM; import static cz.moneta.test.harness.constants.HarnessConfigConstants.SELENIUM_GRID_PLATFORM;
public class SeleniumChromeConnector extends SeleniumWebConnector { public class SeleniumChromeConnector extends SeleniumWebConnector {
public SeleniumChromeConnector(Function<String, By> defaultLookup, StoreAccessor store, String... extraOptions) { public SeleniumChromeConnector(Function<String, By> defaultLookup, StoreAccessor store, String... extraOptions) {
super(defaultLookup, store, extraOptions); super(defaultLookup, store, extraOptions);
} }
@Override @Override
protected Pair<WebDriver, Boolean> getLocalWebDriver() { protected Pair<WebDriver, Boolean> getLocalWebDriver() {
setupDriver(); setupDriver();
return Optional.ofNullable(store.getConfig("selenium.chromebinary.path")) return Optional.ofNullable(store.getConfig("selenium.chromebinary.path"))
.map(bin -> { .map(bin -> {
ChromeOptions chromeOptions = getChromeOptions(); ChromeOptions chromeOptions = getChromeOptions();
chromeOptions.setBinary(bin); chromeOptions.setBinary(bin);
return chromeOptions; return chromeOptions;
}) })
.map(o -> Pair.<WebDriver, Boolean>of(new ChromeDriver(o), false)) .map(o -> Pair.<WebDriver, Boolean>of(new ChromeDriver(o), false))
.orElse(null); .orElse(null);
} }
private void setupDriver() { private void setupDriver() {
Optional.ofNullable(store.getConfig("selenium.chrome.webdriver.path")) Optional.ofNullable(store.getConfig("selenium.chrome.webdriver.path"))
.map(path -> { .map(path -> {
System.setProperty("webdriver.chrome.driver", path); System.setProperty("webdriver.chrome.driver", path);
return path; return path;
}) })
.orElseThrow(() -> new AssertionError("Please provide path to the Chrome driver using selenium.chrome.webdriver.path configuration key or use a selenium grid")); .orElseThrow(() -> new AssertionError("Please provide path to the Chrome driver using selenium.chrome.webdriver.path configuration key or use a selenium grid"));
} }
private ChromeOptions getChromeOptions() { private ChromeOptions getChromeOptions() {
ChromeOptions chromeOptions = new ChromeOptions(); ChromeOptions chromeOptions = new ChromeOptions();
Optional.ofNullable(store.getConfig("selenium.webdriver.chrome.options")) Optional.ofNullable(store.getConfig("selenium.webdriver.chrome.options"))
.map(o -> o.split("\\s+")) .map(o -> o.split("\\s+"))
.ifPresent(chromeOptions::addArguments); .ifPresent(chromeOptions::addArguments);
chromeOptions.addArguments(extraOptions); chromeOptions.addArguments(extraOptions);
this.driverUtils().addDefaultDesiredCapabilities(chromeOptions); this.driverUtils().addDefaultDesiredCapabilities(chromeOptions);
this.driverUtils().addAdditionalDesiredCapabilities(chromeOptions, "selenium.chrome.capabilities"); this.driverUtils().addAdditionalDesiredCapabilities(chromeOptions, "selenium.chrome.capabilities");
// Set up Selenium Grid session name // Set up Selenium Grid session name
chromeOptions.setCapability("se:name", store.get(HarnessConfigConstants.TEST_UNIQUE_ID).toString()); chromeOptions.setCapability("se:name", store.get(HarnessConfigConstants.TEST_UNIQUE_ID).toString());
// Choose which Selenium Grid node will be used // Choose which Selenium Grid node will be used
Optional.ofNullable(store.getConfig(SELENIUM_GRID_PLATFORM)) Optional.ofNullable(store.getConfig(SELENIUM_GRID_PLATFORM))
.ifPresent(platform -> chromeOptions.setPlatformName(platform)); .ifPresent(platform -> chromeOptions.setPlatformName(platform));
// Overrides default download directory in Chrome profile preferences, http://chromedriver.chromium.org/capabilities // Overrides default download directory in Chrome profile preferences, http://chromedriver.chromium.org/capabilities
// By default headless mode does not download any files unless download directory is set. // By default headless mode does not download any files unless download directory is set.
Optional.ofNullable(store.getConfig(SELENIUM_DOWNLOAD_DIRECTORY)) Optional.ofNullable(store.getConfig(SELENIUM_DOWNLOAD_DIRECTORY))
.ifPresent(experimentalOptions -> { .ifPresent(experimentalOptions -> {
Map<String, Object> prefs = new HashMap<String, Object>(); Map<String, Object> prefs = new HashMap<String, Object>();
prefs.put("download.default_directory", store.getConfig(SELENIUM_DOWNLOAD_DIRECTORY)); prefs.put("download.default_directory", store.getConfig(SELENIUM_DOWNLOAD_DIRECTORY));
chromeOptions.setExperimentalOption("prefs", prefs); chromeOptions.setExperimentalOption("prefs", prefs);
}); });
return chromeOptions; return chromeOptions;
} }
@Override @Override
protected Capabilities getDesiredRemoteCapabilities() { protected Capabilities getDesiredRemoteCapabilities() {
return new DesiredCapabilities(getChromeOptions()); return new DesiredCapabilities(getChromeOptions());
} }
@Override @Override
public void downloadFileViaIePrompt(int waitInSeconds) { public void downloadFileViaIePrompt(int waitInSeconds) {
throw new HarnessException("This method is not implemented on Chrome. Use capabilities settings instead."); throw new HarnessException("This method is not implemented on Chrome. Use capabilities settings instead.");
} }
} }

View File

@ -1,91 +1,91 @@
package cz.moneta.test.harness.connectors.web; package cz.moneta.test.harness.connectors.web;
import cz.moneta.test.harness.constants.HarnessConfigConstants; import cz.moneta.test.harness.constants.HarnessConfigConstants;
import cz.moneta.test.harness.context.StoreAccessor; import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.exception.HarnessException; import cz.moneta.test.harness.exception.HarnessException;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import org.openqa.selenium.By; import org.openqa.selenium.By;
import org.openqa.selenium.Capabilities; import org.openqa.selenium.Capabilities;
import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebDriver;
import org.openqa.selenium.edge.EdgeDriver; import org.openqa.selenium.edge.EdgeDriver;
import org.openqa.selenium.edge.EdgeOptions; import org.openqa.selenium.edge.EdgeOptions;
import org.openqa.selenium.remote.DesiredCapabilities; import org.openqa.selenium.remote.DesiredCapabilities;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.function.Function; import java.util.function.Function;
import static cz.moneta.test.harness.constants.HarnessConfigConstants.SELENIUM_DOWNLOAD_DIRECTORY; import static cz.moneta.test.harness.constants.HarnessConfigConstants.SELENIUM_DOWNLOAD_DIRECTORY;
import static cz.moneta.test.harness.constants.HarnessConfigConstants.SELENIUM_GRID_PLATFORM; import static cz.moneta.test.harness.constants.HarnessConfigConstants.SELENIUM_GRID_PLATFORM;
public class SeleniumEdgeConnector extends SeleniumWebConnector { public class SeleniumEdgeConnector extends SeleniumWebConnector {
public SeleniumEdgeConnector(Function<String, By> defaultLookup, StoreAccessor store) { public SeleniumEdgeConnector(Function<String, By> defaultLookup, StoreAccessor store) {
super(defaultLookup, store); super(defaultLookup, store);
} }
@Override @Override
protected Pair<WebDriver, Boolean> getLocalWebDriver() { protected Pair<WebDriver, Boolean> getLocalWebDriver() {
setupDriver(); setupDriver();
EdgeDriver driver = new EdgeDriver(getEdgeOptions()); EdgeDriver driver = new EdgeDriver(getEdgeOptions());
driver.manage() driver.manage()
.window() .window()
.maximize(); .maximize();
return Pair.of(driver, false); return Pair.of(driver, false);
} }
private void setupDriver() { private void setupDriver() {
Optional.ofNullable(store.getConfig("selenium.edge.webdriver.path")) Optional.ofNullable(store.getConfig("selenium.edge.webdriver.path"))
.map(path -> { .map(path -> {
System.setProperty("webdriver.edge.driver", path); System.setProperty("webdriver.edge.driver", path);
return path; return path;
}) })
.orElseThrow(() -> new AssertionError("Please provide path to the Edge driver using selenium.edge.webdriver.path configuration key or use a selenium grid")); .orElseThrow(() -> new AssertionError("Please provide path to the Edge driver using selenium.edge.webdriver.path configuration key or use a selenium grid"));
} }
private EdgeOptions getEdgeOptions() { private EdgeOptions getEdgeOptions() {
EdgeOptions edgeOptions = new EdgeOptions(); EdgeOptions edgeOptions = new EdgeOptions();
this.driverUtils().addDefaultDesiredCapabilities(edgeOptions); this.driverUtils().addDefaultDesiredCapabilities(edgeOptions);
Optional.ofNullable(store.getConfig("selenium.webdriver.edge.options")) Optional.ofNullable(store.getConfig("selenium.webdriver.edge.options"))
.map(o -> o.split("\\s+")) .map(o -> o.split("\\s+"))
.ifPresent(edgeOptions::addArguments); .ifPresent(edgeOptions::addArguments);
edgeOptions.addArguments(extraOptions); edgeOptions.addArguments(extraOptions);
this.driverUtils().addAdditionalDesiredCapabilities(edgeOptions, "selenium.edge.capabilities"); this.driverUtils().addAdditionalDesiredCapabilities(edgeOptions, "selenium.edge.capabilities");
// Set up Selenium Grid session name // Set up Selenium Grid session name
edgeOptions.setCapability("se:name", store.get(HarnessConfigConstants.TEST_UNIQUE_ID).toString()); edgeOptions.setCapability("se:name", store.get(HarnessConfigConstants.TEST_UNIQUE_ID).toString());
// Choose which Selenium Grid node will be used // Choose which Selenium Grid node will be used
Optional.ofNullable(store.getConfig(SELENIUM_GRID_PLATFORM)) Optional.ofNullable(store.getConfig(SELENIUM_GRID_PLATFORM))
.ifPresent(platform -> edgeOptions.setPlatformName(platform)); .ifPresent(platform -> edgeOptions.setPlatformName(platform));
Map<String, Object> prefs = new HashMap<String, Object>(); Map<String, Object> prefs = new HashMap<String, Object>();
// Allow clipboard usage // Allow clipboard usage
prefs.put("profile.default_content_setting_values.clipboard", 1); prefs.put("profile.default_content_setting_values.clipboard", 1);
// Overrides default download directory in Edge profile preferences // Overrides default download directory in Edge profile preferences
Optional.ofNullable(store.getConfig(SELENIUM_DOWNLOAD_DIRECTORY)) Optional.ofNullable(store.getConfig(SELENIUM_DOWNLOAD_DIRECTORY))
.ifPresent(experimentalOptions -> { .ifPresent(experimentalOptions -> {
prefs.put("download.default_directory", store.getConfig(SELENIUM_DOWNLOAD_DIRECTORY)); prefs.put("download.default_directory", store.getConfig(SELENIUM_DOWNLOAD_DIRECTORY));
}); });
edgeOptions.setExperimentalOption("prefs", prefs); edgeOptions.setExperimentalOption("prefs", prefs);
return edgeOptions; return edgeOptions;
} }
@Override @Override
protected Capabilities getDesiredRemoteCapabilities() { protected Capabilities getDesiredRemoteCapabilities() {
return new DesiredCapabilities(getEdgeOptions()); return new DesiredCapabilities(getEdgeOptions());
} }
@Override @Override
public void downloadFileViaIePrompt(int waitInSeconds) { public void downloadFileViaIePrompt(int waitInSeconds) {
throw new HarnessException("This method is not implemented on Chrome. Use capabilities settings instead."); throw new HarnessException("This method is not implemented on Chrome. Use capabilities settings instead.");
} }
} }

View File

@ -1,185 +1,185 @@
package cz.moneta.test.harness.connectors.web; package cz.moneta.test.harness.connectors.web;
import cz.moneta.test.harness.connectors.Connector; import cz.moneta.test.harness.connectors.Connector;
import cz.moneta.test.harness.context.StoreAccessor; import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.exception.HarnessException; import cz.moneta.test.harness.exception.HarnessException;
import cz.moneta.test.harness.support.web.Lookup; import cz.moneta.test.harness.support.web.Lookup;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import org.openqa.selenium.By; import org.openqa.selenium.By;
import org.openqa.selenium.Capabilities; import org.openqa.selenium.Capabilities;
import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebDriver;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Stream; import java.util.stream.Stream;
public abstract class SeleniumWebConnector implements Connector { public abstract class SeleniumWebConnector implements Connector {
private final WebDriver webDriver; private final WebDriver webDriver;
private final boolean isRemoteDriver; private final boolean isRemoteDriver;
protected final Function<String, By> defaultLookup; protected final Function<String, By> defaultLookup;
protected final StoreAccessor store; protected final StoreAccessor store;
protected final String[] extraOptions; protected final String[] extraOptions;
private String nodeHost; private String nodeHost;
public SeleniumWebConnector(Function<String, By> defaultLookup, StoreAccessor store, String[] extraOptions) { public SeleniumWebConnector(Function<String, By> defaultLookup, StoreAccessor store, String[] extraOptions) {
this.extraOptions = extraOptions; this.extraOptions = extraOptions;
this.store = store; this.store = store;
this.defaultLookup = defaultLookup; this.defaultLookup = defaultLookup;
Pair<WebDriver, Boolean> driverInfo = getPreferredWebDriver(); Pair<WebDriver, Boolean> driverInfo = getPreferredWebDriver();
isRemoteDriver = driverInfo.getRight(); isRemoteDriver = driverInfo.getRight();
WebDriver driver = driverInfo.getLeft(); WebDriver driver = driverInfo.getLeft();
if (driver == null) { if (driver == null) {
throw new HarnessException("We were not able to initialize webDriver. Please check your configs, logs and talk to your admin"); throw new HarnessException("We were not able to initialize webDriver. Please check your configs, logs and talk to your admin");
} }
//TODO fix this - it slows down test execution but some tests are build with this implicit wait //TODO fix this - it slows down test execution but some tests are build with this implicit wait
driver.manage().timeouts().implicitlyWait(2L, TimeUnit.SECONDS); driver.manage().timeouts().implicitlyWait(2L, TimeUnit.SECONDS);
driver.manage().timeouts().pageLoadTimeout(90L, TimeUnit.SECONDS); driver.manage().timeouts().pageLoadTimeout(90L, TimeUnit.SECONDS);
this.webDriver = driver; this.webDriver = driver;
} }
public SeleniumWebConnector(Function<String, By> defaultLookup, StoreAccessor store) { public SeleniumWebConnector(Function<String, By> defaultLookup, StoreAccessor store) {
this(defaultLookup, store, new String[]{}); this(defaultLookup, store, new String[]{});
} }
public Clicks clicks() { public Clicks clicks() {
return new Clicks(this); return new Clicks(this);
} }
public Waits waits() { public Waits waits() {
return new Waits(this); return new Waits(this);
} }
public Alerts alerts() { public Alerts alerts() {
return new Alerts(this); return new Alerts(this);
} }
public Types types() { public Types types() {
return new Types(this); return new Types(this);
} }
public Selects selects() { public Selects selects() {
return new Selects(this); return new Selects(this);
} }
public SendKeys sendKeys() { public SendKeys sendKeys() {
return new SendKeys(this); return new SendKeys(this);
} }
public Frames frames() { public Frames frames() {
return new Frames(this); return new Frames(this);
} }
public Windows windows() { public Windows windows() {
return new Windows(this); return new Windows(this);
} }
public Navigations navigations() { public Navigations navigations() {
return new Navigations(this); return new Navigations(this);
} }
public ElementsChecks elementsChecks() { public ElementsChecks elementsChecks() {
return new ElementsChecks(this); return new ElementsChecks(this);
} }
public Cookies cookies() { public Cookies cookies() {
return new Cookies(this); return new Cookies(this);
} }
public Scripts scripts() { public Scripts scripts() {
return new Scripts(this); return new Scripts(this);
} }
public ElementsActions elementsActions() { public ElementsActions elementsActions() {
return new ElementsActions(this); return new ElementsActions(this);
} }
public Scrolling scrolling() {return new Scrolling(this); } public Scrolling scrolling() {return new Scrolling(this); }
public DriverUtils driverUtils() { public DriverUtils driverUtils() {
return new DriverUtils(this); return new DriverUtils(this);
} }
public StoreAccessor getStore() { public StoreAccessor getStore() {
return store; return store;
} }
public boolean isRemoteDriver() { public boolean isRemoteDriver() {
return isRemoteDriver; return isRemoteDriver;
} }
public abstract void downloadFileViaIePrompt(int waitInSeconds); public abstract void downloadFileViaIePrompt(int waitInSeconds);
/** /**
* returns a pair of web driver and whether it is a remote instance (true = remote, false = local driver) * returns a pair of web driver and whether it is a remote instance (true = remote, false = local driver)
*/ */
private Pair<WebDriver, Boolean> getPreferredWebDriver() { private Pair<WebDriver, Boolean> getPreferredWebDriver() {
return Stream.<Supplier<Pair<WebDriver, Boolean>>>of(this::getDedicatedWebDriver, this::getSharedWebDriver, this::getLocalWebDriver) return Stream.<Supplier<Pair<WebDriver, Boolean>>>of(this::getDedicatedWebDriver, this::getSharedWebDriver, this::getLocalWebDriver)
.map(Supplier::get) .map(Supplier::get)
.filter(Objects::nonNull) .filter(Objects::nonNull)
.findFirst() .findFirst()
.orElseThrow(() -> new IllegalStateException("No suitable web driver could be initialized.")); .orElseThrow(() -> new IllegalStateException("No suitable web driver could be initialized."));
} }
private Pair<WebDriver, Boolean> getDedicatedWebDriver() { private Pair<WebDriver, Boolean> getDedicatedWebDriver() {
return Optional.ofNullable(store.getConfig("selenium.grid.dedicated.url")) return Optional.ofNullable(store.getConfig("selenium.grid.dedicated.url"))
.map(url -> Pair.of(driverUtils().initializeRemoteWebDriver(url), true)) .map(url -> Pair.of(driverUtils().initializeRemoteWebDriver(url), true))
.orElse(null); .orElse(null);
} }
private Pair<WebDriver, Boolean> getSharedWebDriver() { private Pair<WebDriver, Boolean> getSharedWebDriver() {
return Optional.ofNullable(store.getConfig("selenium.grid.shared.url")) return Optional.ofNullable(store.getConfig("selenium.grid.shared.url"))
.map(url -> Pair.of(driverUtils().initializeRemoteWebDriver(url), true)) .map(url -> Pair.of(driverUtils().initializeRemoteWebDriver(url), true))
.orElse(null); .orElse(null);
} }
public String getAttribute(String path, Lookup lookup, String attributeName) { public String getAttribute(String path, Lookup lookup, String attributeName) {
return waits().waitForLazyElement(path, lookup) return waits().waitForLazyElement(path, lookup)
.getAttribute(attributeName ); .getAttribute(attributeName );
} }
protected abstract Pair<WebDriver, Boolean> getLocalWebDriver(); protected abstract Pair<WebDriver, Boolean> getLocalWebDriver();
protected abstract Capabilities getDesiredRemoteCapabilities(); protected abstract Capabilities getDesiredRemoteCapabilities();
public WebDriver getDriver() { public WebDriver getDriver() {
return webDriver; return webDriver;
} }
@Override @Override
public void close() { public void close() {
webDriver.quit(); webDriver.quit();
} }
public String getCurrentUrl() { public String getCurrentUrl() {
return webDriver.getCurrentUrl(); return webDriver.getCurrentUrl();
} }
protected Function<String, By> resolveLookup(Lookup lookup) { protected Function<String, By> resolveLookup(Lookup lookup) {
switch (lookup) { switch (lookup) {
case XPATH: case XPATH:
return By::xpath; return By::xpath;
case ID: case ID:
return By::id; return By::id;
case CLASSNAME: case CLASSNAME:
return By::className; return By::className;
case NAME: case NAME:
return By::name; return By::name;
default: default:
return defaultLookup; return defaultLookup;
} }
} }
public String getNodeHost() { public String getNodeHost() {
return nodeHost; return nodeHost;
} }
public void setNodeHost(String nodeHost) { public void setNodeHost(String nodeHost) {
this.nodeHost = nodeHost; this.nodeHost = nodeHost;
} }
} }

View File

@ -1,35 +1,35 @@
package cz.moneta.test.harness.connectors.web; package cz.moneta.test.harness.connectors.web;
import cz.moneta.test.harness.support.web.Key; import cz.moneta.test.harness.support.web.Key;
import org.openqa.selenium.Keys; import org.openqa.selenium.Keys;
import org.openqa.selenium.interactions.Actions; import org.openqa.selenium.interactions.Actions;
import java.util.Arrays; import java.util.Arrays;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
public class SendKeys { public class SendKeys {
private final SeleniumWebConnector connector; private final SeleniumWebConnector connector;
public SendKeys(SeleniumWebConnector connector) { public SendKeys(SeleniumWebConnector connector) {
this.connector = connector; this.connector = connector;
} }
public void sendKeysOneAtATime(Key[] keys) { public void sendKeysOneAtATime(Key[] keys) {
Actions actions = new Actions(connector.getDriver()); Actions actions = new Actions(connector.getDriver());
Arrays.stream(keys) Arrays.stream(keys)
.forEach(k -> { .forEach(k -> {
actions.sendKeys(k).build().perform(); actions.sendKeys(k).build().perform();
try { try {
TimeUnit.MILLISECONDS.sleep(100); TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) { } catch (InterruptedException e) {
// //
} }
}); });
} }
public void sendKeysAsChord(Key[] keys) { public void sendKeysAsChord(Key[] keys) {
Actions actions = new Actions(connector.getDriver()); Actions actions = new Actions(connector.getDriver());
actions.sendKeys(Keys.chord(keys)).build().perform(); actions.sendKeys(Keys.chord(keys)).build().perform();
} }
} }

View File

@ -1,61 +1,61 @@
package cz.moneta.test.harness.connectors.web; package cz.moneta.test.harness.connectors.web;
import cz.moneta.test.harness.support.web.Lookup; import cz.moneta.test.harness.support.web.Lookup;
import cz.moneta.test.harness.support.web.TextContainer; import cz.moneta.test.harness.support.web.TextContainer;
import org.openqa.selenium.JavascriptExecutor; import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.Keys; import org.openqa.selenium.Keys;
import org.openqa.selenium.WebElement; import org.openqa.selenium.WebElement;
import org.openqa.selenium.interactions.Actions; import org.openqa.selenium.interactions.Actions;
import java.util.Optional; import java.util.Optional;
public class Types { public class Types {
private final SeleniumWebConnector connector; private final SeleniumWebConnector connector;
public Types(SeleniumWebConnector connector) { public Types(SeleniumWebConnector connector) {
this.connector = connector; this.connector = connector;
} }
public void type(TextContainer input, String text, boolean clear, Lookup lookup) { public void type(TextContainer input, String text, boolean clear, Lookup lookup) {
if (clear) { if (clear) {
clearInput(input, lookup); clearInput(input, lookup);
} }
connector.waits() connector.waits()
.waitForLazyElement(input.getPath(), lookup) .waitForLazyElement(input.getPath(), lookup)
.sendKeys(text); .sendKeys(text);
} }
public void jsType(TextContainer input, String text, boolean clear, Lookup lookup) { public void jsType(TextContainer input, String text, boolean clear, Lookup lookup) {
if (clear) { if (clear) {
clearInput(input, lookup); clearInput(input, lookup);
} }
Optional.of(connector.getDriver()) Optional.of(connector.getDriver())
.filter(JavascriptExecutor.class::isInstance) .filter(JavascriptExecutor.class::isInstance)
.map(JavascriptExecutor.class::cast) .map(JavascriptExecutor.class::cast)
.ifPresent(e -> { .ifPresent(e -> {
WebElement element = connector.waits().waitForLazyElement(input.getPath(), lookup); WebElement element = connector.waits().waitForLazyElement(input.getPath(), lookup);
if ("textarea".equals(element.getTagName())) { if ("textarea".equals(element.getTagName())) {
e.executeScript("arguments[0].value=arguments[1];", element, text); e.executeScript("arguments[0].value=arguments[1];", element, text);
} else { } else {
e.executeScript("arguments[0].setAttribute('value', arguments[1]);", element, text); e.executeScript("arguments[0].setAttribute('value', arguments[1]);", element, text);
} }
}); });
} }
private void clearInput(TextContainer input, Lookup lookup) { private void clearInput(TextContainer input, Lookup lookup) {
connector.waits() connector.waits()
.waitForLazyElement(input.getPath(), lookup) .waitForLazyElement(input.getPath(), lookup)
.sendKeys(Keys.chord(Keys.CONTROL, "a"), Keys.BACK_SPACE); .sendKeys(Keys.chord(Keys.CONTROL, "a"), Keys.BACK_SPACE);
} }
public void typeWithControlDown(String input, String keys, Lookup lookup) { public void typeWithControlDown(String input, String keys, Lookup lookup) {
new Actions(connector.getDriver()) new Actions(connector.getDriver())
.click(connector.waits().waitForLazyElement(input, lookup)) .click(connector.waits().waitForLazyElement(input, lookup))
.keyDown(Keys.CONTROL) .keyDown(Keys.CONTROL)
.sendKeys(keys) .sendKeys(keys)
.keyUp(Keys.CONTROL) .keyUp(Keys.CONTROL)
.perform(); .perform();
} }
} }

View File

@ -1,184 +1,184 @@
package cz.moneta.test.harness.connectors.web; package cz.moneta.test.harness.connectors.web;
import cz.moneta.test.harness.exception.HarnessException; import cz.moneta.test.harness.exception.HarnessException;
import cz.moneta.test.harness.support.web.Lookup; import cz.moneta.test.harness.support.web.Lookup;
import cz.moneta.test.harness.support.web.Until; import cz.moneta.test.harness.support.web.Until;
import cz.moneta.test.harness.utils.expectedconditions.BooleanExpectedConditions; import cz.moneta.test.harness.utils.expectedconditions.BooleanExpectedConditions;
import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ArrayUtils;
import org.openqa.selenium.*; import org.openqa.selenium.*;
import org.openqa.selenium.support.ui.ExpectedCondition; import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.ExpectedConditions; import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.FluentWait; import org.openqa.selenium.support.ui.FluentWait;
import org.openqa.selenium.support.ui.WebDriverWait; import org.openqa.selenium.support.ui.WebDriverWait;
import java.time.Duration; import java.time.Duration;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.function.Function; import java.util.function.Function;
public class Waits { public class Waits {
private final SeleniumWebConnector connector; private final SeleniumWebConnector connector;
private static final int LAZY_ELEMENT_RENDER_TIMEOUT = 5; private static final int LAZY_ELEMENT_RENDER_TIMEOUT = 5;
public Waits(SeleniumWebConnector connector) { public Waits(SeleniumWebConnector connector) {
this.connector = connector; this.connector = connector;
} }
public void waitForElements(int timeoutSeconds, Lookup lookup, Until until, String... elementsToCheck) { public void waitForElements(int timeoutSeconds, Lookup lookup, Until until, String... elementsToCheck) {
WebDriverWait wait = new WebDriverWait(connector.getDriver(), Duration.ofSeconds(timeoutSeconds)); WebDriverWait wait = new WebDriverWait(connector.getDriver(), Duration.ofSeconds(timeoutSeconds));
wait.ignoring(WebDriverException.class); wait.ignoring(WebDriverException.class);
Arrays.stream(elementsToCheck) Arrays.stream(elementsToCheck)
.forEach(path -> wait.until(resolveUntil(until).apply(byLocator(lookup, path)))); .forEach(path -> wait.until(resolveUntil(until).apply(byLocator(lookup, path))));
} }
public void waitForAtLeastOneElement(int timeoutSeconds, Lookup lookup, String... elementsToCheck) { public void waitForAtLeastOneElement(int timeoutSeconds, Lookup lookup, String... elementsToCheck) {
WebDriverWait wait = new WebDriverWait(connector.getDriver(), Duration.ofSeconds(timeoutSeconds)); WebDriverWait wait = new WebDriverWait(connector.getDriver(), Duration.ofSeconds(timeoutSeconds));
ExpectedCondition[] expectedConditions = Arrays.stream(elementsToCheck) ExpectedCondition[] expectedConditions = Arrays.stream(elementsToCheck)
.map(path -> .map(path ->
ExpectedConditions.visibilityOfElementLocated(byLocator(lookup, path))) ExpectedConditions.visibilityOfElementLocated(byLocator(lookup, path)))
.toArray(ExpectedCondition[]::new); .toArray(ExpectedCondition[]::new);
wait.until(ExpectedConditions.or(expectedConditions)); wait.until(ExpectedConditions.or(expectedConditions));
} }
WebElement waitForLazyElement(String path, Lookup lookup) { WebElement waitForLazyElement(String path, Lookup lookup) {
doWaitForLazyElement(path, lookup); doWaitForLazyElement(path, lookup);
return connector.getDriver() return connector.getDriver()
.findElement(byLocator(lookup, path)); .findElement(byLocator(lookup, path));
} }
List<WebElement> waitForLazyElements(String path, Lookup lookup) { List<WebElement> waitForLazyElements(String path, Lookup lookup) {
doWaitForLazyElement(path, lookup); doWaitForLazyElement(path, lookup);
return connector.getDriver() return connector.getDriver()
.findElements(byLocator(lookup, path)); .findElements(byLocator(lookup, path));
} }
private void doWaitForLazyElement(String path, Lookup lookup) { private void doWaitForLazyElement(String path, Lookup lookup) {
new FluentWait<>(connector.getDriver()) new FluentWait<>(connector.getDriver())
.withTimeout(Duration.ofSeconds(LAZY_ELEMENT_RENDER_TIMEOUT)) .withTimeout(Duration.ofSeconds(LAZY_ELEMENT_RENDER_TIMEOUT))
.pollingEvery(Duration.ofMillis(500)) .pollingEvery(Duration.ofMillis(500))
.ignoring(NoSuchElementException.class) .ignoring(NoSuchElementException.class)
.ignoring(StaleElementReferenceException.class) .ignoring(StaleElementReferenceException.class)
.withMessage("Element/s not found within " + LAZY_ELEMENT_RENDER_TIMEOUT .withMessage("Element/s not found within " + LAZY_ELEMENT_RENDER_TIMEOUT
+ " seconds timeout. If this error occurs, you might want to consider using explicit @Wait annotation. " + path) + " seconds timeout. If this error occurs, you might want to consider using explicit @Wait annotation. " + path)
.until(driver -> !driver.findElements(byLocator(lookup, path)).isEmpty()); .until(driver -> !driver.findElements(byLocator(lookup, path)).isEmpty());
} }
public void waitForElementAndRefresh(String waitElementPath, String refreshElementPath, Lookup waitElementLookup, public void waitForElementAndRefresh(String waitElementPath, String refreshElementPath, Lookup waitElementLookup,
Lookup refreshElementLookup, int timeoutSeconds, int pollingEverySeconds) { Lookup refreshElementLookup, int timeoutSeconds, int pollingEverySeconds) {
Runnable refreshFunction = () -> connector.clicks().click(() -> refreshElementPath, refreshElementLookup); Runnable refreshFunction = () -> connector.clicks().click(() -> refreshElementPath, refreshElementLookup);
waitForElementAndRefresh(waitElementPath, waitElementLookup, refreshFunction, timeoutSeconds, waitForElementAndRefresh(waitElementPath, waitElementLookup, refreshFunction, timeoutSeconds,
pollingEverySeconds, true); pollingEverySeconds, true);
// waitForLazyElement(refreshElementPath, refreshElementLookup).click(); // waitForLazyElement(refreshElementPath, refreshElementLookup).click();
} }
public void waitForElementAndRefresh(String waitElementPath, Lookup waitElementLookup, Runnable refreshFunction, public void waitForElementAndRefresh(String waitElementPath, Lookup waitElementLookup, Runnable refreshFunction,
int timeoutSeconds, int pollingEverySeconds, boolean shouldBeWaitElementVisible) { int timeoutSeconds, int pollingEverySeconds, boolean shouldBeWaitElementVisible) {
new FluentWait<>(connector.getDriver()) new FluentWait<>(connector.getDriver())
.withTimeout(Duration.ofSeconds(timeoutSeconds)) .withTimeout(Duration.ofSeconds(timeoutSeconds))
.pollingEvery(Duration.ofSeconds(pollingEverySeconds)) .pollingEvery(Duration.ofSeconds(pollingEverySeconds))
.ignoring(NoSuchElementException.class) .ignoring(NoSuchElementException.class)
.ignoring(StaleElementReferenceException.class) .ignoring(StaleElementReferenceException.class)
.ignoring(TimeoutException.class) .ignoring(TimeoutException.class)
.withMessage("Element/s not present within " + timeoutSeconds + " seconds timeout. Wait and refreshed " + .withMessage("Element/s not present within " + timeoutSeconds + " seconds timeout. Wait and refreshed " +
"every: " + pollingEverySeconds + " seconds.\n For element: " + waitElementPath) "every: " + pollingEverySeconds + " seconds.\n For element: " + waitElementPath)
.until(driver -> { .until(driver -> {
if (shouldBeWaitElementVisible == true) { if (shouldBeWaitElementVisible == true) {
return checkIfElementPresentAndRefresh(waitElementPath, waitElementLookup, refreshFunction, driver); return checkIfElementPresentAndRefresh(waitElementPath, waitElementLookup, refreshFunction, driver);
} else { } else {
return checkIfElementDisappearAndRefresh(waitElementPath, waitElementLookup, refreshFunction, driver); return checkIfElementDisappearAndRefresh(waitElementPath, waitElementLookup, refreshFunction, driver);
} }
}); });
} }
private WebElement checkIfElementPresentAndRefresh(String waitElementPath, Lookup waitElementLookup, Runnable refreshFunction, WebDriver driver) { private WebElement checkIfElementPresentAndRefresh(String waitElementPath, Lookup waitElementLookup, Runnable refreshFunction, WebDriver driver) {
Optional.of(driver.findElements(byLocator(waitElementLookup, waitElementPath))) Optional.of(driver.findElements(byLocator(waitElementLookup, waitElementPath)))
.filter(Collection::isEmpty) .filter(Collection::isEmpty)
.ifPresent(col -> refreshFunction.run()); .ifPresent(col -> refreshFunction.run());
return driver.findElement(byLocator(waitElementLookup, waitElementPath)); return driver.findElement(byLocator(waitElementLookup, waitElementPath));
} }
private boolean checkIfElementDisappearAndRefresh(String waitElementPath, Lookup waitElementLookup, Runnable refreshFunction, WebDriver driver) { private boolean checkIfElementDisappearAndRefresh(String waitElementPath, Lookup waitElementLookup, Runnable refreshFunction, WebDriver driver) {
Optional.of(driver.findElements(byLocator(waitElementLookup, waitElementPath))) Optional.of(driver.findElements(byLocator(waitElementLookup, waitElementPath)))
.ifPresent(col -> refreshFunction.run()); .ifPresent(col -> refreshFunction.run());
return driver.findElements(byLocator(waitElementLookup, waitElementPath)).size() == 0; return driver.findElements(byLocator(waitElementLookup, waitElementPath)).size() == 0;
} }
public void waitOrFailOnErrorElement(int timeoutSeconds, String pathForWaiting, String pathToFail, Lookup lookup) { public void waitOrFailOnErrorElement(int timeoutSeconds, String pathForWaiting, String pathToFail, Lookup lookup) {
WebDriverWait wait = new WebDriverWait(connector.getDriver(), Duration.ofSeconds(timeoutSeconds)); WebDriverWait wait = new WebDriverWait(connector.getDriver(), Duration.ofSeconds(timeoutSeconds));
wait.withTimeout(Duration.ofSeconds(timeoutSeconds)) wait.withTimeout(Duration.ofSeconds(timeoutSeconds))
.pollingEvery(Duration.ofSeconds(3)) .pollingEvery(Duration.ofSeconds(3))
.withMessage("Element " + pathForWaiting + " or " + pathToFail + " isn't present after " + timeoutSeconds + " seconds") .withMessage("Element " + pathForWaiting + " or " + pathToFail + " isn't present after " + timeoutSeconds + " seconds")
.until(ExpectedConditions.or( .until(ExpectedConditions.or(
visibilityOfElementLocated(lookup, pathForWaiting), visibilityOfElementLocated(lookup, pathForWaiting),
visibilityOfElementLocated(lookup, pathToFail))); visibilityOfElementLocated(lookup, pathToFail)));
if (connector.elementsChecks().isElementPresent(pathToFail, lookup)) { if (connector.elementsChecks().isElementPresent(pathToFail, lookup)) {
throw new HarnessException(pathToFail + " is present instead of " + pathForWaiting); throw new HarnessException(pathToFail + " is present instead of " + pathForWaiting);
} }
} }
/** /**
* Method for wait if all elements passed as parameter in defined state (Visible, Gone, Present in DOM) and there is not * Method for wait if all elements passed as parameter in defined state (Visible, Gone, Present in DOM) and there is not
* visible fail element. Fail element is for example error screen. * visible fail element. Fail element is for example error screen.
* <p> * <p>
* Wait is implemented as fluent wait with 500ms polling and all parameters are transformed into expected conditions. * Wait is implemented as fluent wait with 500ms polling and all parameters are transformed into expected conditions.
* These conditions are used in fluent wait. * These conditions are used in fluent wait.
* </p> * </p>
* <p> * <p>
* In case of fail element visible is thrown FailElementDisplayedException. * In case of fail element visible is thrown FailElementDisplayedException.
* </p> * </p>
* *
* @param timeoutSeconds - wait timeout * @param timeoutSeconds - wait timeout
* @param lookup - define locator strategy * @param lookup - define locator strategy
* @param until - state to check - Visible, Gone, Present in DOM * @param until - state to check - Visible, Gone, Present in DOM
* @param xpathToFail - xpath for fail element/error screen * @param xpathToFail - xpath for fail element/error screen
* @param elementsToCheck - elements to be wait for * @param elementsToCheck - elements to be wait for
*/ */
public void waitOrFailOnErrorXpath(int timeoutSeconds, Lookup lookup, Until until, String xpathToFail, String... elementsToCheck) { public void waitOrFailOnErrorXpath(int timeoutSeconds, Lookup lookup, Until until, String xpathToFail, String... elementsToCheck) {
ExpectedCondition[] conditionsToCheck = Arrays.stream(elementsToCheck) ExpectedCondition[] conditionsToCheck = Arrays.stream(elementsToCheck)
.map(elementToCheck -> resolveUntil(until).apply(byLocator(lookup, elementToCheck))) .map(elementToCheck -> resolveUntil(until).apply(byLocator(lookup, elementToCheck)))
.toArray(ExpectedCondition[]::new); .toArray(ExpectedCondition[]::new);
ExpectedCondition[] failElementNotDisplayed = new ExpectedCondition[]{BooleanExpectedConditions.failElementNotDisplayed(byLocator(Lookup.XPATH, xpathToFail))}; ExpectedCondition[] failElementNotDisplayed = new ExpectedCondition[]{BooleanExpectedConditions.failElementNotDisplayed(byLocator(Lookup.XPATH, xpathToFail))};
ExpectedCondition[] conditionsToEvaluate = ArrayUtils.addAll(failElementNotDisplayed, conditionsToCheck); ExpectedCondition[] conditionsToEvaluate = ArrayUtils.addAll(failElementNotDisplayed, conditionsToCheck);
WebDriverWait wait = new WebDriverWait(connector.getDriver(), Duration.ofSeconds(timeoutSeconds)); WebDriverWait wait = new WebDriverWait(connector.getDriver(), Duration.ofSeconds(timeoutSeconds));
wait.withTimeout(Duration.ofSeconds(timeoutSeconds)) wait.withTimeout(Duration.ofSeconds(timeoutSeconds))
.pollingEvery(Duration.ofMillis(500L)) .pollingEvery(Duration.ofMillis(500L))
.withMessage(getErrorElementsMessage(timeoutSeconds, elementsToCheck)) .withMessage(getErrorElementsMessage(timeoutSeconds, elementsToCheck))
.until(ExpectedConditions.and(conditionsToEvaluate)); .until(ExpectedConditions.and(conditionsToEvaluate));
} }
private String getErrorElementsMessage(int timeoutSeconds, String... elements) { private String getErrorElementsMessage(int timeoutSeconds, String... elements) {
String elementsInString = String.join(",", elements); String elementsInString = String.join(",", elements);
return "Some of listed elements isn't present after " + timeoutSeconds + " seconds. Elements: " + elementsInString; return "Some of listed elements isn't present after " + timeoutSeconds + " seconds. Elements: " + elementsInString;
} }
public By byLocator(Lookup lookup, String path) { public By byLocator(Lookup lookup, String path) {
return connector.resolveLookup(lookup).apply(path); return connector.resolveLookup(lookup).apply(path);
} }
private ExpectedCondition<WebElement> visibilityOfElementLocated(Lookup lookup, String path) { private ExpectedCondition<WebElement> visibilityOfElementLocated(Lookup lookup, String path) {
return ExpectedConditions.visibilityOfElementLocated(byLocator(lookup, path)); return ExpectedConditions.visibilityOfElementLocated(byLocator(lookup, path));
} }
private Function<By, ExpectedCondition<?>> resolveUntil(Until until) { private Function<By, ExpectedCondition<?>> resolveUntil(Until until) {
switch (until) { switch (until) {
case VISIBLE: case VISIBLE:
return ExpectedConditions::visibilityOfElementLocated; return ExpectedConditions::visibilityOfElementLocated;
case PRESENT_IN_DOM: case PRESENT_IN_DOM:
return ExpectedConditions::presenceOfElementLocated; return ExpectedConditions::presenceOfElementLocated;
case GONE: case GONE:
return ExpectedConditions::invisibilityOfElementLocated; return ExpectedConditions::invisibilityOfElementLocated;
case CLICKABLE: case CLICKABLE:
return ExpectedConditions::elementToBeClickable; return ExpectedConditions::elementToBeClickable;
default: default:
return ExpectedConditions::presenceOfElementLocated; return ExpectedConditions::presenceOfElementLocated;
} }
} }
} }

View File

@ -1,65 +1,65 @@
package cz.moneta.test.harness.connectors.web; package cz.moneta.test.harness.connectors.web;
import cz.moneta.test.harness.exception.HarnessException; import cz.moneta.test.harness.exception.HarnessException;
import org.openqa.selenium.Dimension; import org.openqa.selenium.Dimension;
import org.openqa.selenium.NoSuchWindowException; import org.openqa.selenium.NoSuchWindowException;
import org.openqa.selenium.WebDriver; import org.openqa.selenium.WebDriver;
import org.openqa.selenium.support.ui.ExpectedConditions; import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait; import org.openqa.selenium.support.ui.WebDriverWait;
import java.time.Duration; import java.time.Duration;
import java.util.Optional; import java.util.Optional;
public class Windows { public class Windows {
private final SeleniumWebConnector connector; private final SeleniumWebConnector connector;
public Windows(SeleniumWebConnector connector) { public Windows(SeleniumWebConnector connector) {
this.connector = connector; this.connector = connector;
} }
public void switchToOtherWindow(int timeout, int expectedNumberOfWindows) { public void switchToOtherWindow(int timeout, int expectedNumberOfWindows) {
WebDriverWait wait = new WebDriverWait(connector.getDriver(), Duration.ofSeconds(timeout)); WebDriverWait wait = new WebDriverWait(connector.getDriver(), Duration.ofSeconds(timeout));
wait.until(ExpectedConditions.numberOfWindowsToBe(expectedNumberOfWindows)); wait.until(ExpectedConditions.numberOfWindowsToBe(expectedNumberOfWindows));
connector.getDriver() connector.getDriver()
.getWindowHandles() .getWindowHandles()
.stream() .stream()
.filter(w -> getCurrentWindow().map(cw -> !cw.equals(w)).orElse(true)) .filter(w -> getCurrentWindow().map(cw -> !cw.equals(w)).orElse(true))
.findFirst() .findFirst()
.map(otherWindow -> connector.getDriver().switchTo().window(otherWindow)) .map(otherWindow -> connector.getDriver().switchTo().window(otherWindow))
.orElseThrow(() -> new HarnessException( .orElseThrow(() -> new HarnessException(
"Not possible to switch to other window. Only current window is present.")); "Not possible to switch to other window. Only current window is present."));
} }
public void switchToWindowByTitle(String title) { public void switchToWindowByTitle(String title) {
for (String winHandle : connector.getDriver().getWindowHandles()) { for (String winHandle : connector.getDriver().getWindowHandles()) {
if (connector.getDriver().switchTo().window(winHandle).getTitle().equals(title)) { if (connector.getDriver().switchTo().window(winHandle).getTitle().equals(title)) {
return; return;
} }
} }
throw new HarnessException("A browser window named \"" + title + "\" was not found"); throw new HarnessException("A browser window named \"" + title + "\" was not found");
} }
public void switchWindowResolution(int width, int height) { public void switchWindowResolution(int width, int height) {
WebDriver.Window window = connector.getDriver().manage().window(); WebDriver.Window window = connector.getDriver().manage().window();
Dimension targetSize = new Dimension(width, height); Dimension targetSize = new Dimension(width, height);
window.setSize(targetSize); window.setSize(targetSize);
if (!targetSize.equals(window.getSize())) { if (!targetSize.equals(window.getSize())) {
throw new IllegalStateException(String.format("Failed to switch window size to %d %d", width, height)); throw new IllegalStateException(String.format("Failed to switch window size to %d %d", width, height));
} }
} }
private Optional<String> getCurrentWindow() { private Optional<String> getCurrentWindow() {
try { try {
return Optional.of(connector return Optional.of(connector
.getDriver() .getDriver()
.getWindowHandle()); .getWindowHandle());
} catch (NoSuchWindowException e) { } catch (NoSuchWindowException e) {
return Optional.empty(); return Optional.empty();
} }
} }
public void closeCurrentWindow() { public void closeCurrentWindow() {
connector.getDriver().close(); connector.getDriver().close();
} }
} }

View File

@ -1,78 +1,78 @@
package cz.moneta.test.harness.connectors.wso2; package cz.moneta.test.harness.connectors.wso2;
import cz.moneta.test.harness.connectors.rest.BaseRestConnector; import cz.moneta.test.harness.connectors.rest.BaseRestConnector;
import cz.moneta.test.harness.connectors.rest.RemoteRestCallRequest; import cz.moneta.test.harness.connectors.rest.RemoteRestCallRequest;
import cz.moneta.test.harness.connectors.rest.ResponseHandler; import cz.moneta.test.harness.connectors.rest.ResponseHandler;
import cz.moneta.test.harness.connectors.rest.RestConnector; import cz.moneta.test.harness.connectors.rest.RestConnector;
import cz.moneta.test.harness.context.ConfigAccessor; import cz.moneta.test.harness.context.ConfigAccessor;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import jakarta.ws.rs.client.Client; import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.client.Entity; import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.client.Invocation; import jakarta.ws.rs.client.Invocation;
import jakarta.ws.rs.client.WebTarget; import jakarta.ws.rs.client.WebTarget;
import jakarta.ws.rs.core.GenericType; import jakarta.ws.rs.core.GenericType;
import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.function.Function; import java.util.function.Function;
@Deprecated @Deprecated
public class RemoteWso2Connector extends BaseRestConnector implements RestConnector { public class RemoteWso2Connector extends BaseRestConnector implements RestConnector {
private final WebTarget target; private final WebTarget target;
public RemoteWso2Connector(ConfigAccessor store) { public RemoteWso2Connector(ConfigAccessor store) {
try { try {
Client client = createHttpClient("RemoteWso2Connector"); Client client = createHttpClient("RemoteWso2Connector");
this.target = Optional.ofNullable(store.getConfig("selenium.grid.shared.url")) this.target = Optional.ofNullable(store.getConfig("selenium.grid.shared.url"))
.map(client::target) .map(client::target)
.map(t -> t.path("grid/admin")) .map(t -> t.path("grid/admin"))
.map(t -> t.path(Wso2ConnectorServlet.class.getSimpleName())) .map(t -> t.path(Wso2ConnectorServlet.class.getSimpleName()))
.orElseThrow(() -> new IllegalStateException("Failed to initialize remote Wso2 connector.")); .orElseThrow(() -> new IllegalStateException("Failed to initialize remote Wso2 connector."));
} catch (Exception e) { } catch (Exception e) {
throw new IllegalStateException("Failed to initialize remote Wso2 connector.", e); throw new IllegalStateException("Failed to initialize remote Wso2 connector.", e);
} }
} }
@Override @Override
public <T> Pair<Integer, T> get(String path, Map<String, Object> properties, GenericType<T> responseType, public <T> Pair<Integer, T> get(String path, Map<String, Object> properties, GenericType<T> responseType,
Map<String, Object> headers) { Map<String, Object> headers) {
RemoteRestCallRequest get = new RemoteRestCallRequest(path, properties, headers); RemoteRestCallRequest get = new RemoteRestCallRequest(path, properties, headers);
return sendAndReceive("call-get", get, responseType); return sendAndReceive("call-get", get, responseType);
} }
@Override @Override
public <T> Pair<Integer, T> post(String path, Entity<?> request, GenericType<T> responseType, Map<String, Object> headers) { public <T> Pair<Integer, T> post(String path, Entity<?> request, GenericType<T> responseType, Map<String, Object> headers) {
RemoteRestCallRequest post = new RemoteRestCallRequest(path, request, headers); RemoteRestCallRequest post = new RemoteRestCallRequest(path, request, headers);
return sendAndReceive("call-post", post, responseType); return sendAndReceive("call-post", post, responseType);
} }
@Override @Override
public <T> Pair<Integer, T> delete(String path, Map<String, Object> properties, GenericType<T> responseType, public <T> Pair<Integer, T> delete(String path, Map<String, Object> properties, GenericType<T> responseType,
Map<String, Object> headers) { Map<String, Object> headers) {
RemoteRestCallRequest delete = new RemoteRestCallRequest(path, properties, headers); RemoteRestCallRequest delete = new RemoteRestCallRequest(path, properties, headers);
return sendAndReceive("call-delete", delete, responseType); return sendAndReceive("call-delete", delete, responseType);
} }
@Override @Override
public <T> Pair<Integer, T> patch(String path, Entity<?> request, GenericType<T> responseType, Map<String, Object> headers) { public <T> Pair<Integer, T> patch(String path, Entity<?> request, GenericType<T> responseType, Map<String, Object> headers) {
RemoteRestCallRequest patch = new RemoteRestCallRequest(path, request, headers); RemoteRestCallRequest patch = new RemoteRestCallRequest(path, request, headers);
return sendAndReceive("call-patch", patch, responseType); return sendAndReceive("call-patch", patch, responseType);
} }
@Override @Override
public RestConnector registerResponseHandler( public RestConnector registerResponseHandler(
ResponseHandler<Pair<Invocation.Builder, Function<Invocation.Builder, Response>>, Response> responseHandler) { ResponseHandler<Pair<Invocation.Builder, Function<Invocation.Builder, Response>>, Response> responseHandler) {
throw new UnsupportedOperationException("RemoteWso2Connector#registerResponseHandler not implemented"); throw new UnsupportedOperationException("RemoteWso2Connector#registerResponseHandler not implemented");
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private <T> Pair<Integer, T> sendAndReceive(String method, RemoteRestCallRequest request, GenericType<T> responseType) { private <T> Pair<Integer, T> sendAndReceive(String method, RemoteRestCallRequest request, GenericType<T> responseType) {
Response response = target.path(method) Response response = target.path(method)
.request() .request()
.post(Entity.json(request)); .post(Entity.json(request));
return Pair.of(response.getStatus(), response.readEntity(responseType)); return Pair.of(response.getStatus(), response.readEntity(responseType));
} }
} }

View File

@ -1,33 +1,33 @@
package cz.moneta.test.harness.connectors.wso2; package cz.moneta.test.harness.connectors.wso2;
import cz.moneta.test.harness.connectors.rest.ResponseHandler; import cz.moneta.test.harness.connectors.rest.ResponseHandler;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import org.apache.http.HttpStatus; import org.apache.http.HttpStatus;
import jakarta.ws.rs.client.Invocation; import jakarta.ws.rs.client.Invocation;
import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
public class TokenRenewalResponseHandler implements ResponseHandler<Pair<Invocation.Builder, Function<Invocation.Builder, Response>>, Response> { public class TokenRenewalResponseHandler implements ResponseHandler<Pair<Invocation.Builder, Function<Invocation.Builder, Response>>, Response> {
private final Supplier<String> tokenSupplier; private final Supplier<String> tokenSupplier;
public TokenRenewalResponseHandler(Supplier<String> tokenSupplier) { public TokenRenewalResponseHandler(Supplier<String> tokenSupplier) {
this.tokenSupplier = tokenSupplier; this.tokenSupplier = tokenSupplier;
} }
/** /**
* Checks whether the HTTP response status is 401 UNAUTHORIZED in which case it resends the original request with * Checks whether the HTTP response status is 401 UNAUTHORIZED in which case it resends the original request with
* a renewed Authorization Bearer token * a renewed Authorization Bearer token
*/ */
@Override @Override
public Response handle(Pair<Invocation.Builder, Function<Invocation.Builder, Response>> invocation, Response response) { public Response handle(Pair<Invocation.Builder, Function<Invocation.Builder, Response>> invocation, Response response) {
if (response.getStatus() == HttpStatus.SC_UNAUTHORIZED) { if (response.getStatus() == HttpStatus.SC_UNAUTHORIZED) {
Invocation.Builder builder = invocation.getLeft().header("Authorization", "Bearer " + tokenSupplier.get()); Invocation.Builder builder = invocation.getLeft().header("Authorization", "Bearer " + tokenSupplier.get());
return invocation.getRight().apply(builder); return invocation.getRight().apply(builder);
} else { } else {
return response; return response;
} }
} }
} }

View File

@ -1,95 +1,95 @@
package cz.moneta.test.harness.connectors.wso2; package cz.moneta.test.harness.connectors.wso2;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
import cz.moneta.test.harness.connectors.common.ServletConfigAccessor; import cz.moneta.test.harness.connectors.common.ServletConfigAccessor;
import cz.moneta.test.harness.connectors.rest.RemoteRestCallRequest; import cz.moneta.test.harness.connectors.rest.RemoteRestCallRequest;
import cz.moneta.test.harness.connectors.rest.SimpleRestConnector; import cz.moneta.test.harness.connectors.rest.SimpleRestConnector;
import cz.moneta.test.harness.context.ConfigAccessor; import cz.moneta.test.harness.context.ConfigAccessor;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import jakarta.ws.rs.client.Entity; import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.GenericType; import jakarta.ws.rs.core.GenericType;
import java.io.IOException; import java.io.IOException;
import java.util.Optional; import java.util.Optional;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Stream; import java.util.stream.Stream;
public class Wso2ConnectorServlet extends HttpServlet { public class Wso2ConnectorServlet extends HttpServlet {
private static final ObjectMapper JACKSON = new ObjectMapper() private static final ObjectMapper JACKSON = new ObjectMapper()
.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false) .configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
.registerModules(new ParameterNamesModule(), new Jdk8Module(), new JavaTimeModule()); .registerModules(new ParameterNamesModule(), new Jdk8Module(), new JavaTimeModule());
private final ConfigAccessor store; private final ConfigAccessor store;
private final SimpleRestConnector connector; private final SimpleRestConnector connector;
public Wso2ConnectorServlet() { public Wso2ConnectorServlet() {
this.store = new ServletConfigAccessor(); this.store = new ServletConfigAccessor();
this.connector = new SimpleRestConnector( this.connector = new SimpleRestConnector(
store.getConfig("endpoints.wso2.url"), "Wso2ConnectorServlet"); store.getConfig("endpoints.wso2.url"), "Wso2ConnectorServlet");
} }
@Override @Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) { protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
Optional.ofNullable(req.getPathInfo()) Optional.ofNullable(req.getPathInfo())
.flatMap(pi -> Stream.<Supplier<Optional<Supplier<Pair<Integer, String>>>>>of( .flatMap(pi -> Stream.<Supplier<Optional<Supplier<Pair<Integer, String>>>>>of(
() -> Optional.of(pi).filter(i -> i.contains("/call-get")).map(i -> () -> callGet(req)), () -> Optional.of(pi).filter(i -> i.contains("/call-get")).map(i -> () -> callGet(req)),
() -> Optional.of(pi).filter(i -> i.contains("/call-post")).map(i -> () -> callPost(req))) () -> Optional.of(pi).filter(i -> i.contains("/call-post")).map(i -> () -> callPost(req)))
.map(Supplier::get) .map(Supplier::get)
.filter(Optional::isPresent) .filter(Optional::isPresent)
.map(Optional::get) .map(Optional::get)
.findFirst()) .findFirst())
.map(Supplier::get) .map(Supplier::get)
.ifPresent(r -> mapOntoResponse(r, resp)); .ifPresent(r -> mapOntoResponse(r, resp));
} }
private void mapOntoResponse(Pair<Integer, String> result, HttpServletResponse resp) { private void mapOntoResponse(Pair<Integer, String> result, HttpServletResponse resp) {
try { try {
resp.setContentType("application/json"); resp.setContentType("application/json");
resp.setStatus(result.getLeft()); resp.setStatus(result.getLeft());
resp.getWriter().write(result.getRight()); resp.getWriter().write(result.getRight());
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); //TODO logging e.printStackTrace(); //TODO logging
} }
} }
private Pair<Integer, String> callGet(HttpServletRequest req) { private Pair<Integer, String> callGet(HttpServletRequest req) {
try { try {
RemoteRestCallRequest request = JACKSON.readValue(req.getInputStream(), RemoteRestCallRequest.class); RemoteRestCallRequest request = JACKSON.readValue(req.getInputStream(), RemoteRestCallRequest.class);
return connector.get( return connector.get(
request.getPath(), request.getPath(),
request.getGetProperties(), request.getGetProperties(),
new GenericType<>(String.class), new GenericType<>(String.class),
request.getHeaders()); request.getHeaders());
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); //TODO logging e.printStackTrace(); //TODO logging
return null; return null;
} }
} }
private Pair<Integer, String> callPost(HttpServletRequest req) { private Pair<Integer, String> callPost(HttpServletRequest req) {
try { try {
RemoteRestCallRequest request = JACKSON.readValue(req.getInputStream(), RemoteRestCallRequest.class); RemoteRestCallRequest request = JACKSON.readValue(req.getInputStream(), RemoteRestCallRequest.class);
return connector.post( return connector.post(
request.getPath(), request.getPath(),
Entity.json(request.getRequest()), Entity.json(request.getRequest()),
new GenericType<>(String.class), new GenericType<>(String.class),
request.getHeaders()); request.getHeaders());
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); //TODO logging e.printStackTrace(); //TODO logging
return null; return null;
} }
} }
@Override @Override
public void destroy() { public void destroy() {
connector.close(); connector.close();
} }
} }

View File

@ -1,21 +1,22 @@
package cz.moneta.test.harness.constants; package cz.moneta.test.harness.constants;
public final class HarnessConfigConstants { public final class HarnessConfigConstants {
public static final String TEST_UNIQUE_ID = "TEST_UNIQUE_ID"; public static final String TEST_UNIQUE_ID = "TEST_UNIQUE_ID";
public static final String TEST_SHORT_ID = "TEST_SHORT_ID"; public static final String TEST_SHORT_ID = "TEST_SHORT_ID";
public static final String VAULT_USERNAME_CONFIG = "vault.username"; public static final String VAULT_USERNAME_CONFIG = "vault.username";
public static final String VAULT_PASSWORD_CONFIG = "vault.password"; public static final String VAULT_PASSWORD_CONFIG = "vault.password";
public static final String VAULT_URL_CONFIG = "vault.url"; public static final String VAULT_URL_CONFIG = "vault.url";
public static final String VAULT_WSO2_KEYS_PATH = "vault.client.secrets.path"; public static final String VAULT_KAFKA_KEYS_CONFIG = "vault.kafka.secrets.path";
public static final String VAULT_CAGW_KEYS_PATH = "vault.cagw.client.secrets.path"; public static final String VAULT_WSO2_KEYS_PATH = "vault.client.secrets.path";
public static final String ENVIRONMENT_TYPE = "environment.type"; public static final String VAULT_CAGW_KEYS_PATH = "vault.cagw.client.secrets.path";
public static final String JENKINS_BUILD_URL = "jenkins.build.url"; public static final String ENVIRONMENT_TYPE = "environment.type";
public static final String SELENIUM_DOWNLOAD_DIRECTORY = "selenium.download.directory"; public static final String JENKINS_BUILD_URL = "jenkins.build.url";
public static final String SELENIUM_GRID_PLATFORM = "selenium.grid.platform"; public static final String SELENIUM_DOWNLOAD_DIRECTORY = "selenium.download.directory";
public static final String SELENIUM_GRID_PLATFORM = "selenium.grid.platform";
public static final long DEFAULT_REST_READ_TIMEOUT = 10L;
public static final long DEFAULT_REST_READ_TIMEOUT = 10L;
private HarnessConfigConstants() {
} private HarnessConfigConstants() {
} }
}

View File

@ -1,204 +1,204 @@
package cz.moneta.test.harness.context; package cz.moneta.test.harness.context;
import cz.moneta.test.harness.annotations.TestContext; import cz.moneta.test.harness.annotations.TestContext;
import cz.moneta.test.harness.endpoints.Endpoint; import cz.moneta.test.harness.endpoints.Endpoint;
import cz.moneta.test.harness.exception.HarnessConfigurationException; import cz.moneta.test.harness.exception.HarnessConfigurationException;
import cz.moneta.test.harness.exception.HarnessException; import cz.moneta.test.harness.exception.HarnessException;
import cz.moneta.test.harness.support.data.Generator; import cz.moneta.test.harness.support.data.Generator;
import cz.moneta.test.harness.support.data.GeneratorType; import cz.moneta.test.harness.support.data.GeneratorType;
import cz.moneta.test.harness.support.util.Level; import cz.moneta.test.harness.support.util.Level;
import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.junit.jupiter.api.extension.ExtensionContext.Store; import org.junit.jupiter.api.extension.ExtensionContext.Store;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import static cz.moneta.test.harness.HarnessJunit5Extension.ACTIVE_ENDPOINTS; import static cz.moneta.test.harness.HarnessJunit5Extension.ACTIVE_ENDPOINTS;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@TestContext @TestContext
public abstract class BaseStoreAccessor implements StoreAccessor { public abstract class BaseStoreAccessor implements StoreAccessor {
private static final Logger LOG = LogManager.getLogger("Harness"); private static final Logger LOG = LogManager.getLogger("Harness");
private final Store rootStore; private final Store rootStore;
private final Store globalStore; private final Store globalStore;
private final Store endpointStore; private final Store endpointStore;
private final Store configStore; private final Store configStore;
private final Store generatorsStore; private final Store generatorsStore;
public BaseStoreAccessor(Store rootStore, Store globalStore, Store endpointStore, Store configStore, Store generatorsStore) { public BaseStoreAccessor(Store rootStore, Store globalStore, Store endpointStore, Store configStore, Store generatorsStore) {
this.rootStore = rootStore; this.rootStore = rootStore;
this.globalStore = globalStore; this.globalStore = globalStore;
this.endpointStore = endpointStore; this.endpointStore = endpointStore;
this.configStore = configStore; this.configStore = configStore;
this.generatorsStore = generatorsStore; this.generatorsStore = generatorsStore;
} }
@Override @Override
public <E extends Endpoint> E getEndpoint(Class<E> endpointClass, Object... args) { public <E extends Endpoint> E getEndpoint(Class<E> endpointClass, Object... args) {
Set<Pair<Class<E>, List<Object>>> activeEndpoints = endpointStore.get(ACTIVE_ENDPOINTS, Set.class); Set<Pair<Class<E>, List<Object>>> activeEndpoints = endpointStore.get(ACTIVE_ENDPOINTS, Set.class);
return endpointStore.getOrComputeIfAbsent( return endpointStore.getOrComputeIfAbsent(
Pair.of(endpointClass, Arrays.asList(args)), Pair.of(endpointClass, Arrays.asList(args)),
c -> { c -> {
try { try {
Class[] paramTypes = Stream.concat( Class[] paramTypes = Stream.concat(
Stream.of(StoreAccessor.class), Stream.of(StoreAccessor.class),
Arrays.stream(args).map(Object::getClass)).toArray(Class[]::new); Arrays.stream(args).map(Object::getClass)).toArray(Class[]::new);
Constructor<E> constructor = endpointClass.getConstructor(paramTypes); Constructor<E> constructor = endpointClass.getConstructor(paramTypes);
return constructor.newInstance(ArrayUtils.insert(0, args, this)); return constructor.newInstance(ArrayUtils.insert(0, args, this));
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
throw Stream.of(Optional.ofNullable(e.getCause()), Optional.of(e)) throw Stream.of(Optional.ofNullable(e.getCause()), Optional.of(e))
.filter(Optional::isPresent) .filter(Optional::isPresent)
.map(Optional::get) .map(Optional::get)
.findFirst() .findFirst()
.map(t -> new IllegalStateException("Endpoint " + endpointClass.getSimpleName() + .map(t -> new IllegalStateException("Endpoint " + endpointClass.getSimpleName() +
" could not be instantiated: " + t.getMessage(), e) " could not be instantiated: " + t.getMessage(), e)
) )
.get(); .get();
} finally { } finally {
activeEndpoints.add(c); activeEndpoints.add(c);
} }
}, },
endpointClass); endpointClass);
} }
@Override @Override
public void closeEndpoint(Endpoint activeEndpoint) { public void closeEndpoint(Endpoint activeEndpoint) {
Set<Pair<Class<Endpoint>, List<Object>>> activeEndpoints = endpointStore.get(ACTIVE_ENDPOINTS, Set.class); Set<Pair<Class<Endpoint>, List<Object>>> activeEndpoints = endpointStore.get(ACTIVE_ENDPOINTS, Set.class);
Pair<Class<Endpoint>, List<Object>> pair = activeEndpoints.stream() Pair<Class<Endpoint>, List<Object>> pair = activeEndpoints.stream()
.filter(p -> endpointStore.get(p, Endpoint.class) == activeEndpoint) .filter(p -> endpointStore.get(p, Endpoint.class) == activeEndpoint)
.findFirst() .findFirst()
.orElseThrow(() -> new IllegalArgumentException("Cannot find active endpoint to close")); .orElseThrow(() -> new IllegalArgumentException("Cannot find active endpoint to close"));
Endpoint endpoint = endpointStore.get(pair, Endpoint.class); Endpoint endpoint = endpointStore.get(pair, Endpoint.class);
endpoint.close(); endpoint.close();
activeEndpoints.remove(pair); activeEndpoints.remove(pair);
endpointStore.remove(pair, Endpoint.class); endpointStore.remove(pair, Endpoint.class);
} }
@Override @Override
public <E extends Endpoint> Set<E> getActiveEndpoints() { public <E extends Endpoint> Set<E> getActiveEndpoints() {
Set<Pair<Class<E>, List<Object>>> activeEndpoints = endpointStore.get(ACTIVE_ENDPOINTS, Set.class); Set<Pair<Class<E>, List<Object>>> activeEndpoints = endpointStore.get(ACTIVE_ENDPOINTS, Set.class);
return activeEndpoints.stream() return activeEndpoints.stream()
.map(pair -> (E) endpointStore.get(pair, Endpoint.class)) .map(pair -> (E) endpointStore.get(pair, Endpoint.class))
.collect(Collectors.toSet()); .collect(Collectors.toSet());
} }
@Override @Override
public String getConfig(String key) { public String getConfig(String key) {
return configStore.get(key, String.class); return configStore.get(key, String.class);
} }
@Override @Override
public String getConfig(String key, String defaultValue) { public String getConfig(String key, String defaultValue) {
return Optional.ofNullable(configStore.get(key, String.class)).orElse(defaultValue); return Optional.ofNullable(configStore.get(key, String.class)).orElse(defaultValue);
} }
@Override @Override
public void putConfig(String key, String value) { public void putConfig(String key, String value) {
log(Level.INFO, "Value: " + value + " is stored with key: " + key); log(Level.INFO, "Value: " + value + " is stored with key: " + key);
configStore.put(key, value); configStore.put(key, value);
} }
@Override @Override
public <T> T get(String key) { public <T> T get(String key) {
return get(key, false); return get(key, false);
} }
@Override @Override
public <T> T get(String key, boolean nullEnabled) { public <T> T get(String key, boolean nullEnabled) {
if (nullEnabled) { if (nullEnabled) {
return (T) globalStore.get(key); return (T) globalStore.get(key);
} else { } else {
return (T) Optional.ofNullable(globalStore.get(key)) return (T) Optional.ofNullable(globalStore.get(key))
.orElseThrow(() -> new HarnessException("Error getting value from store, key: " + key + " is null\nCheck if value in this key is correctly set")); .orElseThrow(() -> new HarnessException("Error getting value from store, key: " + key + " is null\nCheck if value in this key is correctly set"));
} }
} }
@Override @Override
public void store(String key, Object value) { public void store(String key, Object value) {
log(Level.INFO, "Value: " + value + " is stored with key: " + key); log(Level.INFO, "Value: " + value + " is stored with key: " + key);
globalStore.put(key, value); globalStore.put(key, value);
} }
@Override @Override
public void storeGlobal(String key, Object value) { public void storeGlobal(String key, Object value) {
log(Level.INFO, "Value: " + value + " is stored with key: " + key); log(Level.INFO, "Value: " + value + " is stored with key: " + key);
rootStore.put(key, value); rootStore.put(key, value);
} }
@Override @Override
public void storeGlobal(String key, Supplier orCreate) { public void storeGlobal(String key, Supplier orCreate) {
log(Level.INFO, "Value: " + orCreate.get().toString() + " is stored with key: " + key); log(Level.INFO, "Value: " + orCreate.get().toString() + " is stored with key: " + key);
rootStore.getOrComputeIfAbsent(key, k -> orCreate.get()); rootStore.getOrComputeIfAbsent(key, k -> orCreate.get());
} }
@Override @Override
public <T> T getGlobal(String key, Supplier<T> orCreate) { public <T> T getGlobal(String key, Supplier<T> orCreate) {
return (T) rootStore.getOrComputeIfAbsent(key, k -> orCreate.get()); return (T) rootStore.getOrComputeIfAbsent(key, k -> orCreate.get());
} }
@Override @Override
public <T> T getGlobal(String key) { public <T> T getGlobal(String key) {
return (T) rootStore.get(key); return (T) rootStore.get(key);
} }
@Override @Override
public void log(String template, Object... args) { public void log(String template, Object... args) {
LOG.info(template, args); LOG.info(template, args);
} }
@Override @Override
public void log(Level level, String template, Object... args) { public void log(Level level, String template, Object... args) {
LOG.log(org.apache.logging.log4j.Level.getLevel(level.name()), template, args); LOG.log(org.apache.logging.log4j.Level.getLevel(level.name()), template, args);
} }
protected void addGenerator(GeneratorType type, Generator<?> generator) { protected void addGenerator(GeneratorType type, Generator<?> generator) {
generatorsStore.put(type, generator); generatorsStore.put(type, generator);
} }
@Override @Override
public <T> T generate(GeneratorType type, Object... params) { public <T> T generate(GeneratorType type, Object... params) {
return (T) generatorsStore.get(type, Generator.class).generate(this, params); return (T) generatorsStore.get(type, Generator.class).generate(this, params);
} }
/** /**
* Get config value from system property or value from config store. * Get config value from system property or value from config store.
* <p> * <p>
* Value get by priority: * Value get by priority:
* <ol> * <ol>
* <li> * <li>
* System property (e.g. passed by maven -D) * System property (e.g. passed by maven -D)
* </li> * </li>
* <li> * <li>
* Value from config store * Value from config store
* </li> * </li>
* </ol> * </ol>
* </p> * </p>
* <p> * <p>
* In case of value not found HarnessConfigurationException is thrown. * In case of value not found HarnessConfigurationException is thrown.
* </p> * </p>
* *
* @param key identifier * @param key identifier
* @return String value for defined key * @return String value for defined key
*/ */
public String getSystemOrConfigValue(String key) { public String getSystemOrConfigValue(String key) {
return Stream.of(Optional.ofNullable(System.getProperty(key)), Optional.ofNullable(getConfig(key))) return Stream.of(Optional.ofNullable(System.getProperty(key)), Optional.ofNullable(getConfig(key)))
.filter(Optional::isPresent) .filter(Optional::isPresent)
.map(Optional::get) .map(Optional::get)
.findFirst() .findFirst()
.orElseThrow(() -> new HarnessConfigurationException(("You need to configure " + key + " parameter!"))); .orElseThrow(() -> new HarnessConfigurationException(("You need to configure " + key + " parameter!")));
} }
} }

View File

@ -1,10 +1,10 @@
package cz.moneta.test.harness.context; package cz.moneta.test.harness.context;
public interface ConfigAccessor { public interface ConfigAccessor {
String getConfig(String key); String getConfig(String key);
String getConfig(String key, String defaultValue); String getConfig(String key, String defaultValue);
void putConfig(String key, String value); void putConfig(String key, String value);
} }

View File

@ -1,98 +1,98 @@
package cz.moneta.test.harness.context; package cz.moneta.test.harness.context;
import cz.moneta.test.harness.endpoints.Endpoint; import cz.moneta.test.harness.endpoints.Endpoint;
import cz.moneta.test.harness.support.data.Generator; import cz.moneta.test.harness.support.data.Generator;
import cz.moneta.test.harness.support.data.GeneratorType; import cz.moneta.test.harness.support.data.GeneratorType;
import cz.moneta.test.harness.support.util.Level; import cz.moneta.test.harness.support.util.Level;
import java.util.Set; import java.util.Set;
import java.util.function.Supplier; import java.util.function.Supplier;
public interface StoreAccessor extends ConfigAccessor { public interface StoreAccessor extends ConfigAccessor {
/** /**
* This method is used to get a direct access to an application or system endpoint. Typical usage is: * This method is used to get a direct access to an application or system endpoint. Typical usage is:
* <br/> * <br/>
* <pre>{@code * <pre>{@code
* public Login openLoginPage() { * public Login openLoginPage() {
* LoansBranchEndpoint endpoint = harness.getEndpoint(LoansBranchEndpoint.class); * LoansBranchEndpoint endpoint = harness.getEndpoint(LoansBranchEndpoint.class);
* endpoint.openApplication(); * endpoint.openApplication();
* return Builders.newWebFlowBuilder(Login.class, endpoint, harness); * return Builders.newWebFlowBuilder(Login.class, endpoint, harness);
* } * }
* }</pre> * }</pre>
* *
* Subsequent calls to this method within the same test class return the same instance of the endpoint. * Subsequent calls to this method within the same test class return the same instance of the endpoint.
* <br/> * <br/>
* <i><b>NOTE</b></i> calling this method with different arguments results in multiple instance of the same endpoint * <i><b>NOTE</b></i> calling this method with different arguments results in multiple instance of the same endpoint
* being instantiated * being instantiated
* *
* @param endpointClass desired endpoint class * @param endpointClass desired endpoint class
* @param args arguments to be passed to the endpoint implementation constructor * @param args arguments to be passed to the endpoint implementation constructor
* @return returns an existing or - in case has not been instantiated - new instance of the endpoint * @return returns an existing or - in case has not been instantiated - new instance of the endpoint
*/ */
<E extends Endpoint> E getEndpoint(Class<E> endpointClass, Object... args); <E extends Endpoint> E getEndpoint(Class<E> endpointClass, Object... args);
void closeEndpoint(Endpoint endpoint); void closeEndpoint(Endpoint endpoint);
<E extends Endpoint> Set<E> getActiveEndpoints(); <E extends Endpoint> Set<E> getActiveEndpoints();
/** /**
* Returns a value previously stored via the {@link StoreAccessor#store} method within the same test class. * Returns a value previously stored via the {@link StoreAccessor#store} method within the same test class.
* <pre>{@code * <pre>{@code
* harness.withUfoBanka() * harness.withUfoBanka()
* .openLoginPage() * .openLoginPage()
* .then(bankaTasks.login().withCredentialsAndTurnOffSignpad(credentials)) * .then(bankaTasks.login().withCredentialsAndTurnOffSignpad(credentials))
* .fillSearchTerm(harness.get(HKO001_MainClientPage.CLIENT_CIF_STORE_KEY)) * .fillSearchTerm(harness.get(HKO001_MainClientPage.CLIENT_CIF_STORE_KEY))
* .clickSearchByCif() * .clickSearchByCif()
* }</pre> * }</pre>
*/ */
<T> T get(String key); <T> T get(String key);
<T> T get(String key, boolean nullEnabled); <T> T get(String key, boolean nullEnabled);
/** /**
* Stores the given value with the key. The key-value pair is kept for the duration of the test class execution. * Stores the given value with the key. The key-value pair is kept for the duration of the test class execution.
*/ */
void store(String key, Object value); void store(String key, Object value);
/** /**
* Works much like {@link StoreAccessor#get} only it gets values from the persistent global storage that is kept * Works much like {@link StoreAccessor#get} only it gets values from the persistent global storage that is kept
* for the duration of the execution of all test classes * for the duration of the execution of all test classes
* *
* @param key key * @param key key
* @param orCreate if the key-value pair is not found, the supplier instantiates and stores the value before * @param orCreate if the key-value pair is not found, the supplier instantiates and stores the value before
* returning it * returning it
*/ */
<T> T getGlobal(String key, Supplier<T> orCreate); <T> T getGlobal(String key, Supplier<T> orCreate);
/** /**
* Works much like {@link StoreAccessor#get} only it gets values from the persistent global storage that is kept * Works much like {@link StoreAccessor#get} only it gets values from the persistent global storage that is kept
* for the duration of the execution of all test classes * for the duration of the execution of all test classes
*/ */
<T> T getGlobal(String key); <T> T getGlobal(String key);
/** /**
* Works much like {@link StoreAccessor#store} only it stores values in the persistent global storage that is kept * Works much like {@link StoreAccessor#store} only it stores values in the persistent global storage that is kept
* for the duration of the execution of all test classes * for the duration of the execution of all test classes
*/ */
void storeGlobal(String key, Object value); void storeGlobal(String key, Object value);
void storeGlobal(String key, Supplier orCreate); void storeGlobal(String key, Supplier orCreate);
void log(String template, Object... args); void log(String template, Object... args);
void log(Level level, String template, Object... args); void log(Level level, String template, Object... args);
/** /**
* Generates a value. * Generates a value.
* <p/> * <p/>
* Typical usage: * Typical usage:
* <pre>{@code * <pre>{@code
* String ico = harness.generate(GeneratorType.ICO); * String ico = harness.generate(GeneratorType.ICO);
* }</pre> * }</pre>
* @param type typ of the generator to be used * @param type typ of the generator to be used
* @param params parameters to be passed to the {@link Generator#generate} method * @param params parameters to be passed to the {@link Generator#generate} method
*/ */
<T> T generate(GeneratorType type, Object... params); <T> T generate(GeneratorType type, Object... params);
} }

View File

@ -1,27 +1,27 @@
package cz.moneta.test.harness.data; package cz.moneta.test.harness.data;
import java.util.Arrays; import java.util.Arrays;
public enum Browser { public enum Browser {
GOOGLE_CHROME("chrome"), GOOGLE_CHROME("chrome"),
MS_EDGE("edge"), MS_EDGE("edge"),
NOT_SUPPORTED("not_supported"); NOT_SUPPORTED("not_supported");
private String configName; private String configName;
Browser(String configName) { Browser(String configName) {
this.configName = configName; this.configName = configName;
} }
public String getConfigName() { public String getConfigName() {
return configName; return configName;
} }
public static Browser getBrowserByConfigName(String configName) { public static Browser getBrowserByConfigName(String configName) {
return Arrays.stream(values()) return Arrays.stream(values())
.filter(browser -> browser.getConfigName().equals(configName.trim().toLowerCase())) .filter(browser -> browser.getConfigName().equals(configName.trim().toLowerCase()))
.findFirst() .findFirst()
.orElse(NOT_SUPPORTED); .orElse(NOT_SUPPORTED);
} }
} }

View File

@ -1,20 +1,20 @@
package cz.moneta.test.harness.endpoints; package cz.moneta.test.harness.endpoints;
import cz.moneta.test.harness.connectors.WsConnector; import cz.moneta.test.harness.connectors.WsConnector;
import javax.xml.namespace.QName; import javax.xml.namespace.QName;
import java.net.URL; import java.net.URL;
public abstract class BaseWsEndpoint implements Endpoint { public abstract class BaseWsEndpoint implements Endpoint {
private final WsConnector connector; private final WsConnector connector;
public BaseWsEndpoint(String address, URL wsdlUrl, QName serviceName) { public BaseWsEndpoint(String address, URL wsdlUrl, QName serviceName) {
connector = new WsConnector(address, wsdlUrl, serviceName); connector = new WsConnector(address, wsdlUrl, serviceName);
} }
public <RESP> RESP invoke(Object request, Class<RESP> responseClass) { public <RESP> RESP invoke(Object request, Class<RESP> responseClass) {
return connector.invoke(request, responseClass); return connector.invoke(request, responseClass);
} }
} }

View File

@ -1,28 +1,28 @@
package cz.moneta.test.harness.endpoints; package cz.moneta.test.harness.endpoints;
import cz.moneta.test.harness.annotations.Environment; import cz.moneta.test.harness.annotations.Environment;
import cz.moneta.test.harness.constants.HarnessConfigConstants; import cz.moneta.test.harness.constants.HarnessConfigConstants;
import cz.moneta.test.harness.context.StoreAccessor; import cz.moneta.test.harness.context.StoreAccessor;
public class CommonEndpoint implements Endpoint { public class CommonEndpoint implements Endpoint {
private Environment environment; private Environment environment;
public CommonEndpoint(StoreAccessor storeAccessor) { public CommonEndpoint(StoreAccessor storeAccessor) {
this.environment = getEnvironmentByConfigName(storeAccessor); this.environment = getEnvironmentByConfigName(storeAccessor);
} }
private Environment getEnvironmentByConfigName(StoreAccessor storeAccessor) { private Environment getEnvironmentByConfigName(StoreAccessor storeAccessor) {
String environmentConfigName = System.getProperty(HarnessConfigConstants.ENVIRONMENT_TYPE); String environmentConfigName = System.getProperty(HarnessConfigConstants.ENVIRONMENT_TYPE);
if (environmentConfigName == null) { if (environmentConfigName == null) {
environmentConfigName = storeAccessor.getConfig(HarnessConfigConstants.ENVIRONMENT_TYPE); environmentConfigName = storeAccessor.getConfig(HarnessConfigConstants.ENVIRONMENT_TYPE);
} }
return Environment.fromString(environmentConfigName); return Environment.fromString(environmentConfigName);
} }
public Environment getEnvironment() { public Environment getEnvironment() {
return environment; return environment;
} }
} }

View File

@ -1,35 +1,35 @@
package cz.moneta.test.harness.endpoints; package cz.moneta.test.harness.endpoints;
/** /**
* Endpoints are the the primary interface for interacting with the Harness library. * Endpoints are the the primary interface for interacting with the Harness library.
* <p/> * <p/>
* They serve as unified wrappers around implementation details (i.e. native libraries), so, that no prior knowledge * They serve as unified wrappers around implementation details (i.e. native libraries), so, that no prior knowledge
* of numerous system client libraries is required. * of numerous system client libraries is required.
* <p/> * <p/>
* Common endpoint would hold an instance of a single {@link cz.moneta.test.harness.connectors.Connector Connector} * Common endpoint would hold an instance of a single {@link cz.moneta.test.harness.connectors.Connector Connector}
* responsible for the actual native client library interaction. Such connectors would include REST, WS, ORACLE or * responsible for the actual native client library interaction. Such connectors would include REST, WS, ORACLE or
* Selenium ones. * Selenium ones.
* <p/> * <p/>
* While implementing an endpoint for a new system, it is a good idea to find a similar endpoint harnessing the * While implementing an endpoint for a new system, it is a good idea to find a similar endpoint harnessing the
* same/required connector type. For example when implementing an endpoint for a new REST API, one * same/required connector type. For example when implementing an endpoint for a new REST API, one
* could start with modifying {@link cz.moneta.test.harness.endpoints.autoapi.AutoApiEndpoint AutoApiEndpoint} * could start with modifying {@link cz.moneta.test.harness.endpoints.autoapi.AutoApiEndpoint AutoApiEndpoint}
* or similar * or similar
* <p/> * <p/>
* Some endpoints may require a new Connector implemented for a specific technology which is not handled by Harness yet * Some endpoints may require a new Connector implemented for a specific technology which is not handled by Harness yet
* <p/> * <p/>
* Endpoints are usually instantiated inside a {@code harness.withSomeSystemName()} method which in turn calls * Endpoints are usually instantiated inside a {@code harness.withSomeSystemName()} method which in turn calls
* {@code harness.getEndpoint(SomeSystemEndpoint.class)} * {@code harness.getEndpoint(SomeSystemEndpoint.class)}
* <p/> * <p/>
* All endpoints instantiated via the {@link cz.moneta.test.harness.context.StoreAccessor#getEndpoint(Class, Object...)} * All endpoints instantiated via the {@link cz.moneta.test.harness.context.StoreAccessor#getEndpoint(Class, Object...)}
* method are automatically closed (i.e. the resources are released) after each test * method are automatically closed (i.e. the resources are released) after each test
*/ */
public interface Endpoint { public interface Endpoint {
default boolean canAccess() { default boolean canAccess() {
return true; return true;
} }
default void close() { default void close() {
} }
} }

View File

@ -1,190 +1,190 @@
package cz.moneta.test.harness.endpoints; package cz.moneta.test.harness.endpoints;
import cz.moneta.test.harness.connectors.mobile.AppiumMobileConnector; import cz.moneta.test.harness.connectors.mobile.AppiumMobileConnector;
import cz.moneta.test.harness.connectors.mobile.UnsupportedPlatformException; import cz.moneta.test.harness.connectors.mobile.UnsupportedPlatformException;
import cz.moneta.test.harness.connectors.mobile.android.AndroidConnector; import cz.moneta.test.harness.connectors.mobile.android.AndroidConnector;
import cz.moneta.test.harness.connectors.mobile.ios.IosConnector; import cz.moneta.test.harness.connectors.mobile.ios.IosConnector;
import cz.moneta.test.harness.endpoints.smartbanka.MobilePlatform; import cz.moneta.test.harness.endpoints.smartbanka.MobilePlatform;
import cz.moneta.test.harness.exception.HarnessException; import cz.moneta.test.harness.exception.HarnessException;
import cz.moneta.test.harness.support.mobile.MobileLookup; import cz.moneta.test.harness.support.mobile.MobileLookup;
import cz.moneta.test.harness.support.mobile.share.Direction; import cz.moneta.test.harness.support.mobile.share.Direction;
import cz.moneta.test.harness.support.web.Clickable; import cz.moneta.test.harness.support.web.Clickable;
import cz.moneta.test.harness.support.web.TextContainer; import cz.moneta.test.harness.support.web.TextContainer;
import cz.moneta.test.harness.support.web.Until; import cz.moneta.test.harness.support.web.Until;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.Supplier; import java.util.function.Supplier;
public class MobileEndpoint<T extends AppiumMobileConnector> implements Endpoint { public class MobileEndpoint<T extends AppiumMobileConnector> implements Endpoint {
protected final ThreadLocal<T> connector; protected final ThreadLocal<T> connector;
public MobileEndpoint(Supplier<T> connectorSupplier) { public MobileEndpoint(Supplier<T> connectorSupplier) {
this.connector = ThreadLocal.withInitial(connectorSupplier); this.connector = ThreadLocal.withInitial(connectorSupplier);
} }
public AndroidConnector getAndroid() { public AndroidConnector getAndroid() {
try { try {
return (AndroidConnector) connector.get(); return (AndroidConnector) connector.get();
} catch (ClassCastException e) { } catch (ClassCastException e) {
throw new UnsupportedPlatformException(MobilePlatform.ANDROID); throw new UnsupportedPlatformException(MobilePlatform.ANDROID);
} }
} }
public IosConnector getIos() { public IosConnector getIos() {
try { try {
return (IosConnector) connector.get(); return (IosConnector) connector.get();
} catch (ClassCastException e) { } catch (ClassCastException e) {
throw new UnsupportedPlatformException(MobilePlatform.IOS); throw new UnsupportedPlatformException(MobilePlatform.IOS);
} }
} }
public void click(Clickable clickable, MobileLookup mobileLookup) { public void click(Clickable clickable, MobileLookup mobileLookup) {
connector.get().click(clickable.getPath(), mobileLookup); connector.get().click(clickable.getPath(), mobileLookup);
} }
public void acceptAlert(int timeout) { public void acceptAlert(int timeout) {
connector.get().acceptAlert(timeout); connector.get().acceptAlert(timeout);
} }
public void dismissAlert(int timeout) { public void dismissAlert(int timeout) {
connector.get().dismissAlert(timeout); connector.get().dismissAlert(timeout);
} }
public String getText(String path, MobileLookup mobileLookup) { public String getText(String path, MobileLookup mobileLookup) {
return connector.get().getText(path, mobileLookup); return connector.get().getText(path, mobileLookup);
} }
public String getText(String path) { public String getText(String path) {
return getText(path, MobileLookup.DEFAULT); return getText(path, MobileLookup.DEFAULT);
} }
public void waitForElementsToLoad(int timeoutSeconds, MobileLookup mobileLookup, Until until, String... elementsToCheck) { public void waitForElementsToLoad(int timeoutSeconds, MobileLookup mobileLookup, Until until, String... elementsToCheck) {
connector.get().waitForElements(timeoutSeconds, mobileLookup, until, elementsToCheck); connector.get().waitForElements(timeoutSeconds, mobileLookup, until, elementsToCheck);
} }
public void sleepSeconds(int seconds) { public void sleepSeconds(int seconds) {
try { try {
TimeUnit.SECONDS.sleep(seconds); TimeUnit.SECONDS.sleep(seconds);
} catch (InterruptedException e) { } catch (InterruptedException e) {
// //
} }
} }
public void scroll(Direction direction) { public void scroll(Direction direction) {
switch (direction) { switch (direction) {
case UP: case UP:
connector.get().scrollUp(); connector.get().scrollUp();
break; break;
case DOWN: case DOWN:
connector.get().scrollDown(); connector.get().scrollDown();
break; break;
case RIGHT: case RIGHT:
connector.get().scrollRight(); connector.get().scrollRight();
break; break;
case LEFT: case LEFT:
connector.get().scrollLeft(); connector.get().scrollLeft();
break; break;
default: default:
throw new HarnessException("unknown direction"); throw new HarnessException("unknown direction");
} }
} }
public void scrollToElement(Direction direction, MobileLookup mobileLookup, String path) { public void scrollToElement(Direction direction, MobileLookup mobileLookup, String path) {
switch (direction) { switch (direction) {
case UP: case UP:
connector.get().scrollUpUntil(path, mobileLookup); connector.get().scrollUpUntil(path, mobileLookup);
break; break;
case DOWN: case DOWN:
connector.get().scrollDownUntil(path, mobileLookup); connector.get().scrollDownUntil(path, mobileLookup);
break; break;
case LEFT: case LEFT:
connector.get().scrollLeftUntil(path, mobileLookup); connector.get().scrollLeftUntil(path, mobileLookup);
break; break;
case RIGHT: case RIGHT:
connector.get().scrollRightUntil(path, mobileLookup); connector.get().scrollRightUntil(path, mobileLookup);
break; break;
default: default:
throw new HarnessException("unknown direction"); throw new HarnessException("unknown direction");
} }
} }
public void swipeViewLeft(){ public void swipeViewLeft(){
connector.get().swipeViewLeft(); connector.get().swipeViewLeft();
} }
public void swipeViewRight(){ public void swipeViewRight(){
connector.get().swipeViewRight(); connector.get().swipeViewRight();
} }
public void swipeFromToElement(String source, String target, MobileLookup mobileLookup) { public void swipeFromToElement(String source, String target, MobileLookup mobileLookup) {
connector.get().swipeFromToElement(source, target, mobileLookup); connector.get().swipeFromToElement(source, target, mobileLookup);
} }
public void type(TextContainer input, String text, boolean clear, MobileLookup mobileLookup) { public void type(TextContainer input, String text, boolean clear, MobileLookup mobileLookup) {
connector.get().type(input, text, clear, mobileLookup); connector.get().type(input, text, clear, mobileLookup);
} }
public void type(TextContainer input, String text, MobileLookup mobileLookup) { public void type(TextContainer input, String text, MobileLookup mobileLookup) {
type(input, text, true, mobileLookup); type(input, text, true, mobileLookup);
} }
public void type(TextContainer input, String text) { public void type(TextContainer input, String text) {
type(input, text, true, MobileLookup.DEFAULT); type(input, text, true, MobileLookup.DEFAULT);
} }
public void takeSnapshot(String prefix) { public void takeSnapshot(String prefix) {
connector.get().takeSnapshot(getFileName(prefix, ".png")); connector.get().takeSnapshot(getFileName(prefix, ".png"));
} }
public void saveSources(String viewName) { public void saveSources(String viewName) {
connector.get().saveSource(getFileName(viewName, ".xml")); connector.get().saveSource(getFileName(viewName, ".xml"));
} }
public void captureVideo(String prefix) { public void captureVideo(String prefix) {
connector.get().captureVideo(getFileName(prefix, ".mp4")); connector.get().captureVideo(getFileName(prefix, ".mp4"));
} }
public void resetApp() { public void resetApp() {
connector.get().resetApp(); connector.get().resetApp();
} }
public void checkElementContent(String id, String content, MobileLookup mobileLookup) { public void checkElementContent(String id, String content, MobileLookup mobileLookup) {
connector.get().elementsCheck().checkElementContent(id, content, mobileLookup); connector.get().elementsCheck().checkElementContent(id, content, mobileLookup);
} }
public void checkElementContent(String id, String content) { public void checkElementContent(String id, String content) {
connector.get().elementsCheck().checkElementContent(id, content, MobileLookup.DEFAULT); connector.get().elementsCheck().checkElementContent(id, content, MobileLookup.DEFAULT);
} }
public void checkElementPresent(String path, MobileLookup mobileLookup){ public void checkElementPresent(String path, MobileLookup mobileLookup){
connector.get().elementsCheck().checkElementPresent(path, mobileLookup); connector.get().elementsCheck().checkElementPresent(path, mobileLookup);
} }
@Override @Override
public void close() { public void close() {
connector.get().close(); connector.get().close();
} }
private String getFileName(String prefix, String suffix) { private String getFileName(String prefix, String suffix) {
SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy/(HH.mm.ss)"); SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy/(HH.mm.ss)");
Date date = new Date(System.currentTimeMillis()); Date date = new Date(System.currentTimeMillis());
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
if (prefix != null) { if (prefix != null) {
sb.append(prefix); sb.append(prefix);
sb.append("-"); sb.append("-");
} }
sb.append(formatter.format(date)); sb.append(formatter.format(date));
sb.append("-"); sb.append("-");
sb.append(MobilePlatform.getMobilePlatformFromConfig(connector.get().getStore())); sb.append(MobilePlatform.getMobilePlatformFromConfig(connector.get().getStore()));
sb.append("-"); sb.append("-");
sb.append(connector.get().getDeviceName()); sb.append(connector.get().getDeviceName());
if (suffix != null) { if (suffix != null) {
sb.append(suffix); sb.append(suffix);
} }
return sb.toString(); return sb.toString();
} }
} }

View File

@ -1,13 +1,13 @@
package cz.moneta.test.harness.endpoints; package cz.moneta.test.harness.endpoints;
import cz.moneta.test.harness.context.StoreAccessor; import cz.moneta.test.harness.context.StoreAccessor;
import org.openqa.selenium.By; import org.openqa.selenium.By;
public class MonetaPortalEndpoint extends WebEndpoint { public class MonetaPortalEndpoint extends WebEndpoint {
private static final String ENDPOINT_URL_IDENTIFIER = "endpoints.moneta-portal.url"; private static final String ENDPOINT_URL_IDENTIFIER = "endpoints.moneta-portal.url";
public MonetaPortalEndpoint(StoreAccessor storeAccessor) { public MonetaPortalEndpoint(StoreAccessor storeAccessor) {
super(ENDPOINT_URL_IDENTIFIER, By::xpath, storeAccessor); super(ENDPOINT_URL_IDENTIFIER, By::xpath, storeAccessor);
} }
} }

View File

@ -1,27 +1,27 @@
package cz.moneta.test.harness.endpoints; package cz.moneta.test.harness.endpoints;
import cz.moneta.test.harness.connectors.rest.ExtendedRestResponse; import cz.moneta.test.harness.connectors.rest.ExtendedRestResponse;
import cz.moneta.test.harness.context.StoreAccessor; import cz.moneta.test.harness.context.StoreAccessor;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import jakarta.ws.rs.client.Entity; import jakarta.ws.rs.client.Entity;
import java.util.Map; import java.util.Map;
public interface RestEndpoint extends Endpoint { public interface RestEndpoint extends Endpoint {
<T> Pair<Integer, T> get(String path, Map<String, Object> properties, Class<T> responseType, Map<String, Object> headers); <T> Pair<Integer, T> get(String path, Map<String, Object> properties, Class<T> responseType, Map<String, Object> headers);
//TODO - make jackson pick up the correct generic type rather than a linked hash map //TODO - make jackson pick up the correct generic type rather than a linked hash map
<T> Pair<Integer, T> post(String path, Entity<?> request, Class<T> responseType, Map<String, Object> headers, boolean rawResponse); <T> Pair<Integer, T> post(String path, Entity<?> request, Class<T> responseType, Map<String, Object> headers, boolean rawResponse);
default <T> Pair<Integer, ExtendedRestResponse<T>> postExtended(String path, Entity<?> request, default <T> Pair<Integer, ExtendedRestResponse<T>> postExtended(String path, Entity<?> request,
Class<T> responseType, Class<T> responseType,
Map<String, Object> headers) { Map<String, Object> headers) {
throw new UnsupportedOperationException(String.format("Endpoint %s does not support extended request yet. Talk to the Harness support team about it", this.getClass().getSimpleName())); throw new UnsupportedOperationException(String.format("Endpoint %s does not support extended request yet. Talk to the Harness support team about it", this.getClass().getSimpleName()));
} }
<T> Pair<Integer, T> delete(String path, Map<String, Object> properties, Class<T> responseType, Map<String, Object> headers); <T> Pair<Integer, T> delete(String path, Map<String, Object> properties, Class<T> responseType, Map<String, Object> headers);
<T> Pair<Integer, T> patch(String path, Entity<?> request, Class<T> responseType, Map<String, Object> headers, boolean rawResponse); <T> Pair<Integer, T> patch(String path, Entity<?> request, Class<T> responseType, Map<String, Object> headers, boolean rawResponse);
StoreAccessor getStore(); StoreAccessor getStore();
} }

View File

@ -1,13 +1,13 @@
package cz.moneta.test.harness.endpoints; package cz.moneta.test.harness.endpoints;
import cz.moneta.test.harness.context.StoreAccessor; import cz.moneta.test.harness.context.StoreAccessor;
import org.openqa.selenium.By; import org.openqa.selenium.By;
public class SmartAutoEndpoint extends WebEndpoint { public class SmartAutoEndpoint extends WebEndpoint {
private static final String ENDPOINT_URL_IDENTIFIER = "endpoints.smart-auto.url"; private static final String ENDPOINT_URL_IDENTIFIER = "endpoints.smart-auto.url";
public SmartAutoEndpoint(StoreAccessor storeAccessor) { public SmartAutoEndpoint(StoreAccessor storeAccessor) {
super(ENDPOINT_URL_IDENTIFIER, By::xpath, storeAccessor); super(ENDPOINT_URL_IDENTIFIER, By::xpath, storeAccessor);
} }
} }

View File

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

View File

@ -1,50 +1,50 @@
package cz.moneta.test.harness.endpoints.autoapi; package cz.moneta.test.harness.endpoints.autoapi;
import cz.moneta.test.harness.connectors.rest.RestConnector; import cz.moneta.test.harness.connectors.rest.RestConnector;
import cz.moneta.test.harness.connectors.rest.SimpleRestConnector; import cz.moneta.test.harness.connectors.rest.SimpleRestConnector;
import cz.moneta.test.harness.context.StoreAccessor; import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.RestEndpoint; import cz.moneta.test.harness.endpoints.RestEndpoint;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import jakarta.ws.rs.client.Entity; import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.GenericType; import jakarta.ws.rs.core.GenericType;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
public class AutoApiEndpoint implements RestEndpoint { public class AutoApiEndpoint implements RestEndpoint {
private RestConnector restConnector; private RestConnector restConnector;
private StoreAccessor store; private StoreAccessor store;
public AutoApiEndpoint(StoreAccessor store) { public AutoApiEndpoint(StoreAccessor store) {
this.store = store; this.store = store;
String endpointName = "endpoints.auto-api.url"; String endpointName = "endpoints.auto-api.url";
this.restConnector = Optional.ofNullable(store.getConfig(endpointName)) this.restConnector = Optional.ofNullable(store.getConfig(endpointName))
.map(url -> new SimpleRestConnector(url, "AutoApiRestLogger")) .map(url -> new SimpleRestConnector(url, "AutoApiRestLogger"))
.orElseThrow(() -> new IllegalStateException("You need to configure " + endpointName + " to work with AutoApi")); .orElseThrow(() -> new IllegalStateException("You need to configure " + endpointName + " to work with AutoApi"));
} }
@Override @Override
public <T> Pair<Integer, T> get(String path, Map<String, Object> properties, Class<T> responseType, Map<String, Object> headers) { 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); return restConnector.get(path, properties, new GenericType<>(responseType), headers);
} }
@Override @Override
public <T> Pair<Integer, T> post(String path, Entity<?> request, Class<T> responseType, Map<String, Object> headers, boolean rawResponse) { 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); return restConnector.post(path, request, new GenericType<>(responseType), headers);
} }
@Override @Override
public <T> Pair<Integer, T> delete(String path, Map<String, Object> properties, Class<T> responseType, Map<String, Object> headers) { 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); return restConnector.delete(path, properties, new GenericType<>(responseType), headers);
} }
@Override @Override
public <T> Pair<Integer, T> patch(String path, Entity<?> request, Class<T> responseType, Map<String, Object> headers, boolean rawResponse) { 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); return restConnector.patch(path, request, new GenericType<>(responseType), headers);
} }
@Override @Override
public StoreAccessor getStore() { public StoreAccessor getStore() {
return store; return store;
} }
} }

View File

@ -1,50 +1,50 @@
package cz.moneta.test.harness.endpoints.autoapi; package cz.moneta.test.harness.endpoints.autoapi;
import cz.moneta.test.harness.connectors.rest.RestConnector; import cz.moneta.test.harness.connectors.rest.RestConnector;
import cz.moneta.test.harness.connectors.rest.SimpleRestConnector; import cz.moneta.test.harness.connectors.rest.SimpleRestConnector;
import cz.moneta.test.harness.context.StoreAccessor; import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.RestEndpoint; import cz.moneta.test.harness.endpoints.RestEndpoint;
import jakarta.ws.rs.client.Entity; import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.GenericType; import jakarta.ws.rs.core.GenericType;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
public class PSmartAutoEndpoint implements RestEndpoint { public class PSmartAutoEndpoint implements RestEndpoint {
private RestConnector restConnector; private RestConnector restConnector;
private StoreAccessor store; private StoreAccessor store;
public PSmartAutoEndpoint(StoreAccessor store) { public PSmartAutoEndpoint(StoreAccessor store) {
this.store = store; this.store = store;
String endpointName = "endpoints.psmartauto.url"; String endpointName = "endpoints.psmartauto.url";
this.restConnector = Optional.ofNullable(store.getConfig(endpointName)) this.restConnector = Optional.ofNullable(store.getConfig(endpointName))
.map(url -> new SimpleRestConnector(url, "PSmartAutoRestLogger")) .map(url -> new SimpleRestConnector(url, "PSmartAutoRestLogger"))
.orElseThrow(() -> new IllegalStateException("You need to configure " + endpointName + " to work with PSmartAuto")); .orElseThrow(() -> new IllegalStateException("You need to configure " + endpointName + " to work with PSmartAuto"));
} }
@Override @Override
public <T> Pair<Integer, T> get(String path, Map<String, Object> properties, Class<T> responseType, Map<String, Object> headers) { 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); return restConnector.get(path, properties, new GenericType<>(responseType), headers);
} }
@Override @Override
public <T> Pair<Integer, T> post(String path, Entity<?> request, Class<T> responseType, Map<String, Object> headers, boolean rawResponse) { 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); return restConnector.post(path, request, new GenericType<>(responseType), headers);
} }
@Override @Override
public <T> Pair<Integer, T> delete(String path, Map<String, Object> properties, Class<T> responseType, Map<String, Object> headers) { 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); return restConnector.delete(path, properties, new GenericType<>(responseType), headers);
} }
@Override @Override
public <T> Pair<Integer, T> patch(String path, Entity<?> request, Class<T> responseType, Map<String, Object> headers, boolean rawResponse) { 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); return restConnector.patch(path, request, new GenericType<>(responseType), headers);
} }
@Override @Override
public StoreAccessor getStore() { public StoreAccessor getStore() {
return store; return store;
} }
} }

View File

@ -1,47 +1,47 @@
package cz.moneta.test.harness.endpoints.autodb; package cz.moneta.test.harness.endpoints.autodb;
import cz.moneta.test.harness.connectors.database.OracleConnector; import cz.moneta.test.harness.connectors.database.OracleConnector;
import cz.moneta.test.harness.context.StoreAccessor; import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.Endpoint; import cz.moneta.test.harness.endpoints.Endpoint;
import cz.moneta.test.harness.support.auth.AuthSupport; import cz.moneta.test.harness.support.auth.AuthSupport;
import cz.moneta.test.harness.support.auth.Credentials; import cz.moneta.test.harness.support.auth.Credentials;
import org.jooq.DSLContext; import org.jooq.DSLContext;
import org.jooq.Record; import org.jooq.Record;
import org.jooq.Result; import org.jooq.Result;
import org.jooq.Routine; import org.jooq.Routine;
import org.jooq.TableRecord; import org.jooq.TableRecord;
import java.util.Optional; import java.util.Optional;
import java.util.function.Function; import java.util.function.Function;
public class AutoDBEndpoint implements Endpoint { public class AutoDBEndpoint implements Endpoint {
private final ThreadLocal<OracleConnector> autoDbConnector = ThreadLocal.withInitial(this::initConnector); private final ThreadLocal<OracleConnector> autoDbConnector = ThreadLocal.withInitial(this::initConnector);
private final StoreAccessor store; private final StoreAccessor store;
public AutoDBEndpoint(StoreAccessor store) { public AutoDBEndpoint(StoreAccessor store) {
this.store = store; this.store = store;
} }
public <R extends TableRecord<R>> R executeDsl(Function<DSLContext, R> query) { public <R extends TableRecord<R>> R executeDsl(Function<DSLContext, R> query) {
return autoDbConnector.get().executeDsl(query); return autoDbConnector.get().executeDsl(query);
} }
public <R extends Routine<?>> R executeProcedure(R procedure) { public <R extends Routine<?>> R executeProcedure(R procedure) {
return autoDbConnector.get().executeProcedure(procedure); return autoDbConnector.get().executeProcedure(procedure);
} }
public Result<Record> executeSql(String sql) { public Result<Record> executeSql(String sql) {
return autoDbConnector.get().executeSql(sql); return autoDbConnector.get().executeSql(sql);
} }
private OracleConnector initConnector() { private OracleConnector initConnector() {
String endpointName = "endpoints.auto-db.url"; String endpointName = "endpoints.auto-db.url";
Credentials credentials = AuthSupport.getCredentials("auto-db", store); Credentials credentials = AuthSupport.getCredentials("auto-db", store);
return Optional.ofNullable(store.getConfig(endpointName)) return Optional.ofNullable(store.getConfig(endpointName))
.map(url -> new OracleConnector(url, credentials.getUsername(), credentials.getPassword())) .map(url -> new OracleConnector(url, credentials.getUsername(), credentials.getPassword()))
.orElseThrow(() -> new IllegalStateException("You need to configure " + endpointName + " to work with Udebs Database")); .orElseThrow(() -> new IllegalStateException("You need to configure " + endpointName + " to work with Udebs Database"));
} }
} }

View File

@ -0,0 +1,14 @@
package cz.moneta.test.harness.endpoints.bankid;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.WebEndpoint;
import org.openqa.selenium.By;
public class BankIdSoniaDemoWebEndpoint extends WebEndpoint {
private static final String ENDPOINT_URL_IDENTIFIER = "endpoints.bankid.demoapp.url";
public BankIdSoniaDemoWebEndpoint(StoreAccessor storeAccessor) {
super(ENDPOINT_URL_IDENTIFIER, By::xpath, storeAccessor);
}
}

View File

@ -1,14 +1,14 @@
package cz.moneta.test.harness.endpoints.broadcom; package cz.moneta.test.harness.endpoints.broadcom;
import cz.moneta.test.harness.context.StoreAccessor; import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.WebEndpoint; import cz.moneta.test.harness.endpoints.WebEndpoint;
import org.openqa.selenium.By; import org.openqa.selenium.By;
public class BroadcomEndpoint extends WebEndpoint { public class BroadcomEndpoint extends WebEndpoint {
private static final String ENDPOINT_URL_IDENTIFIER = "endpoints.broadcom.url"; private static final String ENDPOINT_URL_IDENTIFIER = "endpoints.broadcom.url";
public BroadcomEndpoint(StoreAccessor storeAccessor) { public BroadcomEndpoint(StoreAccessor storeAccessor) {
super(ENDPOINT_URL_IDENTIFIER, By::xpath, storeAccessor); super(ENDPOINT_URL_IDENTIFIER, By::xpath, storeAccessor);
} }
} }

View File

@ -1,59 +1,59 @@
package cz.moneta.test.harness.endpoints.cagw; package cz.moneta.test.harness.endpoints.cagw;
import cz.moneta.test.harness.connectors.rest.RestConnector; import cz.moneta.test.harness.connectors.rest.RestConnector;
import cz.moneta.test.harness.connectors.rest.SimpleRestConnector; import cz.moneta.test.harness.connectors.rest.SimpleRestConnector;
import cz.moneta.test.harness.constants.HarnessConfigConstants; import cz.moneta.test.harness.constants.HarnessConfigConstants;
import cz.moneta.test.harness.context.StoreAccessor; import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.RestEndpoint; import cz.moneta.test.harness.endpoints.RestEndpoint;
import cz.moneta.test.harness.support.auth.AuthSupport; import cz.moneta.test.harness.support.auth.AuthSupport;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import jakarta.ws.rs.client.Entity; import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.GenericType; import jakarta.ws.rs.core.GenericType;
import java.util.Map; import java.util.Map;
public class CaGwEndpoint implements RestEndpoint { public class CaGwEndpoint implements RestEndpoint {
private RestConnector restConnector; private RestConnector restConnector;
private StoreAccessor store; private StoreAccessor store;
public CaGwEndpoint(StoreAccessor store) { public CaGwEndpoint(StoreAccessor store) {
this.store = store; this.store = store;
String url = store.getConfig("endpoints.cagw.url"); String url = store.getConfig("endpoints.cagw.url");
if (url == null) { if (url == null) {
throw new IllegalStateException("You need to configure endpoints.cagw.url to work with CBL"); throw new IllegalStateException("You need to configure endpoints.cagw.url to work with CBL");
} }
this.restConnector = new SimpleRestConnector(url, "CaGwRestLogger"); this.restConnector = new SimpleRestConnector(url, "CaGwRestLogger");
} }
@Override @Override
public <T> Pair<Integer, T> get(String path, Map<String, Object> properties, Class<T> responseType, Map<String, Object> headers) { 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); return restConnector.get(path, properties, new GenericType<>(responseType), headers);
} }
@Override @Override
public <T> Pair<Integer, T> post(String path, Entity<?> request, Class<T> responseType, Map<String, Object> headers, boolean rawResponse) { 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); return restConnector.post(path, request, new GenericType<>(responseType), headers);
} }
@Override @Override
public <T> Pair<Integer, T> delete(String path, Map<String, Object> properties, Class<T> responseType, Map<String, Object> headers) { 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); return restConnector.delete(path, properties, new GenericType<>(responseType), headers);
} }
@Override @Override
public <T> Pair<Integer, T> patch(String path, Entity<?> request, Class<T> responseType, Map<String, Object> headers, boolean rawResponse) { 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); return restConnector.patch(path, request, new GenericType<>(responseType), headers);
} }
public String getClientSecret(String clientKey) { public String getClientSecret(String clientKey) {
return AuthSupport.getClientSecret(clientKey, store, HarnessConfigConstants.VAULT_CAGW_KEYS_PATH); return AuthSupport.getClientSecret(clientKey, store, HarnessConfigConstants.VAULT_CAGW_KEYS_PATH);
} }
@Override @Override
public StoreAccessor getStore() { public StoreAccessor getStore() {
return store; return store;
} }
} }

View File

@ -1,14 +1,14 @@
package cz.moneta.test.harness.endpoints.cashman; package cz.moneta.test.harness.endpoints.cashman;
import cz.moneta.test.harness.context.StoreAccessor; import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.WebEndpoint; import cz.moneta.test.harness.endpoints.WebEndpoint;
import org.openqa.selenium.By; import org.openqa.selenium.By;
public class CashmanEndpoint extends WebEndpoint { public class CashmanEndpoint extends WebEndpoint {
private static final String ENDPOINT_URL_IDENTIFIER = "endpoints.cashman.url"; private static final String ENDPOINT_URL_IDENTIFIER = "endpoints.cashman.url";
public CashmanEndpoint(StoreAccessor storeAccessor) { public CashmanEndpoint(StoreAccessor storeAccessor) {
super(ENDPOINT_URL_IDENTIFIER, By::xpath, storeAccessor); super(ENDPOINT_URL_IDENTIFIER, By::xpath, storeAccessor);
} }
} }

View File

@ -0,0 +1,38 @@
package cz.moneta.test.harness.endpoints.cds;
import cz.moneta.test.harness.connectors.database.OracleConnector;
import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.Endpoint;
import cz.moneta.test.harness.support.auth.AuthSupport;
import cz.moneta.test.harness.support.auth.Credentials;
import org.jooq.Record;
import org.jooq.Result;
import java.util.Optional;
public class CdsEndpoint implements Endpoint {
private final StoreAccessor store;
private final ThreadLocal<OracleConnector> connector = ThreadLocal.withInitial(this::initConnector);
public CdsEndpoint(StoreAccessor store) {
this.store = store;
}
public Result<Record> executeSql(String sql) {
return connector.get().executeSql(sql);
}
@Override
public boolean canAccess() {
return AuthSupport.hasCredentials("cds", store);
}
private OracleConnector initConnector() {
String endpointName = "endpoints.cds.url";
Credentials credentials = AuthSupport.getCredentials("cds", store);
return Optional.ofNullable(store.getConfig(endpointName))
.map(url -> new OracleConnector(url, credentials.getUsername(), credentials.getPassword()))
.orElseThrow(() -> new IllegalStateException("You need to configure " + endpointName + " to work with CDS"));
}
}

View File

@ -1,20 +1,20 @@
package cz.moneta.test.harness.endpoints.cebia; package cz.moneta.test.harness.endpoints.cebia;
import cz.moneta.test.harness.context.StoreAccessor; import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.BaseWsEndpoint; import cz.moneta.test.harness.endpoints.BaseWsEndpoint;
import javax.xml.namespace.QName; import javax.xml.namespace.QName;
import java.net.URL; import java.net.URL;
@Deprecated(since = "unmaintained since update to Java 17") @Deprecated(since = "unmaintained since update to Java 17")
public class CebiaWsEndpoint extends BaseWsEndpoint { public class CebiaWsEndpoint extends BaseWsEndpoint {
private static final String CEBIA_ADDRESS = "https://app.cebia.com/IVATEST/Services/IvaServiceActual.svc"; private static final String CEBIA_ADDRESS = "https://app.cebia.com/IVATEST/Services/IvaServiceActual.svc";
private static final URL CEBIA_WSDL_URL = CebiaWsEndpoint.class.getClassLoader().getResource("ws/IvaServiceActual.wsdl"); private static final URL CEBIA_WSDL_URL = CebiaWsEndpoint.class.getClassLoader().getResource("ws/IvaServiceActual.wsdl");
private static final QName CEBIA_SERVICE_NAME = new QName("http://schemas.cebia.com/iva/service/", "IvaService"); private static final QName CEBIA_SERVICE_NAME = new QName("http://schemas.cebia.com/iva/service/", "IvaService");
public CebiaWsEndpoint(StoreAccessor store) { public CebiaWsEndpoint(StoreAccessor store) {
super(CEBIA_ADDRESS, CEBIA_WSDL_URL, CEBIA_SERVICE_NAME); super(CEBIA_ADDRESS, CEBIA_WSDL_URL, CEBIA_SERVICE_NAME);
} }
} }

View File

@ -1,16 +1,16 @@
package cz.moneta.test.harness.endpoints.demo; package cz.moneta.test.harness.endpoints.demo;
import cz.moneta.test.harness.context.StoreAccessor; import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.data.Browser; import cz.moneta.test.harness.data.Browser;
import cz.moneta.test.harness.endpoints.WebEndpoint; import cz.moneta.test.harness.endpoints.WebEndpoint;
import org.openqa.selenium.By; import org.openqa.selenium.By;
public class DemoChromeWebEndpoint extends WebEndpoint { public class DemoChromeWebEndpoint extends WebEndpoint {
private static final String ENDPOINT_URL_IDENTIFIER = "endpoints.demo.url"; private static final String ENDPOINT_URL_IDENTIFIER = "endpoints.demo.url";
private static final Browser DEFAULT_BROWSER = Browser.GOOGLE_CHROME; private static final Browser DEFAULT_BROWSER = Browser.GOOGLE_CHROME;
public DemoChromeWebEndpoint(StoreAccessor storeAccessor) { public DemoChromeWebEndpoint(StoreAccessor storeAccessor) {
super(ENDPOINT_URL_IDENTIFIER, By::xpath, storeAccessor, DEFAULT_BROWSER); super(ENDPOINT_URL_IDENTIFIER, By::xpath, storeAccessor, DEFAULT_BROWSER);
} }
} }

View File

@ -1,16 +1,16 @@
package cz.moneta.test.harness.endpoints.demo; package cz.moneta.test.harness.endpoints.demo;
import cz.moneta.test.harness.context.StoreAccessor; import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.data.Browser; import cz.moneta.test.harness.data.Browser;
import cz.moneta.test.harness.endpoints.WebEndpoint; import cz.moneta.test.harness.endpoints.WebEndpoint;
import org.openqa.selenium.By; import org.openqa.selenium.By;
public class DemoEdgeWebEndpoint extends WebEndpoint { public class DemoEdgeWebEndpoint extends WebEndpoint {
private static final String ENDPOINT_URL_IDENTIFIER = "endpoints.demo.url"; private static final String ENDPOINT_URL_IDENTIFIER = "endpoints.demo.url";
private static final Browser DEFAULT_BROWSER = Browser.MS_EDGE; private static final Browser DEFAULT_BROWSER = Browser.MS_EDGE;
public DemoEdgeWebEndpoint(StoreAccessor storeAccessor) { public DemoEdgeWebEndpoint(StoreAccessor storeAccessor) {
super(ENDPOINT_URL_IDENTIFIER, By::xpath, storeAccessor, DEFAULT_BROWSER); super(ENDPOINT_URL_IDENTIFIER, By::xpath, storeAccessor, DEFAULT_BROWSER);
} }
} }

View File

@ -1,28 +1,28 @@
package cz.moneta.test.harness.endpoints.demo; package cz.moneta.test.harness.endpoints.demo;
import cz.moneta.test.harness.connectors.DemoConnector; import cz.moneta.test.harness.connectors.DemoConnector;
import cz.moneta.test.harness.context.StoreAccessor; import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.Endpoint; import cz.moneta.test.harness.endpoints.Endpoint;
public class DemoEndpoint implements Endpoint { public class DemoEndpoint implements Endpoint {
private DemoConnector connector; private DemoConnector connector;
public DemoEndpoint(StoreAccessor store) { public DemoEndpoint(StoreAccessor store) {
connector = new DemoConnector(); connector = new DemoConnector();
String location = store.getConfig("endpoints.demo.spirits.location"); String location = store.getConfig("endpoints.demo.spirits.location");
connector.connectToTempDirectoryService(location); connector.connectToTempDirectoryService(location);
} }
public void summonSpirit(String name) { public void summonSpirit(String name) {
connector.createFile(name); connector.createFile(name);
} }
public boolean spiritPresent(String name) { public boolean spiritPresent(String name) {
return connector.fileExists(name); return connector.fileExists(name);
} }
public void expelSpirit(String name) { public void expelSpirit(String name) {
connector.deleteFile(name); connector.deleteFile(name);
} }
} }

View File

@ -1,14 +1,14 @@
package cz.moneta.test.harness.endpoints.dmbsib; package cz.moneta.test.harness.endpoints.dmbsib;
import cz.moneta.test.harness.context.StoreAccessor; import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.WebEndpoint; import cz.moneta.test.harness.endpoints.WebEndpoint;
import org.openqa.selenium.By; import org.openqa.selenium.By;
public class DmbsIbEndpoint extends WebEndpoint { public class DmbsIbEndpoint extends WebEndpoint {
private static final String ENDPOINT_URL_IDENTIFIER = "endpoints.dmbsib.web.url"; private static final String ENDPOINT_URL_IDENTIFIER = "endpoints.dmbsib.web.url";
public DmbsIbEndpoint(StoreAccessor storeAccessor) { public DmbsIbEndpoint(StoreAccessor storeAccessor) {
super(ENDPOINT_URL_IDENTIFIER, By::xpath, storeAccessor); super(ENDPOINT_URL_IDENTIFIER, By::xpath, storeAccessor);
} }
} }

View File

@ -1,50 +1,50 @@
package cz.moneta.test.harness.endpoints.elastic; package cz.moneta.test.harness.endpoints.elastic;
import cz.moneta.test.harness.connectors.rest.RestConnector; import cz.moneta.test.harness.connectors.rest.RestConnector;
import cz.moneta.test.harness.connectors.rest.SimpleRestConnector; import cz.moneta.test.harness.connectors.rest.SimpleRestConnector;
import cz.moneta.test.harness.context.StoreAccessor; import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.RestEndpoint; import cz.moneta.test.harness.endpoints.RestEndpoint;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import jakarta.ws.rs.client.Entity; import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.GenericType; import jakarta.ws.rs.core.GenericType;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
public class ElasticReadEndpoint implements RestEndpoint { public class ElasticReadEndpoint implements RestEndpoint {
private final RestConnector restConnector; private final RestConnector restConnector;
private final StoreAccessor store; private final StoreAccessor store;
public ElasticReadEndpoint(StoreAccessor store) { public ElasticReadEndpoint(StoreAccessor store) {
this.store = store; this.store = store;
String endpointUrl = "endpoints.elastic.read.url"; String endpointUrl = "endpoints.elastic.read.url";
this.restConnector = Optional.ofNullable(store.getConfig(endpointUrl)) this.restConnector = Optional.ofNullable(store.getConfig(endpointUrl))
.map(url -> new SimpleRestConnector(url, "ElasticRestLogger")) .map(url -> new SimpleRestConnector(url, "ElasticRestLogger"))
.orElseThrow(() -> new IllegalStateException("You need to configure " + endpointUrl + " to work with Elastic Search.")); .orElseThrow(() -> new IllegalStateException("You need to configure " + endpointUrl + " to work with Elastic Search."));
} }
@Override @Override
public <T> Pair<Integer, T> get(String path, Map<String, Object> properties, Class<T> responseType, Map<String, Object> headers) { 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); return restConnector.get(path, properties, new GenericType<>(responseType), headers);
} }
@Override @Override
public <T> Pair<Integer, T> post(String path, Entity<?> request, Class<T> responseType, Map<String, Object> headers, boolean rawResponse) { 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); return restConnector.post(path, request, new GenericType<>(responseType), headers);
} }
@Override @Override
public <T> Pair<Integer, T> delete(String path, Map<String, Object> properties, Class<T> responseType, Map<String, Object> headers) { 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); return restConnector.delete(path, properties, new GenericType<>(responseType), headers);
} }
@Override @Override
public <T> Pair<Integer, T> patch(String path, Entity<?> request, Class<T> responseType, Map<String, Object> headers, boolean rawResponse) { 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); return restConnector.patch(path, request, new GenericType<>(responseType), headers);
} }
@Override @Override
public StoreAccessor getStore() { public StoreAccessor getStore() {
return store; return store;
} }
} }

View File

@ -1,50 +1,50 @@
package cz.moneta.test.harness.endpoints.elastic; package cz.moneta.test.harness.endpoints.elastic;
import cz.moneta.test.harness.connectors.rest.RestConnector; import cz.moneta.test.harness.connectors.rest.RestConnector;
import cz.moneta.test.harness.connectors.rest.SimpleRestConnector; import cz.moneta.test.harness.connectors.rest.SimpleRestConnector;
import cz.moneta.test.harness.context.StoreAccessor; import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.RestEndpoint; import cz.moneta.test.harness.endpoints.RestEndpoint;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import jakarta.ws.rs.client.Entity; import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.GenericType; import jakarta.ws.rs.core.GenericType;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
public class ElasticWriteEndpoint implements RestEndpoint { public class ElasticWriteEndpoint implements RestEndpoint {
private final RestConnector restConnector; private final RestConnector restConnector;
private final StoreAccessor store; private final StoreAccessor store;
public ElasticWriteEndpoint(StoreAccessor store) { public ElasticWriteEndpoint(StoreAccessor store) {
this.store = store; this.store = store;
String endpointUrl = "endpoints.elastic.write.url"; String endpointUrl = "endpoints.elastic.write.url";
this.restConnector = Optional.ofNullable(store.getConfig(endpointUrl)) this.restConnector = Optional.ofNullable(store.getConfig(endpointUrl))
.map(url -> new SimpleRestConnector(url, "ElasticRestLogger")) .map(url -> new SimpleRestConnector(url, "ElasticRestLogger"))
.orElseThrow(() -> new IllegalStateException("You need to configure " + endpointUrl + " to work with Elastic Search.")); .orElseThrow(() -> new IllegalStateException("You need to configure " + endpointUrl + " to work with Elastic Search."));
} }
@Override @Override
public <T> Pair<Integer, T> get(String path, Map<String, Object> properties, Class<T> responseType, Map<String, Object> headers) { 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); return restConnector.get(path, properties, new GenericType<>(responseType), headers);
} }
@Override @Override
public <T> Pair<Integer, T> post(String path, Entity<?> request, Class<T> responseType, Map<String, Object> headers, boolean rawResponse) { 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); return restConnector.post(path, request, new GenericType<>(responseType), headers);
} }
@Override @Override
public <T> Pair<Integer, T> delete(String path, Map<String, Object> properties, Class<T> responseType, Map<String, Object> headers) { 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); return restConnector.delete(path, properties, new GenericType<>(responseType), headers);
} }
@Override @Override
public <T> Pair<Integer, T> patch(String path, Entity<?> request, Class<T> responseType, Map<String, Object> headers, boolean rawResponse) { 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); return restConnector.patch(path, request, new GenericType<>(responseType), headers);
} }
@Override @Override
public StoreAccessor getStore() { public StoreAccessor getStore() {
return store; return store;
} }
} }

View File

@ -1,17 +1,17 @@
package cz.moneta.test.harness.endpoints.exevido; package cz.moneta.test.harness.endpoints.exevido;
import cz.moneta.test.harness.context.StoreAccessor; import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.data.Browser; import cz.moneta.test.harness.data.Browser;
import cz.moneta.test.harness.endpoints.WebEndpoint; import cz.moneta.test.harness.endpoints.WebEndpoint;
import org.openqa.selenium.By; import org.openqa.selenium.By;
public class ExevidoEndpoint extends WebEndpoint { public class ExevidoEndpoint extends WebEndpoint {
private static final String ENDPOINT_URL_IDENTIFIER = "endpoints.exevido.url"; private static final String ENDPOINT_URL_IDENTIFIER = "endpoints.exevido.url";
private static final Browser DEFAULT_BROWSER = Browser.GOOGLE_CHROME; private static final Browser DEFAULT_BROWSER = Browser.GOOGLE_CHROME;
public ExevidoEndpoint(StoreAccessor storeAccessor) { public ExevidoEndpoint(StoreAccessor storeAccessor) {
super(ENDPOINT_URL_IDENTIFIER, By::id, storeAccessor, DEFAULT_BROWSER); super(ENDPOINT_URL_IDENTIFIER, By::id, storeAccessor, DEFAULT_BROWSER);
} }
} }

View File

@ -1,14 +1,14 @@
package cz.moneta.test.harness.endpoints.finanso; package cz.moneta.test.harness.endpoints.finanso;
import cz.moneta.test.harness.context.StoreAccessor; import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.WebEndpoint; import cz.moneta.test.harness.endpoints.WebEndpoint;
import org.openqa.selenium.By; import org.openqa.selenium.By;
public class FinansoEndpoint extends WebEndpoint { public class FinansoEndpoint extends WebEndpoint {
private static final String ENDPOINT_URL_IDENTIFIER = "endpoints.finanso.url"; private static final String ENDPOINT_URL_IDENTIFIER = "endpoints.finanso.url";
public FinansoEndpoint(StoreAccessor storeAccessor) { public FinansoEndpoint(StoreAccessor storeAccessor) {
super(ENDPOINT_URL_IDENTIFIER, By::xpath, storeAccessor); super(ENDPOINT_URL_IDENTIFIER, By::xpath, storeAccessor);
} }
} }

View File

@ -1,14 +1,14 @@
package cz.moneta.test.harness.endpoints.forte; package cz.moneta.test.harness.endpoints.forte;
import cz.moneta.test.harness.context.StoreAccessor; import cz.moneta.test.harness.context.StoreAccessor;
import cz.moneta.test.harness.endpoints.WebEndpoint; import cz.moneta.test.harness.endpoints.WebEndpoint;
import org.openqa.selenium.By; import org.openqa.selenium.By;
public class ForteEndpoint extends WebEndpoint { public class ForteEndpoint extends WebEndpoint {
private static final String ENDPOINT_URL_IDENTIFIER = "endpoints.forte.url"; private static final String ENDPOINT_URL_IDENTIFIER = "endpoints.forte.url";
public ForteEndpoint(StoreAccessor storeAccessor) { public ForteEndpoint(StoreAccessor storeAccessor) {
super(ENDPOINT_URL_IDENTIFIER, By::xpath, storeAccessor); super(ENDPOINT_URL_IDENTIFIER, By::xpath, storeAccessor);
} }
} }

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