All Downloads are FREE. Search and download functionalities are using the official Maven repository.

step.artefacts.handlers.AssertHandler Maven / Gradle / Ivy

The newest version!
/*******************************************************************************
 * Copyright (C) 2020, exense GmbH
 *
 * This file is part of STEP
 *
 * STEP is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * STEP is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with STEP.  If not, see .
 ******************************************************************************/
package step.artefacts.handlers;

import jakarta.json.JsonObject;

import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.PathNotFoundException;

import jakarta.json.JsonValue;
import step.artefacts.Assert;
import step.artefacts.Assert.AssertOperator;
import step.artefacts.handlers.asserts.*;
import step.artefacts.reports.AssertReportNode;
import step.artefacts.reports.CallFunctionReportNode;
import step.core.artefacts.handlers.ArtefactHandler;
import step.core.artefacts.reports.ReportNode;
import step.core.artefacts.reports.ReportNodeStatus;
import step.core.reports.Error;
import step.core.reports.ErrorType;

import java.util.HashMap;
import java.util.Map;

public class AssertHandler extends ArtefactHandler {

	private final Map operatorHandlers;

	public AssertHandler() {
		this.operatorHandlers = new HashMap<>();
		this.operatorHandlers.put(AssertOperator.EQUALS, new EqualsOperatorHandler());
		this.operatorHandlers.put(AssertOperator.CONTAINS, new ContainsOperatorHandler());
		this.operatorHandlers.put(AssertOperator.BEGINS_WITH, new BeginsWithOperatorHandler());
		this.operatorHandlers.put(AssertOperator.ENDS_WITH, new EndsWithOperatorHandler());
		this.operatorHandlers.put(AssertOperator.MATCHES, new MatchesOperatorHandler());
		this.operatorHandlers.put(AssertOperator.GREATER_THAN, new GreaterThanOperatorHandler());
		this.operatorHandlers.put(AssertOperator.GREATER_THAN_OR_EQUALS, new GreaterThanOrEqualsOperatorHandler());
		this.operatorHandlers.put(AssertOperator.LESS_THAN, new LessThanOperatorHandler());
		this.operatorHandlers.put(AssertOperator.LESS_THAN_OR_EQUALS, new LessThanOrEqualsOperatorHandler());
		this.operatorHandlers.put(AssertOperator.IS_NULL, new IsNullOperatorHandler());
	}

	@Override
	protected void createReportSkeleton_(AssertReportNode parentNode, Assert artefact) {

	}

	@Override
	protected void execute_(AssertReportNode node, Assert artefact) {
		CallFunctionReportNode callFunctionReport = (CallFunctionReportNode) context.getVariablesManager().getVariable("callReport");
		if (callFunctionReport == null) {
			node.setError(new Error(ErrorType.TECHNICAL, "Keyword report unreachable. Asserts should be wrapped in Keyword nodes in the test plan."));
			node.setStatus(ReportNodeStatus.TECHNICAL_ERROR);
			return;
		}
		if (callFunctionReport.getStatus() == ReportNodeStatus.PASSED) {
			JsonObject outputJson = callFunctionReport.getOutputObject();
			String key = artefact.getActual().get();
			AssertOperator operator = artefact.getOperator();
			node.setKey(key);

			// Expected value has a String generic type, but in fact it can be resolved to Boolean or Number
			// so here we use Object type for expected value
			Object expectedValue = artefact.getExpected().get();

			ValueResolvingResult valueResolvingResult = resolveValue(outputJson, key);

			boolean passed = false;
			if (valueResolvingResult.actualResolved) {
				node.setActual(valueToString(valueResolvingResult.actual));
				node.setExpected(valueToString(expectedValue));

				//boolean negate = artefact.isNegate();
				boolean negate = artefact.getDoNegate().get();

				AssertResult assertResult = applyOperator(key, valueResolvingResult, expectedValue, negate, operator);

				node.setDescription(assertResult.getDescription());
				node.setMessage(assertResult.getMessage());
				node.setStatus(assertResult.isPassed() ? ReportNodeStatus.PASSED : ReportNodeStatus.FAILED);

				passed = assertResult.isPassed();
			} else {
				node.setMessage(valueResolvingResult.message);
				node.setStatus(ReportNodeStatus.FAILED);
			}

			if (!passed) {
				String customErrorMessage = artefact.getCustomErrorMessage().get();
				if (customErrorMessage != null && !customErrorMessage.isEmpty()) {
					node.setMessage(customErrorMessage);
				}
				node.setError(new Error(ErrorType.BUSINESS, node.getMessage()));
			}
		} else {
			node.setStatus(ReportNodeStatus.NORUN);
		}
	}

	private ValueResolvingResult resolveValue(JsonObject outputJson, String key) {
		if (key.startsWith("$")) {
			return resolveJsonPathValue(outputJson, key);
		} else {
			return resolveSimpleValue(outputJson, key);
		}
	}

