diff --git a/src/main/java/org/elasticsearch/watcher/condition/ConditionModule.java b/src/main/java/org/elasticsearch/watcher/condition/ConditionModule.java index 25c88544966..67b3d460fad 100644 --- a/src/main/java/org/elasticsearch/watcher/condition/ConditionModule.java +++ b/src/main/java/org/elasticsearch/watcher/condition/ConditionModule.java @@ -9,6 +9,8 @@ import org.elasticsearch.common.inject.AbstractModule; import org.elasticsearch.common.inject.multibindings.MapBinder; import org.elasticsearch.watcher.condition.always.AlwaysCondition; import org.elasticsearch.watcher.condition.always.AlwaysConditionFactory; +import org.elasticsearch.watcher.condition.compare.CompareCondition; +import org.elasticsearch.watcher.condition.compare.CompareConditionFactory; import org.elasticsearch.watcher.condition.never.NeverCondition; import org.elasticsearch.watcher.condition.never.NeverConditionFactory; import org.elasticsearch.watcher.condition.script.ScriptCondition; @@ -42,6 +44,9 @@ public class ConditionModule extends AbstractModule { bind(AlwaysConditionFactory.class).asEagerSingleton(); factoriesBinder.addBinding(AlwaysCondition.TYPE).to(AlwaysConditionFactory.class); + bind(CompareConditionFactory.class).asEagerSingleton(); + factoriesBinder.addBinding(CompareCondition.TYPE).to(CompareConditionFactory.class); + for (Map.Entry> entry : factories.entrySet()) { bind(entry.getValue()).asEagerSingleton(); factoriesBinder.addBinding(entry.getKey()).to(entry.getValue()); diff --git a/src/main/java/org/elasticsearch/watcher/condition/compare/CompareCondition.java b/src/main/java/org/elasticsearch/watcher/condition/compare/CompareCondition.java new file mode 100644 index 00000000000..30ce82f748d --- /dev/null +++ b/src/main/java/org/elasticsearch/watcher/condition/compare/CompareCondition.java @@ -0,0 +1,349 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.watcher.condition.compare; + +import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.joda.time.DateTime; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.watcher.condition.Condition; +import org.elasticsearch.watcher.support.WatcherDateUtils; +import org.elasticsearch.watcher.support.xcontent.WatcherXContentUtils; + +import java.io.IOException; +import java.util.Locale; +import java.util.Objects; + +import static org.elasticsearch.common.joda.time.DateTimeZone.UTC; + +/** + * + */ +public class CompareCondition implements Condition { + + public static final String TYPE = "compare"; + + private String path; + private Op op; + private Object value; + + public CompareCondition(String path, Op op, Object value) { + this.path = path; + this.op = op; + this.value = value; + } + + @Override + public final String type() { + return TYPE; + } + + public String getPath() { + return path; + } + + public Op getOp() { + return op; + } + + public Object getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + CompareCondition condition = (CompareCondition) o; + + if (!path.equals(condition.path)) return false; + if (op != condition.op) return false; + return !(value != null ? !value.equals(condition.value) : condition.value != null); + } + + @Override + public int hashCode() { + int result = path.hashCode(); + result = 31 * result + op.hashCode(); + result = 31 * result + (value != null ? value.hashCode() : 0); + return result; + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + return builder.startObject() + .startObject(path) + .field(op.id(), value) + .endObject() + .endObject(); + } + + public static CompareCondition parse(String watchId, XContentParser parser) throws IOException { + if (parser.currentToken() != XContentParser.Token.START_OBJECT) { + throw new CompareConditionException("could not parse [{}] condition for watch [{}]. expected an object but found [{}] instead", TYPE, watchId, parser.currentToken()); + } + String path = null; + Object value = null; + Op op = null; + + XContentParser.Token token; + while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { + if (token == XContentParser.Token.FIELD_NAME) { + path = parser.currentName(); + } else if (path == null) { + throw new CompareConditionException("could not parse [{}] condition for watch [{}]. expected a field indicating the compared path, but found [{}] instead", TYPE, watchId, token); + } else if (token == XContentParser.Token.START_OBJECT) { + token = parser.nextToken(); + if (token != XContentParser.Token.FIELD_NAME) { + throw new CompareConditionException("could not parse [{}] condition for watch [{}]. expected a field indicating the comparison operator, but found [{}] instead", TYPE, watchId, token); + } + try { + op = Op.resolve(parser.currentName()); + } catch (IllegalArgumentException iae) { + throw new CompareConditionException("could not parse [{}] condition for watch [{}]. unknown comparison operator [{}]", TYPE, watchId, parser.currentName()); + } + token = parser.nextToken(); + if (!op.supportsStructures() && !token.isValue() && token != XContentParser.Token.VALUE_NULL) { + throw new CompareConditionException("could not parse [{}] condition for watch [{}]. compared value for [{}] with operation [{}] must either be a numeric, string, boolean or null value, but found [{}] instead", TYPE, watchId, path, op.name().toLowerCase(Locale.ROOT), token); + } + value = WatcherXContentUtils.readValue(parser, token); + token = parser.nextToken(); + if (token != XContentParser.Token.END_OBJECT) { + throw new CompareConditionException("could not parse [{}] condition for watch [{}]. expected end of path object, but found [{}] instead", TYPE, watchId, token); + } + } else { + throw new CompareConditionException("could not parse [{}] condition for watch [{}]. expected an object for field [{}] but found [{}] instead", TYPE, watchId, path, token); + } + } + + return new CompareCondition(path, op, value); + } + + public static class Result extends Condition.Result { + + private final Object resolveValue; + + Result(Object resolveValue, boolean met) { + super(TYPE, met); + this.resolveValue = resolveValue; + } + + public Object getResolveValue() { + return resolveValue; + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + return builder.startObject() + .field(Field.MET.getPreferredName(), met) + .field(Field.RESOLVED_VALUE.getPreferredName(), resolveValue) + .endObject(); + } + + public static Result parse(String watchId, XContentParser parser) throws IOException { + Object resolvedValue = null; + boolean foundResolvedValue = false; + Boolean met = null; + + String currentFieldName = null; + XContentParser.Token token; + while((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { + if (token == XContentParser.Token.FIELD_NAME) { + currentFieldName = parser.currentName(); + } else if (currentFieldName == null) { + throw new CompareConditionException("could not parse condition result [{}] for watcher [{}]. expected a field but found [{}] instead", TYPE, watchId, token); + } else if (token == XContentParser.Token.VALUE_BOOLEAN) { + if (Field.MET.match(currentFieldName)) { + met = parser.booleanValue(); + } else { + throw new CompareConditionException("could not parse condition result [{}] for watcher [{}]. unexpected boolean field [{}]", TYPE, watchId, currentFieldName); + } + } else if (Field.RESOLVED_VALUE.match(currentFieldName)) { + resolvedValue = WatcherXContentUtils.readValue(parser, token); + foundResolvedValue = true; + } else { + throw new CompareConditionException("could not parse condition result [{}] for watcher [{}]. unexpected field [{}]", TYPE, watchId, currentFieldName); + } + } + + if (!foundResolvedValue) { + throw new CompareConditionException("could not parse condition result [{}] for watcher [{}]. missing required field [{}]", TYPE, watchId, Field.RESOLVED_VALUE.getPreferredName()); + } + + return new Result(resolvedValue, met); + } + } + + public enum Op { + + EQ() { + @Override + public boolean eval(Object v1, Object v2) { + Integer compVal = compare(v1, v2); + return compVal != null && compVal == 0; + } + + @Override + public boolean supportsStructures() { + return true; + } + }, + NOT_EQ() { + @Override + public boolean eval(Object v1, Object v2) { + Integer compVal = compare(v1, v2); + return compVal == null || compVal != 0; + } + + @Override + public boolean supportsStructures() { + return true; + } + }, + LT() { + @Override + public boolean eval(Object v1, Object v2) { + Integer compVal = compare(v1, v2); + return compVal != null && compVal < 0; + } + }, + LTE() { + @Override + public boolean eval(Object v1, Object v2) { + Integer compVal = compare(v1, v2); + return compVal != null && compVal <= 0; + } + }, + GT() { + @Override + public boolean eval(Object v1, Object v2) { + Integer compVal = compare(v1, v2); + return compVal != null && compVal > 0; + } + }, + GTE() { + @Override + public boolean eval(Object v1, Object v2) { + Integer compVal = compare(v1, v2); + return compVal != null && compVal >= 0; + } + }; + + public abstract boolean eval(Object v1, Object v2); + + public boolean supportsStructures() { + return false; + } + + // this method performs lenient comparison, potentially between different types. The second argument + // type (v2) determines the type of comparison (this is because the second argument is configured by the + // user while the first argument is the dynamic path that is evaluated at runtime. That is, if the user configures + // a number, it expects a number, therefore the comparison will be based on numeric comparison). If the + // comparison is numeric, other types (e.g. strings) will converted to numbers if possible, if not, the comparison + // will fail and `false` will be returned. + // + // may return `null` indicating v1 simply doesn't equal v2 (without any order association) + static Integer compare(Object v1, Object v2) { + if (Objects.equals(v1, v2)) { + return 0; + } + if (v1 == null || v2 == null) { + return null; + } + + // special case for numbers. If v1 is not a number, we'll try to convert it to a number + if (v2 instanceof Number) { + if (!(v1 instanceof Number)) { + try { + v1 = Double.valueOf(String.valueOf(v1)); + } catch (NumberFormatException nfe) { + // could not convert to number + return null; + } + } + return ((Number) v1).doubleValue() > ((Number) v2).doubleValue() ? 1 : + ((Number) v1).doubleValue() < ((Number) v2).doubleValue() ? -1 : 0; + } + + // special case for strings. If v1 is not a string, we'll convert it to a string + if (v2 instanceof String) { + v1 = String.valueOf(v1); + return ((String) v1).compareTo((String) v2); + } + + // special case for date/times. If v1 is not a dateTime, we'll try to convert it to a datetime + if (v2 instanceof DateTime) { + if (v1 instanceof DateTime) { + return ((DateTime) v1).compareTo((DateTime) v2); + } + if (v1 instanceof String) { + try { + v1 = WatcherDateUtils.parseDate((String) v1); + } catch (Exception e) { + return null; + } + } else if (v1 instanceof Number){ + v1 = new DateTime(((Number) v1).longValue(), UTC); + } else { + // cannot convert to date... + return null; + } + return ((DateTime) v1).compareTo((DateTime) v2); + } + + if (v1.getClass() != v2.getClass() || Comparable.class.isAssignableFrom(v1.getClass())) { + return null; + } + + try { + return ((Comparable) v1).compareTo(v2); + } catch (Exception e) { + return null; + } + } + + public String id() { + return name().toLowerCase(Locale.ROOT); + } + + public static Op resolve(String id) { + return Op.valueOf(id.toUpperCase(Locale.ROOT)); + } + } + + public static class Builder implements Condition.Builder { + + private String path; + private Op op; + private Object value; + + public Builder(String path, Op op, Object value) { + this.path = path; + this.op = op; + this.value = value; + } + + public CompareCondition build() { + return new CompareCondition(path, op, value); + } + } + + public static class EvaluationException extends CompareConditionException { + + public EvaluationException(String msg, Object... args) { + super(msg, args); + } + + public EvaluationException(String msg, Throwable cause, Object... args) { + super(msg, cause, args); + } + } + + interface Field extends Condition.Field { + ParseField RESOLVED_VALUE = new ParseField("resolved_value"); + } +} diff --git a/src/main/java/org/elasticsearch/watcher/condition/compare/CompareConditionException.java b/src/main/java/org/elasticsearch/watcher/condition/compare/CompareConditionException.java new file mode 100644 index 00000000000..3235cc2d735 --- /dev/null +++ b/src/main/java/org/elasticsearch/watcher/condition/compare/CompareConditionException.java @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.watcher.condition.compare; + +import org.elasticsearch.watcher.condition.ConditionException; + +/** + * + */ +public class CompareConditionException extends ConditionException { + + public CompareConditionException(String msg, Object... args) { + super(msg, args); + } + + public CompareConditionException(String msg, Throwable cause, Object... args) { + super(msg, cause, args); + } +} diff --git a/src/main/java/org/elasticsearch/watcher/condition/compare/CompareConditionFactory.java b/src/main/java/org/elasticsearch/watcher/condition/compare/CompareConditionFactory.java new file mode 100644 index 00000000000..f7949ceea88 --- /dev/null +++ b/src/main/java/org/elasticsearch/watcher/condition/compare/CompareConditionFactory.java @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.watcher.condition.compare; + +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.joda.DateMathParser; +import org.elasticsearch.common.logging.Loggers; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.watcher.condition.ConditionFactory; +import org.elasticsearch.watcher.support.WatcherDateUtils; +import org.elasticsearch.watcher.support.clock.Clock; + +import java.io.IOException; + +/** + * + */ +public class CompareConditionFactory extends ConditionFactory { + + private final Clock clock; + + @Inject + public CompareConditionFactory(Settings settings, Clock clock) { + super(Loggers.getLogger(ExecutableCompareCondition.class, settings)); + this.clock = clock; + } + + @Override + public String type() { + return CompareCondition.TYPE; + } + + @Override + public CompareCondition parseCondition(String watchId, XContentParser parser) throws IOException { + return CompareCondition.parse(watchId, parser); + } + + @Override + public CompareCondition.Result parseResult(String watchId, XContentParser parser) throws IOException { + return CompareCondition.Result.parse(watchId, parser); + } + + @Override + public ExecutableCompareCondition createExecutable(CompareCondition condition) { + return new ExecutableCompareCondition(condition, conditionLogger, clock); + } +} diff --git a/src/main/java/org/elasticsearch/watcher/condition/compare/ExecutableCompareCondition.java b/src/main/java/org/elasticsearch/watcher/condition/compare/ExecutableCompareCondition.java new file mode 100644 index 00000000000..52917616a87 --- /dev/null +++ b/src/main/java/org/elasticsearch/watcher/condition/compare/ExecutableCompareCondition.java @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.watcher.condition.compare; + +import org.elasticsearch.common.logging.ESLogger; +import org.elasticsearch.watcher.condition.ExecutableCondition; +import org.elasticsearch.watcher.execution.WatchExecutionContext; +import org.elasticsearch.watcher.support.Variables; +import org.elasticsearch.watcher.support.WatcherDateUtils; +import org.elasticsearch.watcher.support.clock.Clock; +import org.elasticsearch.watcher.support.xcontent.MapPath; + +import java.io.IOException; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static org.elasticsearch.common.joda.time.DateTimeZone.UTC; + +/** + * + */ +public class ExecutableCompareCondition extends ExecutableCondition { + + static final Pattern DATE_MATH_PATTERN = Pattern.compile("<\\{(.+)\\}>"); + static final Pattern PATH_PATTERN = Pattern.compile("\\{\\{(.+)\\}\\}"); + + + private final Clock clock; + + public ExecutableCompareCondition(CompareCondition condition, ESLogger logger, Clock clock) { + super(condition, logger); + this.clock = clock; + } + + @Override + public CompareCondition.Result execute(WatchExecutionContext ctx) throws IOException { + Map model = Variables.createCtxModel(ctx, ctx.payload()); + + Object configuredValue = condition.getValue(); + + if (configuredValue instanceof String) { + + // checking if the given value is a date math expression + Matcher matcher = DATE_MATH_PATTERN.matcher((String) configuredValue); + if (matcher.matches()) { + String dateMath = matcher.group(1); + configuredValue = WatcherDateUtils.parseDateMath(dateMath, UTC, clock); + } else { + // checking if the given value is a path expression + matcher = PATH_PATTERN.matcher((String) configuredValue); + if (matcher.matches()) { + String configuredPath = matcher.group(1); + configuredValue = MapPath.eval(configuredPath, model); + } + } + } + + Object resolvedValue = MapPath.eval(condition.getPath(), model); + + return new CompareCondition.Result(resolvedValue, condition.getOp().eval(resolvedValue, configuredValue)); + } +} diff --git a/src/main/java/org/elasticsearch/watcher/support/xcontent/MapPath.java b/src/main/java/org/elasticsearch/watcher/support/xcontent/MapPath.java new file mode 100644 index 00000000000..8fe1c8df285 --- /dev/null +++ b/src/main/java/org/elasticsearch/watcher/support/xcontent/MapPath.java @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.watcher.support.xcontent; + +import org.elasticsearch.common.Strings; + +import java.lang.reflect.Array; +import java.util.List; +import java.util.Map; + +/** + * + */ +public class MapPath { + + private MapPath() { + } + + public static T eval(String path, Map map) { + return (T) eval(path, (Object) map); + } + + private static Object eval(String path, Object ctx) { + String[] parts = Strings.splitStringToArray(path, '.'); + StringBuilder resolved = new StringBuilder(); + for (String part : parts) { + if (ctx == null) { + return null; + } + if (ctx instanceof Map) { + ctx = ((Map) ctx).get(part); + if (resolved.length() != 0) { + resolved.append("."); + } + resolved.append(part); + } else if (ctx instanceof List) { + try { + int index = Integer.parseInt(part); + ctx = ((List) ctx).get(index); + if (resolved.length() != 0) { + resolved.append("."); + } + resolved.append(part); + } catch (NumberFormatException nfe) { + return null; + } + } else if (ctx.getClass().isArray()) { + try { + int index = Integer.parseInt(part); + ctx = Array.get(ctx, index); + if (resolved.length() != 0) { + resolved.append("."); + } + resolved.append(part); + } catch (NumberFormatException nfe) { + return null; + } + } else { + return null; + } + } + return ctx; + } +} diff --git a/src/main/java/org/elasticsearch/watcher/support/xcontent/WatcherXContentUtils.java b/src/main/java/org/elasticsearch/watcher/support/xcontent/WatcherXContentUtils.java new file mode 100644 index 00000000000..0fc05938108 --- /dev/null +++ b/src/main/java/org/elasticsearch/watcher/support/xcontent/WatcherXContentUtils.java @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.watcher.support.xcontent; + +import org.elasticsearch.common.xcontent.XContentParser; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * + */ +public class WatcherXContentUtils { + + private WatcherXContentUtils() { + } + + // TODO open this up in core + public static List readList(XContentParser parser, XContentParser.Token token) throws IOException { + ArrayList list = new ArrayList<>(); + while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { + list.add(readValue(parser, token)); + } + return list; + } + + // TODO open this up in core + public static Object readValue(XContentParser parser, XContentParser.Token token) throws IOException { + if (token == XContentParser.Token.VALUE_NULL) { + return null; + } else if (token == XContentParser.Token.VALUE_STRING) { + return parser.text(); + } else if (token == XContentParser.Token.VALUE_NUMBER) { + XContentParser.NumberType numberType = parser.numberType(); + if (numberType == XContentParser.NumberType.INT) { + return parser.intValue(); + } else if (numberType == XContentParser.NumberType.LONG) { + return parser.longValue(); + } else if (numberType == XContentParser.NumberType.FLOAT) { + return parser.floatValue(); + } else if (numberType == XContentParser.NumberType.DOUBLE) { + return parser.doubleValue(); + } + } else if (token == XContentParser.Token.VALUE_BOOLEAN) { + return parser.booleanValue(); + } else if (token == XContentParser.Token.START_OBJECT) { + return parser.map(); + } else if (token == XContentParser.Token.START_ARRAY) { + return readList(parser, token); + } else if (token == XContentParser.Token.VALUE_EMBEDDED_OBJECT) { + return parser.binaryValue(); + } + return null; + } +} diff --git a/src/main/java/org/elasticsearch/watcher/support/xcontent/XContentSource.java b/src/main/java/org/elasticsearch/watcher/support/xcontent/XContentSource.java index b7609b90efe..1e099e9aef2 100644 --- a/src/main/java/org/elasticsearch/watcher/support/xcontent/XContentSource.java +++ b/src/main/java/org/elasticsearch/watcher/support/xcontent/XContentSource.java @@ -11,7 +11,6 @@ import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.xcontent.*; -import org.elasticsearch.common.xcontent.support.XContentMapValues; import java.io.IOException; import java.util.Map; @@ -59,7 +58,7 @@ public class XContentSource implements ToXContent { * @return The extracted value or {@code null} if no value is associated with the given path */ public T getValue(String path) { - return (T) XContentMapValues.extractValue(path, getAsMap()); + return (T) MapPath.eval(path, getAsMap()); } @Override diff --git a/src/main/resources/watch_history.json b/src/main/resources/watch_history.json index 42c874329d2..9bc67c50a50 100644 --- a/src/main/resources/watch_history.json +++ b/src/main/resources/watch_history.json @@ -128,6 +128,20 @@ } } }, + "condition" : { + "type" : "object", + "dynamic" : true, + "properties" : { + "compare" : { + "type" : "object", + "enabled" : false + }, + "script" : { + "type" : "object", + "enabled" : false + } + } + }, "transform" : { "type" : "object", "dynamic" : true, diff --git a/src/test/java/org/elasticsearch/watcher/condition/compare/CompareConditionSearchTests.java b/src/test/java/org/elasticsearch/watcher/condition/compare/CompareConditionSearchTests.java new file mode 100644 index 00000000000..90237d0ef57 --- /dev/null +++ b/src/test/java/org/elasticsearch/watcher/condition/compare/CompareConditionSearchTests.java @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.watcher.condition.compare; + +import org.elasticsearch.action.search.SearchResponse; +import org.elasticsearch.action.search.ShardSearchFailure; +import org.elasticsearch.common.text.StringText; +import org.elasticsearch.search.SearchShardTarget; +import org.elasticsearch.search.aggregations.AggregationBuilders; +import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogram; +import org.elasticsearch.search.aggregations.bucket.histogram.Histogram; +import org.elasticsearch.search.internal.InternalSearchHit; +import org.elasticsearch.search.internal.InternalSearchHits; +import org.elasticsearch.search.internal.InternalSearchResponse; +import org.elasticsearch.watcher.execution.WatchExecutionContext; +import org.elasticsearch.watcher.support.clock.SystemClock; +import org.elasticsearch.watcher.test.AbstractWatcherIntegrationTests; +import org.elasticsearch.watcher.watch.Payload; +import org.junit.Test; + +import static org.elasticsearch.watcher.test.WatcherTestUtils.mockExecutionContext; +import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.when; + +/** + */ +public class CompareConditionSearchTests extends AbstractWatcherIntegrationTests { + + @Test + public void testExecute_withAggs() throws Exception { + + client().admin().indices().prepareCreate("my-index") + .addMapping("my-type", "_timestamp", "enabled=true") + .get(); + + client().prepareIndex("my-index", "my-type").setTimestamp("2005-01-01T00:00").setSource("{}").get(); + client().prepareIndex("my-index", "my-type").setTimestamp("2005-01-01T00:10").setSource("{}").get(); + client().prepareIndex("my-index", "my-type").setTimestamp("2005-01-01T00:20").setSource("{}").get(); + client().prepareIndex("my-index", "my-type").setTimestamp("2005-01-01T00:30").setSource("{}").get(); + refresh(); + + SearchResponse response = client().prepareSearch("my-index") + .addAggregation(AggregationBuilders.dateHistogram("rate").field("_timestamp").interval(DateHistogram.Interval.HOUR).order(Histogram.Order.COUNT_DESC)) + .get(); + + ExecutableCompareCondition condition = new ExecutableCompareCondition(new CompareCondition("ctx.payload.aggregations.rate.buckets.0.doc_count", CompareCondition.Op.GTE, 5), logger, SystemClock.INSTANCE); + + WatchExecutionContext ctx = mockExecutionContext("_name", new Payload.XContent(response)); + assertThat(condition.execute(ctx).met(), is(false)); + + client().prepareIndex("my-index", "my-type").setTimestamp("2005-01-01T00:40").setSource("{}").get(); + refresh(); + + response = client().prepareSearch("my-index") + .addAggregation(AggregationBuilders.dateHistogram("rate").field("_timestamp").interval(DateHistogram.Interval.HOUR).order(Histogram.Order.COUNT_DESC)) + .get(); + + ctx = mockExecutionContext("_name", new Payload.XContent(response)); + assertThat(condition.execute(ctx).met(), is(true)); + } + + @Test + public void testExecute_accessHits() throws Exception { + ExecutableCompareCondition condition = new ExecutableCompareCondition(new CompareCondition("ctx.payload.hits.hits.0._score", CompareCondition.Op.EQ, 1), logger, SystemClock.INSTANCE); + InternalSearchHit hit = new InternalSearchHit(0, "1", new StringText("type"), null); + hit.score(1f); + hit.shard(new SearchShardTarget("a", "a", 0)); + + InternalSearchResponse internalSearchResponse = new InternalSearchResponse(new InternalSearchHits(new InternalSearchHit[]{hit}, 1l, 1f), null, null, null, false, null); + SearchResponse response = new SearchResponse(internalSearchResponse, "", 3, 3, 500l, new ShardSearchFailure[0]); + + WatchExecutionContext ctx = mockExecutionContext("_watch_name", new Payload.XContent(response)); + assertThat(condition.execute(ctx).met(), is(true)); + hit.score(2f); + when(ctx.payload()).thenReturn(new Payload.XContent(response)); + assertThat(condition.execute(ctx).met(), is(false)); + } + +} diff --git a/src/test/java/org/elasticsearch/watcher/condition/compare/CompareConditionTests.java b/src/test/java/org/elasticsearch/watcher/condition/compare/CompareConditionTests.java new file mode 100644 index 00000000000..6faa98e2674 --- /dev/null +++ b/src/test/java/org/elasticsearch/watcher/condition/compare/CompareConditionTests.java @@ -0,0 +1,286 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.watcher.condition.compare; + +import com.carrotsearch.randomizedtesting.annotations.Repeat; +import com.carrotsearch.randomizedtesting.annotations.Seed; +import org.elasticsearch.common.collect.ImmutableList; +import org.elasticsearch.common.collect.ImmutableMap; +import org.elasticsearch.common.joda.time.DateTime; +import org.elasticsearch.common.settings.ImmutableSettings; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.json.JsonXContent; +import org.elasticsearch.test.ElasticsearchTestCase; +import org.elasticsearch.watcher.condition.compare.CompareCondition.Op; +import org.elasticsearch.watcher.execution.WatchExecutionContext; +import org.elasticsearch.watcher.support.clock.ClockMock; +import org.elasticsearch.watcher.support.clock.SystemClock; +import org.elasticsearch.watcher.watch.Payload; +import org.junit.Test; + +import java.util.Locale; + +import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; +import static org.elasticsearch.watcher.test.WatcherTestUtils.mockExecutionContext; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; + +/** + */ +@Seed("9E70A915296AA3F2:FAA20587D7DCA86B") +public class CompareConditionTests extends ElasticsearchTestCase { + + @Test + public void testOpEval_EQ() throws Exception { + assertThat(Op.EQ.eval(null, null), is(true)); + assertThat(Op.EQ.eval(4, 3.0), is(false)); + assertThat(Op.EQ.eval(3, 3.0), is(true)); + assertThat(Op.EQ.eval(2, new Float(3.0)), is(false)); + assertThat(Op.EQ.eval(3, null), is(false)); + assertThat(Op.EQ.eval(2, "2"), is(true)); // comparing as strings + assertThat(Op.EQ.eval(3, "4"), is(false)); // comparing as strings + assertThat(Op.EQ.eval(3, "a"), is(false)); // comparing as strings + assertThat(Op.EQ.eval("3", 3), is(true)); // comparing as numbers + assertThat(Op.EQ.eval("a", "aa"), is(false)); + assertThat(Op.EQ.eval("a", "a"), is(true)); + assertThat(Op.EQ.eval("aa", "ab"), is(false)); + assertThat(Op.EQ.eval(ImmutableMap.of("k", "v"), ImmutableMap.of("k", "v")), is(true)); + assertThat(Op.EQ.eval(ImmutableMap.of("k", "v"), ImmutableMap.of("k1", "v1")), is(false)); + assertThat(Op.EQ.eval(ImmutableList.of("k", "v"), ImmutableList.of("k", "v")), is(true)); + assertThat(Op.EQ.eval(ImmutableList.of("k", "v"), ImmutableList.of("k1", "v1")), is(false)); + } + + @Test + public void testOpEval_NOT_EQ() throws Exception { + assertThat(Op.NOT_EQ.eval(null, null), is(false)); + assertThat(Op.NOT_EQ.eval(4, 3.0), is(true)); + assertThat(Op.NOT_EQ.eval(3, 3.0), is(false)); + assertThat(Op.NOT_EQ.eval(2, new Float(3.0)), is(true)); + assertThat(Op.NOT_EQ.eval(3, null), is(true)); + assertThat(Op.NOT_EQ.eval(2, "2"), is(false)); // comparing as strings + assertThat(Op.NOT_EQ.eval(3, "4"), is(true)); // comparing as strings + assertThat(Op.NOT_EQ.eval(3, "a"), is(true)); // comparing as strings + assertThat(Op.NOT_EQ.eval("3", 3), is(false)); // comparing as numbers + assertThat(Op.NOT_EQ.eval("a", "aa"), is(true)); + assertThat(Op.NOT_EQ.eval("a", "a"), is(false)); + assertThat(Op.NOT_EQ.eval("aa", "ab"), is(true)); + assertThat(Op.NOT_EQ.eval(ImmutableMap.of("k", "v"), ImmutableMap.of("k", "v")), is(false)); + assertThat(Op.NOT_EQ.eval(ImmutableMap.of("k", "v"), ImmutableMap.of("k1", "v1")), is(true)); + assertThat(Op.NOT_EQ.eval(ImmutableList.of("k", "v"), ImmutableList.of("k", "v")), is(false)); + assertThat(Op.NOT_EQ.eval(ImmutableList.of("k", "v"), ImmutableList.of("k1", "v1")), is(true)); + } + + @Test + public void testOpEval_GTE() throws Exception { + assertThat(Op.GTE.eval(4, 3.0), is(true)); + assertThat(Op.GTE.eval(3, 3.0), is(true)); + assertThat(Op.GTE.eval(2, new Float(3.0)), is(false)); + assertThat(Op.GTE.eval(3, null), is(false)); + assertThat(Op.GTE.eval(3, "2"), is(true)); // comparing as strings + assertThat(Op.GTE.eval(3, "4"), is(false)); // comparing as strings + assertThat(Op.GTE.eval(3, "a"), is(false)); // comparing as strings + assertThat(Op.GTE.eval("4", 3), is(true)); // comparing as numbers + assertThat(Op.GTE.eval("a", "aa"), is(false)); + assertThat(Op.GTE.eval("a", "a"), is(true)); + assertThat(Op.GTE.eval("aa", "ab"), is(false)); + } + + @Test + public void testOpEval_GT() throws Exception { + assertThat(Op.GT.eval(4, 3.0), is(true)); + assertThat(Op.GT.eval(3, 3.0), is(false)); + assertThat(Op.GT.eval(2, new Float(3.0)), is(false)); + assertThat(Op.GT.eval(3, null), is(false)); + assertThat(Op.GT.eval(3, "2"), is(true)); // comparing as strings + assertThat(Op.GT.eval(3, "4"), is(false)); // comparing as strings + assertThat(Op.GT.eval(3, "a"), is(false)); // comparing as strings + assertThat(Op.GT.eval("4", 3), is(true)); // comparing as numbers + assertThat(Op.GT.eval("a", "aa"), is(false)); + assertThat(Op.GT.eval("a", "a"), is(false)); + assertThat(Op.GT.eval("aa", "ab"), is(false)); + } + + @Test + public void testOpEval_LTE() throws Exception { + assertThat(Op.LTE.eval(4, 3.0), is(false)); + assertThat(Op.LTE.eval(3, 3.0), is(true)); + assertThat(Op.LTE.eval(2, new Float(3.0)), is(true)); + assertThat(Op.LTE.eval(3, null), is(false)); + assertThat(Op.LTE.eval(3, "2"), is(false)); // comparing as strings + assertThat(Op.LTE.eval(3, "4"), is(true)); // comparing as strings + assertThat(Op.LTE.eval(3, "a"), is(true)); // comparing as strings + assertThat(Op.LTE.eval("4", 3), is(false)); // comparing as numbers + assertThat(Op.LTE.eval("a", "aa"), is(true)); + assertThat(Op.LTE.eval("a", "a"), is(true)); + assertThat(Op.LTE.eval("aa", "ab"), is(true)); + } + + @Test + public void testOpEval_LT() throws Exception { + assertThat(Op.LT.eval(4, 3.0), is(false)); + assertThat(Op.LT.eval(3, 3.0), is(false)); + assertThat(Op.LT.eval(2, new Float(3.0)), is(true)); + assertThat(Op.LT.eval(3, null), is(false)); + assertThat(Op.LT.eval(3, "2"), is(false)); // comparing as strings + assertThat(Op.LT.eval(3, "4"), is(true)); // comparing as strings + assertThat(Op.LT.eval(3, "a"), is(true)); // comparing as strings + assertThat(Op.LT.eval("4", 3), is(false)); // comparing as numbers + assertThat(Op.LT.eval("a", "aa"), is(true)); + assertThat(Op.LT.eval("a", "a"), is(false)); + assertThat(Op.LT.eval("aa", "ab"), is(true)); + } + + @Test @Repeat(iterations = 10) + public void testExecute() throws Exception { + Op op = randomFrom(Op.values()); + int value = randomInt(10); + int payloadValue = randomInt(10); + boolean met = op.eval(payloadValue, value); + + ExecutableCompareCondition condition = new ExecutableCompareCondition(new CompareCondition("ctx.payload.value", op, value), logger, SystemClock.INSTANCE); + WatchExecutionContext ctx = mockExecutionContext("_name", new Payload.Simple("value", payloadValue)); + assertThat(condition.execute(ctx).met(), is(met)); + } + + @Test @Repeat(iterations = 10) + public void testExecute_DateMath() throws Exception { + ClockMock clock = new ClockMock(); + boolean met = randomBoolean(); + Op op = met ? randomFrom(Op.GT, Op.GTE, Op.NOT_EQ) : randomFrom(Op.LT, Op.LTE, Op.EQ); + String value = "<{now-1d}>"; + DateTime payloadValue = clock.now(); + + ExecutableCompareCondition condition = new ExecutableCompareCondition(new CompareCondition("ctx.payload.value", op, value), logger, clock); + WatchExecutionContext ctx = mockExecutionContext("_name", new Payload.Simple("value", payloadValue)); + assertThat(condition.execute(ctx).met(), is(met)); + } + + @Test @Repeat(iterations = 5) + public void testExecute_Path() throws Exception { + ClockMock clock = new ClockMock(); + boolean met = randomBoolean(); + Op op = met ? Op.EQ : Op.NOT_EQ; + String value = "{{ctx.payload.value}}"; + Object payloadValue = new Object(); + + ExecutableCompareCondition condition = new ExecutableCompareCondition(new CompareCondition("ctx.payload.value", op, value), logger, clock); + WatchExecutionContext ctx = mockExecutionContext("_name", new Payload.Simple("value", payloadValue)); + assertThat(condition.execute(ctx).met(), is(met)); + } + + + @Test @Repeat(iterations = 10) + public void testParse_Valid() throws Exception { + Op op = randomFrom(Op.values()); + Object value = randomFrom("value", 1, null); + CompareConditionFactory factory = new CompareConditionFactory(ImmutableSettings.EMPTY, SystemClock.INSTANCE); + XContentBuilder builder = jsonBuilder(); + builder.startObject(); + builder.startObject("key1.key2"); + builder.field(op.name().toLowerCase(Locale.ROOT), value); + builder.endObject(); + builder.endObject(); + + XContentParser parser = JsonXContent.jsonXContent.createParser(builder.bytes()); + parser.nextToken(); + + CompareCondition condition = factory.parseCondition("_id", parser); + + assertThat(condition, notNullValue()); + assertThat(condition.getPath(), is("key1.key2")); + assertThat(condition.getOp(), is(op)); + assertThat(condition.getValue(), is(value)); + } + + @Test(expected = CompareConditionException.class) + public void testParse_InValid_NoOperationBody() throws Exception { + CompareConditionFactory factory = new CompareConditionFactory(ImmutableSettings.EMPTY, SystemClock.INSTANCE); + XContentBuilder builder = jsonBuilder(); + builder.startObject(); + builder.startObject("key1.key2"); + builder.endObject(); + builder.endObject(); + + XContentParser parser = JsonXContent.jsonXContent.createParser(builder.bytes()); + factory.parseCondition("_id", parser); + } + + @Test(expected = CompareConditionException.class) + public void testParse_InValid_UnknownOp() throws Exception { + Object value = randomFrom("value", 1, null); + CompareConditionFactory factory = new CompareConditionFactory(ImmutableSettings.EMPTY, SystemClock.INSTANCE); + XContentBuilder builder = jsonBuilder(); + builder.startObject(); + builder.startObject("key1.key2"); + builder.field("foobar", value); + builder.endObject(); + builder.endObject(); + + XContentParser parser = JsonXContent.jsonXContent.createParser(builder.bytes()); + parser.nextToken(); + + factory.parseCondition("_id", parser); + } + + @Test(expected = CompareConditionException.class) @Repeat(iterations = 10) + public void testParse_InValid_WrongValueForOp() throws Exception { + Object value = randomFrom(ImmutableList.of("1", "2"), ImmutableMap.of("key", "value")); + String op = randomFrom("lt", "lte", "gt", "gte"); + CompareConditionFactory factory = new CompareConditionFactory(ImmutableSettings.EMPTY, SystemClock.INSTANCE); + XContentBuilder builder = jsonBuilder(); + builder.startObject(); + builder.startObject("key1.key2"); + builder.field(op, value); + builder.endObject(); + builder.endObject(); + + XContentParser parser = JsonXContent.jsonXContent.createParser(builder.bytes()); + parser.nextToken(); + + factory.parseCondition("_id", parser); + } + + + @Test @Repeat(iterations = 10) + public void testParse_Result_Valid() throws Exception { + CompareConditionFactory factory = new CompareConditionFactory(ImmutableSettings.EMPTY, SystemClock.INSTANCE); + + boolean met = randomBoolean(); + Object resolvedValue = randomFrom("1", 5, null, ImmutableList.of("1", "2"), ImmutableMap.of("key", "value")); + + XContentBuilder builder = jsonBuilder(); + builder.startObject(); + builder.field("met", met); + builder.field("resolved_value", resolvedValue); + builder.endObject(); + + XContentParser parser = JsonXContent.jsonXContent.createParser(builder.bytes()); + parser.nextToken(); + + CompareCondition.Result result = factory.parseResult("_id", parser); + assertThat(result, notNullValue()); + assertThat(result.met(), is(met)); + assertThat(result.getResolveValue(), is(resolvedValue)); + } + + @Test(expected = CompareConditionException.class) + public void testParse_Result_Invalid_MissingResolvedValue() throws Exception { + CompareConditionFactory factory = new CompareConditionFactory(ImmutableSettings.EMPTY, SystemClock.INSTANCE); + + boolean met = randomBoolean(); + + XContentBuilder builder = jsonBuilder(); + builder.startObject(); + builder.field("met", met); + builder.endObject(); + + XContentParser parser = JsonXContent.jsonXContent.createParser(builder.bytes()); + parser.nextToken(); + + factory.parseResult("_id", parser); + } +} diff --git a/src/test/java/org/elasticsearch/watcher/support/xcontent/MapPathTests.java b/src/test/java/org/elasticsearch/watcher/support/xcontent/MapPathTests.java new file mode 100644 index 00000000000..d7c959c4f43 --- /dev/null +++ b/src/test/java/org/elasticsearch/watcher/support/xcontent/MapPathTests.java @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.watcher.support.xcontent; + +import com.carrotsearch.randomizedtesting.annotations.Repeat; +import org.elasticsearch.common.collect.ImmutableList; +import org.elasticsearch.common.collect.ImmutableMap; +import org.elasticsearch.test.ElasticsearchTestCase; +import org.junit.Test; + +import java.util.List; +import java.util.Map; + +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.Matchers.is; + +/** + * + */ +public class MapPathTests extends ElasticsearchTestCase { + + @Test + public void testEval() throws Exception { + Map map = ImmutableMap.builder() + .put("key", "value") + .build(); + + assertThat(MapPath.eval("key", map), is((Object) "value")); + assertThat(MapPath.eval("key1", map), nullValue()); + } + + @Test @Repeat(iterations = 5) + public void testEval_List() throws Exception { + List list = ImmutableList.of(1, 2, 3, 4); + Map map = ImmutableMap.builder() + .put("key", list) + .build(); + + int index = randomInt(3); + assertThat(MapPath.eval("key." + index, map), is(list.get(index))); + } + + @Test @Repeat(iterations = 5) + public void testEval_Array() throws Exception { + int[] array = new int[] { 1, 2, 3, 4 }; + Map map = ImmutableMap.builder() + .put("key", array) + .build(); + + int index = randomInt(3); + assertThat(((Number) MapPath.eval("key." + index, map)).intValue(), is(array[index])); + } + + @Test + public void testEval_Map() throws Exception { + Map map = ImmutableMap.builder() + .put("a", ImmutableMap.of("b", "val")) + .build(); + + assertThat(MapPath.eval("a.b", map), is((Object) "val")); + } + + + @Test + public void testEval_Mixed() throws Exception { + Map map = ImmutableMap.builder() + .put("a", ImmutableMap.builder() + .put("b", ImmutableList.builder() + .add(ImmutableList.builder() + .add(ImmutableMap.builder() + .put("c", "val") + .build()) + .build()) + .build()) + .build()) + .build(); + + assertThat(MapPath.eval("", map), is((Object) map)); + assertThat(MapPath.eval("a.b.0.0.c", map), is((Object) "val")); + assertThat(MapPath.eval("a.b.0.0.c.d", map), nullValue()); + assertThat(MapPath.eval("a.b.0.0.d", map), nullValue()); + assertThat(MapPath.eval("a.b.c", map), nullValue()); + + } +} diff --git a/src/test/java/org/elasticsearch/watcher/test/integration/EmailSecretsIntegrationTests.java b/src/test/java/org/elasticsearch/watcher/test/integration/EmailSecretsIntegrationTests.java index b72954037c4..437f77d03da 100644 --- a/src/test/java/org/elasticsearch/watcher/test/integration/EmailSecretsIntegrationTests.java +++ b/src/test/java/org/elasticsearch/watcher/test/integration/EmailSecretsIntegrationTests.java @@ -144,11 +144,8 @@ public class EmailSecretsIntegrationTests extends AbstractWatcherIntegrationTest .get(); assertThat(executeResponse, notNullValue()); contentSource = executeResponse.getSource(); - value = contentSource.getValue("execution_result.actions.email.success"); - assertThat(value, instanceOf(List.class)); - List values = (List) value; - assertThat(values, hasSize(1)); - assertThat(values, hasItem(Boolean.TRUE)); + value = contentSource.getValue("execution_result.actions.0.email.success"); + assertThat((Boolean) value, is(true)); if (!latch.await(5, TimeUnit.SECONDS)) { fail("waiting too long for the email to be sent"); diff --git a/src/test/java/org/elasticsearch/watcher/test/integration/HttpSecretsIntegrationTests.java b/src/test/java/org/elasticsearch/watcher/test/integration/HttpSecretsIntegrationTests.java index 88f54772555..25ebe303bf7 100644 --- a/src/test/java/org/elasticsearch/watcher/test/integration/HttpSecretsIntegrationTests.java +++ b/src/test/java/org/elasticsearch/watcher/test/integration/HttpSecretsIntegrationTests.java @@ -32,7 +32,6 @@ import org.junit.Before; import org.junit.Test; import java.net.BindException; -import java.util.List; import java.util.Map; import static org.elasticsearch.common.joda.time.DateTimeZone.UTC; @@ -226,24 +225,18 @@ public class HttpSecretsIntegrationTests extends AbstractWatcherIntegrationTests assertThat(executeResponse, notNullValue()); contentSource = executeResponse.getSource(); - value = contentSource.getValue("execution_result.actions.webhook.response.status"); - assertThat(value, instanceOf(List.class)); + value = contentSource.getValue("execution_result.actions.0.webhook.response.status"); assertThat(value, notNullValue()); - List values = (List) value; - assertThat(values, hasSize(1)); - assertThat(values, hasItem(200)); + assertThat(value, instanceOf(Number.class)); + assertThat(((Number) value).intValue(), is(200)); - value = contentSource.getValue("execution_result.actions.webhook.request.auth.username"); + value = contentSource.getValue("execution_result.actions.0.webhook.request.auth.username"); assertThat(value, notNullValue()); - assertThat(value, instanceOf(List.class)); - values = (List) value; - assertThat(values, hasSize(1)); // the auth username exists + assertThat(value, instanceOf(String.class)); + assertThat((String) value, is(USERNAME)); // the auth username exists - value = contentSource.getValue("execution_result.actions.webhook.request.auth.password"); - assertThat(value, notNullValue()); - assertThat(value, instanceOf(List.class)); - values = (List) value; - assertThat(values, hasSize(0)); // but the auth password was filtered out + value = contentSource.getValue("execution_result.actions.0.webhook.request.auth.password"); + assertThat(value, nullValue()); // but the auth password was filtered out RecordedRequest request = webServer.takeRequest(); assertThat(request.getHeader("Authorization"), equalTo(ApplicableBasicAuth.headerValue(USERNAME, PASSWORD.toCharArray()))); diff --git a/src/test/java/org/elasticsearch/watcher/test/integration/WebhookHttpsIntegrationTests.java b/src/test/java/org/elasticsearch/watcher/test/integration/WebhookHttpsIntegrationTests.java index 8967d11ead0..732c031c855 100644 --- a/src/test/java/org/elasticsearch/watcher/test/integration/WebhookHttpsIntegrationTests.java +++ b/src/test/java/org/elasticsearch/watcher/test/integration/WebhookHttpsIntegrationTests.java @@ -33,7 +33,6 @@ import java.net.BindException; import java.net.URISyntaxException; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.List; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures; import static org.elasticsearch.watcher.client.WatchSourceBuilders.watchBuilder; @@ -120,15 +119,13 @@ public class WebhookHttpsIntegrationTests extends AbstractWatcherIntegrationTest .get(); assertNoFailures(response); XContentSource source = new XContentSource(response.getHits().getAt(0).sourceRef()); - List bodies = source.getValue("execution_result.actions.webhook.response.body"); - assertThat(bodies, notNullValue()); - assertThat(bodies, hasSize(1)); - assertThat(bodies, hasItem("body")); + String body = source.getValue("execution_result.actions.0.webhook.response.body"); + assertThat(body, notNullValue()); + assertThat(body, is("body")); - List statuses = source.getValue("execution_result.actions.webhook.response.status"); - assertThat(statuses, notNullValue()); - assertThat(statuses, hasSize(1)); - assertThat(statuses, hasItem(200)); + Number status = source.getValue("execution_result.actions.0.webhook.response.status"); + assertThat(status, notNullValue()); + assertThat(status.intValue(), is(200)); } @Test diff --git a/src/test/java/org/elasticsearch/watcher/test/integration/WebhookIntegrationTests.java b/src/test/java/org/elasticsearch/watcher/test/integration/WebhookIntegrationTests.java index a66301794fa..a722147fbc4 100644 --- a/src/test/java/org/elasticsearch/watcher/test/integration/WebhookIntegrationTests.java +++ b/src/test/java/org/elasticsearch/watcher/test/integration/WebhookIntegrationTests.java @@ -26,7 +26,6 @@ import org.junit.Before; import org.junit.Test; import java.net.BindException; -import java.util.List; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertNoFailures; import static org.elasticsearch.watcher.client.WatchSourceBuilders.watchBuilder; @@ -98,12 +97,12 @@ public class WebhookIntegrationTests extends AbstractWatcherIntegrationTests { assertNoFailures(response); XContentSource source = new XContentSource(response.getHits().getAt(0).getSourceRef()); - List bodies = source.getValue("execution_result.actions.webhook.response.body"); - assertThat(bodies, notNullValue()); - assertThat(bodies, hasItem("body")); - List statuses = source.getValue("execution_result.actions.webhook.response.status"); - assertThat(statuses, notNullValue()); - assertThat(statuses, hasItem(200)); + String body = source.getValue("execution_result.actions.0.webhook.response.body"); + assertThat(body, notNullValue()); + assertThat(body, is("body")); + Number status = source.getValue("execution_result.actions.0.webhook.response.status"); + assertThat(status, notNullValue()); + assertThat(status.intValue(), is(200)); } @Test diff --git a/src/test/java/org/elasticsearch/watcher/watch/WatchTests.java b/src/test/java/org/elasticsearch/watcher/watch/WatchTests.java index a95c83601d0..e875ed80444 100644 --- a/src/test/java/org/elasticsearch/watcher/watch/WatchTests.java +++ b/src/test/java/org/elasticsearch/watcher/watch/WatchTests.java @@ -6,7 +6,6 @@ package org.elasticsearch.watcher.watch; import com.carrotsearch.ant.tasks.junit4.dependencies.com.google.common.collect.ImmutableSet; -import com.carrotsearch.randomizedtesting.annotations.Repeat; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.collect.ImmutableList; import org.elasticsearch.common.collect.ImmutableMap; @@ -42,6 +41,10 @@ import org.elasticsearch.watcher.condition.ExecutableCondition; import org.elasticsearch.watcher.condition.always.AlwaysCondition; import org.elasticsearch.watcher.condition.always.AlwaysConditionFactory; import org.elasticsearch.watcher.condition.always.ExecutableAlwaysCondition; +import org.elasticsearch.watcher.condition.compare.CompareCondition; +import org.elasticsearch.watcher.condition.compare.CompareCondition.Op; +import org.elasticsearch.watcher.condition.compare.CompareConditionFactory; +import org.elasticsearch.watcher.condition.compare.ExecutableCompareCondition; import org.elasticsearch.watcher.condition.script.ExecutableScriptCondition; import org.elasticsearch.watcher.condition.script.ScriptCondition; import org.elasticsearch.watcher.condition.script.ScriptConditionFactory; @@ -127,7 +130,7 @@ public class WatchTests extends ElasticsearchTestCase { logger = Loggers.getLogger(WatchTests.class); } - @Test @Repeat(iterations = 20) + @Test //@Repeat(iterations = 20) public void testParser_SelfGenerated() throws Exception { TransformRegistry transformRegistry = transformRegistry(); @@ -314,10 +317,12 @@ public class WatchTests extends ElasticsearchTestCase { } private ExecutableCondition randomCondition() { - String type = randomFrom(ScriptCondition.TYPE, AlwaysCondition.TYPE); + String type = randomFrom(ScriptCondition.TYPE, AlwaysCondition.TYPE, CompareCondition.TYPE); switch (type) { case ScriptCondition.TYPE: return new ExecutableScriptCondition(new ScriptCondition(Script.inline("_script").build()), logger, scriptService); + case CompareCondition.TYPE: + return new ExecutableCompareCondition(new CompareCondition("_path", randomFrom(Op.values()), randomFrom(5, "3")), logger, SystemClock.INSTANCE); default: return new ExecutableAlwaysCondition(logger); } @@ -329,6 +334,9 @@ public class WatchTests extends ElasticsearchTestCase { case ScriptCondition.TYPE: parsers.put(ScriptCondition.TYPE, new ScriptConditionFactory(settings, scriptService)); return new ConditionRegistry(parsers.build()); + case CompareCondition.TYPE: + parsers.put(CompareCondition.TYPE, new CompareConditionFactory(settings, SystemClock.INSTANCE)); + return new ConditionRegistry(parsers.build()); default: parsers.put(AlwaysCondition.TYPE, new AlwaysConditionFactory(settings)); return new ConditionRegistry(parsers.build());