	private ValueResolvingResult resolveJsonPathValue(JsonObject outputJson, String key) {
		ValueResolvingResult valueResolvingResult = new ValueResolvingResult();
		try {
			valueResolvingResult.actual = JsonPath.parse(outputJson.toString()).read(key);
			valueResolvingResult.actualResolved = true;
		} catch (PathNotFoundException e) {
			// the attribute is missing (but we mark the value as resolved because some operators like 'notNull' support the missing values as nulls)
			valueResolvingResult.actual = null;
			valueResolvingResult.message = e.getMessage();
			valueResolvingResult.actualResolved = true;
		}
		valueResolvingResult.type = ValueType.JSON_PATH;
		return valueResolvingResult;
	}

	private ValueResolvingResult resolveSimpleValue(JsonObject outputJson, String key) {
		ValueResolvingResult result = new ValueResolvingResult();
		JsonValue jsonValue = outputJson.get(key);
		if (jsonValue == null) {
			// the attribute is missing (but we mark the value as resolved because some operators like 'notNull' support the missing values as nulls)
			result.actual = null;
			result.actualResolved = true;
		} else if (jsonValue.getValueType() == JsonValue.ValueType.NULL) {
			result.actual = null;
			result.actualResolved = true;
		} else if (jsonValue.getValueType() == JsonValue.ValueType.STRING) {
			result.actual = outputJson.getString(key);
			result.actualResolved = true;
		} else if (jsonValue.getValueType() == JsonValue.ValueType.NUMBER) {
			result.actual = outputJson.getJsonNumber(key).numberValue();
			result.actualResolved = true;
		} else if (jsonValue.getValueType() == JsonValue.ValueType.FALSE || jsonValue.getValueType() == JsonValue.ValueType.TRUE) {
			result.actual = outputJson.getBoolean(key);
			result.actualResolved = true;
		} else {
			result.message = "Type of " + key + " (" + jsonValue.getValueType() + ") is not supported";
			result.actualResolved = false;
		}

		result.type = ValueType.SIMPLE;
		return result;
	}

	private AssertResult applyOperator(String key, ValueResolvingResult valueResolvingResult, Object expectedValue, boolean negate, AssertOperator operator) {
		Object actual = valueResolvingResult.actual;
		ValueType type = valueResolvingResult.type;

		AssertOperatorHandler handler = getOperatorHandler(operator);

		if (!handler.isActualValueSupported(actual)) {
			String message;

			if (valueResolvingResult.message != null && !valueResolvingResult.message.isEmpty()) {
				// in some cases (like in case of missing attributes) the error message is already prepared during value resolving
				message = valueResolvingResult.message;
			} else {

				if (actual == null) {
					// user-friendly message for null-value
					message = "Unable to execute assertion. The keyword output doesn't contain the attribute '" + key + "'";
				} else if (type == ValueType.JSON_PATH) {
					// json path value
					message = "The json path '" + key + "' returns an object of type "
							+ actual.getClass().getSimpleName() + " which is not supported for operator " + operator.name();
				} else {
					// simple value
					message = "Type of " + key + " ("
							+ actual.getClass().getSimpleName()
							+ ") is not supported for operator " + operator;
				}
			}
			return createFailedAssertResult(message);
		}

		if (!handler.isExpectedValueSupported(expectedValue)) {
			String message;
			if (expectedValue == null || (expectedValue instanceof String && ((String) expectedValue).isEmpty())) {
				// user-friendly message for null-value
				message = "Unable to execute assertion. The expected value is not defined for the attribute '" + key + "'";
			} else {
				message = "Type of expected value (" + expectedValue.getClass().getSimpleName() + ") of " + key +
						" is not supported for operator " + operator;
			}
			return createFailedAssertResult(message);
		}

		return handler.apply(key, actual, expectedValue, negate);
	}

	public static final String valueToString(Object value) {
		return value == null ? null : value.toString();
	}

	private AssertResult createFailedAssertResult(String message){
		AssertResult result = new AssertResult();
		result.setDescription(null);
		result.setMessage(message);
		result.setPassed(false);
		return result;
	}

	@Override
	public AssertReportNode createReportNode_(ReportNode parentNode, Assert artefact) {
		return new AssertReportNode();
	}

	private AssertOperatorHandler getOperatorHandler(AssertOperator operator) {
		AssertOperatorHandler handler = operatorHandlers.get(operator);
		if (handler == null) {
			throw new IllegalStateException("Handler is not defined for operator " + operator);
		}
		return handler;
	}

	private static class ValueResolvingResult {
		private Object actual = null;
		private boolean actualResolved = false;
		private String message = null;
		private ValueType type = null;
	}

	private enum ValueType {
		SIMPLE,
		JSON_PATH
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy