Add webhook and index actions tests.

This commit adds tests for the webhook and index actions.
Also adds docs for the webhook and index actions and fixes url escaping of webhook action urls.

Original commit: elastic/x-pack-elasticsearch@b70435b198
This commit is contained in:
Brian Murphy 2015-03-18 08:22:18 -07:00
parent 9bb38fa4e1
commit 5e57389c11
11 changed files with 855 additions and 116 deletions

View File

@ -269,7 +269,6 @@
<filtering>true</filtering>
</resource>
</resources>
<testResources>
<testResource>
<directory>${basedir}/src/test/resources</directory>
@ -277,7 +276,6 @@
<include>**/*.*</include>
</includes>
</testResource>
<testResource>
<directory>${basedir}/rest-api-spec</directory>
<targetPath>rest-api-spec</targetPath>
@ -287,7 +285,6 @@
</includes>
</testResource>
</testResources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>

View File

@ -38,8 +38,8 @@ public class IndexAction extends Action<IndexAction.Result> {
private final ClientProxy client;
private final String index;
private final String type;
final String index;
final String type;
public IndexAction(ESLogger logger, @Nullable Transform transform, ClientProxy client, String index, String type) {
super(logger, transform);
@ -235,8 +235,8 @@ public class IndexAction extends Action<IndexAction.Result> {
public static class Result extends Action.Result {
private final Payload response;
private final String reason;
final Payload response;
final String reason;
public Result(Payload response, String reason, boolean isCreated) {
super(TYPE, isCreated);

View File

@ -7,6 +7,7 @@ package org.elasticsearch.watcher.actions.webhook;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.base.Charsets;
import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.logging.ESLogger;
@ -23,15 +24,15 @@ import org.elasticsearch.watcher.support.http.HttpClient;
import org.elasticsearch.watcher.support.http.HttpMethod;
import org.elasticsearch.watcher.support.http.HttpResponse;
import org.elasticsearch.watcher.support.template.Template;
import org.elasticsearch.watcher.support.template.XContentTemplate;
import org.elasticsearch.watcher.transform.Transform;
import org.elasticsearch.watcher.transform.TransformRegistry;
import org.elasticsearch.watcher.watch.Payload;
import org.elasticsearch.watcher.watch.WatchExecutionContext;
import java.io.IOException;
import java.util.Locale;
import java.util.Map;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.*;
/**
*/
@ -41,9 +42,9 @@ public class WebhookAction extends Action<WebhookAction.Result> {
private final HttpClient httpClient;
private final HttpMethod method;
private final Template url;
private final @Nullable Template body;
final HttpMethod method;
final Template url;
final @Nullable Template body;
public WebhookAction(ESLogger logger, @Nullable Transform transform, HttpClient httpClient, HttpMethod method, Template url, Template body) {
super(logger, transform);
@ -61,8 +62,14 @@ public class WebhookAction extends Action<WebhookAction.Result> {
@Override
protected Result execute(WatchExecutionContext ctx, Payload payload) throws IOException {
Map<String, Object> model = Variables.createCtxModel(ctx, payload);
String urlText = url.render(model);
String bodyText = body != null ? body.render(model) : XContentTemplate.YAML.render(model);
Map<String, Object> urlSafeModel = new HashMap<>(model.size());
for (Map.Entry<String, Object> entry : model.entrySet()) {
urlSafeModel.put(entry.getKey(), makeURLSafe(entry.getValue()));
}
String urlText = url.render(urlSafeModel);
String bodyText = body != null ? body.render(model) : ""; //If body is null send an empty body
try {
try (HttpResponse response = httpClient.execute(method, urlText, bodyText)) {
int status = response.status();
@ -158,7 +165,7 @@ public class WebhookAction extends Action<WebhookAction.Result> {
@Override
protected XContentBuilder xContentBody(XContentBuilder builder, Params params) throws IOException {
return builder.field("success", success())
return builder.field(SUCCESS_FIELD.getPreferredName(), success())
.field(WebhookAction.Parser.HTTP_STATUS_FIELD.getPreferredName(), httpStatus)
.field(WebhookAction.Parser.URL_FIELD.getPreferredName(), url)
.field(WebhookAction.Parser.BODY_FIELD.getPreferredName(), body);
@ -174,6 +181,10 @@ public class WebhookAction extends Action<WebhookAction.Result> {
this.reason = reason;
}
public String reason() {
return reason;
}
@Override
protected XContentBuilder xContentBody(XContentBuilder builder, Params params) throws IOException {
return builder.field(WebhookAction.Parser.REASON_FIELD.getPreferredName(), reason);
@ -299,7 +310,8 @@ public class WebhookAction extends Action<WebhookAction.Result> {
throw new ActionException("could not parse webhook result. expected boolean field [success]");
}
Result result = success ? new Result.Executed(httpStatus, url, body) : new Result.Failure(reason);
Result result = (reason == null) ? new Result.Executed(httpStatus, url, body) : new Result.Failure(reason);
if (transformResult != null) {
result.transformResult(transformResult);
}
@ -307,6 +319,28 @@ public class WebhookAction extends Action<WebhookAction.Result> {
}
}
private Object makeURLSafe(Object toSafe) throws UnsupportedEncodingException {
if (toSafe instanceof List) {
List<Object> returnObject = new ArrayList<>(((List) toSafe).size());
for (Object o : (List)toSafe) {
returnObject.add(makeURLSafe(o));
}
return returnObject;
} else if (toSafe instanceof Map) {
Map<Object, Object> returnObject = new HashMap<>(((Map) toSafe).size());
for (Object key : ((Map) toSafe).keySet()) {
returnObject.put(key, makeURLSafe(((Map) toSafe).get(key)));
}
return returnObject;
} else if (toSafe instanceof String) {
return URLEncoder.encode(toSafe.toString(), Charsets.UTF_8.name());
} else {
//Don't know how to convert anything else
return toSafe;
}
}
public static class SourceBuilder implements Action.SourceBuilder {
private final Script url;

View File

@ -82,8 +82,8 @@ public class HttpClient extends AbstractComponent {
urlConnection.getOutputStream().close();
}
HttpResponse response = new HttpResponse();
response.status(urlConnection.getResponseCode());
HttpResponse response = new HttpResponse(urlConnection.getResponseCode());
response.inputStream(urlConnection.getInputStream());
logger.debug("http status code: {}", response.status());
response.inputStream(urlConnection.getInputStream());
return response;

View File

@ -18,12 +18,12 @@ public class HttpResponse implements Closeable {
private InputStream inputStream;
private byte[] body;
public int status() {
return status;
public HttpResponse(int status) {
this.status = status;
}
public void status(int status) {
this.status = status;
public int status() {
return status;
}
public byte[] body() {
@ -44,6 +44,8 @@ public class HttpResponse implements Closeable {
@Override
public void close() throws IOException {
inputStream.close();
if (inputStream != null) {
inputStream.close();
}
}
}

View File

@ -0,0 +1,110 @@
/*
* 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.actions;
import org.elasticsearch.common.collect.ImmutableMap;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.watcher.transform.Transform;
import org.elasticsearch.watcher.transform.TransformRegistry;
import org.elasticsearch.watcher.watch.Payload;
import org.elasticsearch.watcher.watch.WatchExecutionContext;
import java.io.IOException;
import java.util.Map;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;
/**
*/
public class TransformMocks {
public static class TransformMock extends Transform<TransformMock.Result> {
@Override
public String type() {
return "_transform";
}
@Override
public Result apply(WatchExecutionContext ctx, Payload payload) throws IOException {
return new Result("_transform", new Payload.Simple("_key", "_value"));
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
return builder.startObject().endObject();
}
public static class Result extends Transform.Result {
public Result(String type, Payload payload) {
super(type, payload);
}
@Override
protected XContentBuilder xContentBody(XContentBuilder builder, Params params) throws IOException {
return builder;
}
}
}
public static class TransformRegistryMock extends TransformRegistry {
public TransformRegistryMock(final Transform transform) {
super(ImmutableMap.<String, Transform.Parser>of("_transform", new Transform.Parser() {
@Override
public String type() {
return transform.type();
}
@Override
public Transform parse(XContentParser parser) throws IOException {
parser.nextToken();
assertThat(parser.currentToken(), is(XContentParser.Token.END_OBJECT));
return transform;
}
@Override
public Transform.Result parseResult(XContentParser parser) throws IOException {
return null; // should not be called when this ctor is used
}
}));
}
public TransformRegistryMock(final Transform.Result result) {
super(ImmutableMap.<String, Transform.Parser>of("_transform_type", new Transform.Parser() {
@Override
public String type() {
return result.type();
}
@Override
public Transform parse(XContentParser parser) throws IOException {
return null; // should not be called when this ctor is used.
}
@Override
public Transform.Result parseResult(XContentParser parser) throws IOException {
assertThat(parser.currentToken(), is(XContentParser.Token.START_OBJECT));
parser.nextToken();
assertThat(parser.currentToken(), is(XContentParser.Token.FIELD_NAME));
assertThat(parser.currentName(), is("payload"));
parser.nextToken();
assertThat(parser.currentToken(), is(XContentParser.Token.START_OBJECT));
Map<String, Object> data = parser.map();
assertThat(data, equalTo(result.payload().data()));
parser.nextToken();
assertThat(parser.currentToken(), is(XContentParser.Token.END_OBJECT));
return result;
}
}));
}
}
}

View File

@ -15,6 +15,7 @@ import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.elasticsearch.watcher.actions.ActionSettingsException;
import org.elasticsearch.watcher.actions.TransformMocks;
import org.elasticsearch.watcher.actions.email.service.*;
import org.elasticsearch.watcher.support.init.proxy.ScriptServiceProxy;
import org.elasticsearch.watcher.support.template.ScriptTemplate;
@ -148,8 +149,8 @@ public class EmailActionTests extends ElasticsearchTestCase {
ScriptTemplate subject = randomBoolean() ? new ScriptTemplate(scriptService, "_subject") : null;
ScriptTemplate textBody = randomBoolean() ? new ScriptTemplate(scriptService, "_text_body") : null;
ScriptTemplate htmlBody = randomBoolean() ? new ScriptTemplate(scriptService, "_text_html") : null;
final Transform transform = randomBoolean() ? null : new TransformMock();
TransformRegistry transformRegistry = transform == null ? mock(TransformRegistry.class) : new TransformRegistryMock(transform);
final Transform transform = randomBoolean() ? null : new TransformMocks.TransformMock();
TransformRegistry transformRegistry = transform == null ? mock(TransformRegistry.class) : new TransformMocks.TransformRegistryMock(transform);
boolean attachPayload = randomBoolean();
XContentBuilder builder = jsonBuilder().startObject()
.field("account", "_account")
@ -283,8 +284,8 @@ public class EmailActionTests extends ElasticsearchTestCase {
Template textBody = new TemplateMock("_text_body");
Template htmlBody = randomBoolean() ? null : new TemplateMock("_html_body");
boolean attachPayload = randomBoolean();
Transform transform = randomBoolean() ? null : new TransformMock();
TransformRegistry transformRegistry = transform == null ? mock(TransformRegistry.class) : new TransformRegistryMock(transform);
Transform transform = randomBoolean() ? null : new TransformMocks.TransformMock();
TransformRegistry transformRegistry = transform == null ? mock(TransformRegistry.class) : new TransformMocks.TransformRegistryMock(transform);
EmailAction action = new EmailAction(logger, transform, service, email, auth, profile, account, subject, textBody, htmlBody, attachPayload);
@ -327,7 +328,7 @@ public class EmailActionTests extends ElasticsearchTestCase {
when(transformResult.type()).thenReturn("_transform_type");
when(transformResult.payload()).thenReturn(new Payload.Simple("_key", "_value"));
}
TransformRegistry transformRegistry = transformResult != null ? new TransformRegistryMock(transformResult) : mock(TransformRegistry.class);
TransformRegistry transformRegistry = transformResult != null ? new TransformMocks.TransformRegistryMock(transformResult) : mock(TransformRegistry.class);
XContentBuilder builder = jsonBuilder().startObject()
.field("success", success);
@ -423,87 +424,4 @@ public class EmailActionTests extends ElasticsearchTestCase {
}
}
}
static class TransformMock extends Transform<TransformMock.Result> {
@Override
public String type() {
return "_transform";
}
@Override
public Result apply(WatchExecutionContext ctx, Payload payload) throws IOException {
return new Result("_transform", new Payload.Simple("_key", "_value"));
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
return builder.startObject().endObject();
}
public static class Result extends Transform.Result {
public Result(String type, Payload payload) {
super(type, payload);
}
@Override
protected XContentBuilder xContentBody(XContentBuilder builder, Params params) throws IOException {
return builder;
}
}
}
static class TransformRegistryMock extends TransformRegistry {
public TransformRegistryMock(final Transform transform) {
super(ImmutableMap.<String, Transform.Parser>of("_transform", new Transform.Parser() {
@Override
public String type() {
return transform.type();
}
@Override
public Transform parse(XContentParser parser) throws IOException {
parser.nextToken();
assertThat(parser.currentToken(), is(XContentParser.Token.END_OBJECT));
return transform;
}
@Override
public Transform.Result parseResult(XContentParser parser) throws IOException {
return null; // should not be called when this ctor is used
}
}));
}
public TransformRegistryMock(final Transform.Result result) {
super(ImmutableMap.<String, Transform.Parser>of("_transform_type", new Transform.Parser() {
@Override
public String type() {
return result.type();
}
@Override
public Transform parse(XContentParser parser) throws IOException {
return null; // should not be called when this ctor is used.
}
@Override
public Transform.Result parseResult(XContentParser parser) throws IOException {
assertThat(parser.currentToken(), is(XContentParser.Token.START_OBJECT));
parser.nextToken();
assertThat(parser.currentToken(), is(XContentParser.Token.FIELD_NAME));
assertThat(parser.currentName(), is("payload"));
parser.nextToken();
assertThat(parser.currentToken(), is(XContentParser.Token.START_OBJECT));
Map<String, Object> data = parser.map();
assertThat(data, equalTo(result.payload().data()));
parser.nextToken();
assertThat(parser.currentToken(), is(XContentParser.Token.END_OBJECT));
return result;
}
}));
}
}
}

View File

@ -0,0 +1,202 @@
/*
* 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.actions.index;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import org.elasticsearch.action.search.SearchResponse;
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.script.ScriptService;
import org.elasticsearch.test.ElasticsearchIntegrationTest;
import org.elasticsearch.watcher.actions.ActionException;
import org.elasticsearch.watcher.actions.TransformMocks;
import org.elasticsearch.watcher.actions.email.service.Authentication;
import org.elasticsearch.watcher.actions.email.service.Email;
import org.elasticsearch.watcher.actions.email.service.EmailService;
import org.elasticsearch.watcher.actions.email.service.Profile;
import org.elasticsearch.watcher.support.http.HttpClient;
import org.elasticsearch.watcher.support.init.proxy.ClientProxy;
import org.elasticsearch.watcher.support.init.proxy.ScriptServiceProxy;
import org.elasticsearch.watcher.test.WatcherTestUtils;
import org.elasticsearch.watcher.transform.Transform;
import org.elasticsearch.watcher.transform.TransformRegistry;
import org.elasticsearch.watcher.trigger.schedule.ScheduleTriggerEvent;
import org.elasticsearch.watcher.watch.Payload;
import org.elasticsearch.watcher.watch.Watch;
import org.elasticsearch.watcher.watch.WatchExecutionContext;
import org.junit.Test;
import java.util.HashMap;
import java.util.Map;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery;
import static org.elasticsearch.search.builder.SearchSourceBuilder.searchSource;
import static org.hamcrest.Matchers.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
*/
public class IndexActionTests extends ElasticsearchIntegrationTest {
@Test
public void testIndexActionExecute() throws Exception {
IndexAction action = new IndexAction(logger, null, ClientProxy.of(client()), "test-index", "test-type");
final String account = "account1";
Watch alert = WatcherTestUtils.createTestWatch("testAlert",
ClientProxy.of(client()),
ScriptServiceProxy.of(internalCluster().getInstance(ScriptService.class)),
new HttpClient(ImmutableSettings.EMPTY),
new EmailService() {
@Override
public EmailService.EmailSent send(Email email, Authentication auth, Profile profile) {
return new EmailSent(account, email);
}
@Override
public EmailSent send(Email email, Authentication auth, Profile profile, String accountName) {
return new EmailSent(account, email);
}
},
logger);
WatchExecutionContext ctx = new WatchExecutionContext("testid", alert, new DateTime(), new ScheduleTriggerEvent(new DateTime(), new DateTime()));
Map<String, Object> payloadMap = new HashMap<>();
payloadMap.put("test", "foo");
IndexAction.Result result = action.execute(ctx, new Payload.Simple(payloadMap));
assertThat(result.success(), equalTo(true));
Map<String, Object> responseData = result.response().data();
assertThat(responseData.get("created"), equalTo((Object)Boolean.TRUE));
assertThat(responseData.get("version"), equalTo((Object) 1L));
assertThat(responseData.get("type").toString(), equalTo("test-type"));
assertThat(responseData.get("index").toString(), equalTo("test-index"));
refresh(); //Manually refresh to make sure data is available
SearchResponse sr = client().prepareSearch("test-index")
.setTypes("test-type")
.setSource(searchSource().query(matchAllQuery()).buildAsBytes()).get();
assertThat(sr.getHits().totalHits(), equalTo(1L));
}
@Test @Repeat(iterations = 10)
public void testParser() throws Exception {
final Transform transform = randomBoolean() ? null : new TransformMocks.TransformMock();
TransformRegistry transformRegistry = transform == null ? mock(TransformRegistry.class) : new TransformMocks.TransformRegistryMock(transform);
XContentBuilder builder = jsonBuilder();
builder.startObject();
{
builder.field(IndexAction.Parser.INDEX_FIELD.getPreferredName(), "test-index");
builder.field(IndexAction.Parser.TYPE_FIELD.getPreferredName(), "test-type");
if (transform != null){
builder.startObject(Transform.Parser.TRANSFORM_FIELD.getPreferredName()).field(transform.type(), transform);
}
}
builder.endObject();
IndexAction.Parser actionParser = new IndexAction.Parser(ImmutableSettings.EMPTY, ClientProxy.of(client()), transformRegistry);
XContentParser parser = JsonXContent.jsonXContent.createParser(builder.bytes());
parser.nextToken();
IndexAction action = actionParser.parse(parser);
assertThat(action.type, equalTo("test-type"));
assertThat(action.index, equalTo("test-index"));
if (transform != null) {
assertThat(action.transform(), notNullValue());
assertThat(action.transform(), equalTo(transform));
} else {
assertThat(action.transform(), nullValue());
}
}
@Test @Repeat(iterations = 10)
public void testParser_Failure() throws Exception {
XContentBuilder builder = jsonBuilder();
boolean useIndex = randomBoolean();
boolean useType = randomBoolean();
builder.startObject();
{
if (useIndex) {
builder.field(IndexAction.Parser.INDEX_FIELD.getPreferredName(), "test-index");
}
if (useType) {
builder.field(IndexAction.Parser.TYPE_FIELD.getPreferredName(), "test-type");
}
}
builder.endObject();
IndexAction.Parser actionParser = new IndexAction.Parser(ImmutableSettings.EMPTY, ClientProxy.of(client()), null);
XContentParser parser = JsonXContent.jsonXContent.createParser(builder.bytes());
parser.nextToken();
try {
actionParser.parse(parser);
if (!(useIndex && useType)) {
fail();
}
} catch (ActionException ae) {
assertThat(useIndex && useType, equalTo(false));
}
}
@Test @Repeat(iterations =30)
public void testParser_Result() throws Exception {
boolean success = randomBoolean();
Transform.Result transformResult = randomBoolean() ? null : mock(Transform.Result.class);
if (transformResult != null) {
when(transformResult.type()).thenReturn("_transform_type");
when(transformResult.payload()).thenReturn(new Payload.Simple("_key", "_value"));
}
TransformRegistry transformRegistry = transformResult != null ? new TransformMocks.TransformRegistryMock(transformResult) : mock(TransformRegistry.class);
XContentBuilder builder = jsonBuilder().startObject()
.field("success", success);
if (success) {
Map<String,Object> data = new HashMap<>();
data.put("created", true);
data.put("id", "0");
data.put("version", 1);
data.put("type", "test-type");
data.put("index", "test-index");
builder.field(IndexAction.Parser.RESPONSE_FIELD.getPreferredName(), data);
if (transformResult != null) {
builder.startObject("transform_result")
.startObject("_transform_type")
.field("payload", new Payload.Simple("_key", "_value").data())
.endObject()
.endObject();
}
} else {
builder.field("reason", "_reason");
}
XContentParser parser = JsonXContent.jsonXContent.createParser(builder.bytes());
parser.nextToken();
IndexAction.Result result = new IndexAction.Parser(ImmutableSettings.EMPTY, ClientProxy.of(client()), transformRegistry)
.parseResult(parser);
assertThat(result.success(), is(success));
if (success) {
Map<String, Object> responseData = result.response().data();
assertThat(responseData.get("created"), equalTo((Object)Boolean.TRUE));
assertThat(responseData.get("version"), equalTo((Object) 1));
assertThat(responseData.get("type").toString(), equalTo("test-type"));
assertThat(responseData.get("index").toString(), equalTo("test-index"));
} else {
assertThat(result.reason, is("_reason"));
}
}
}

View File

@ -0,0 +1,466 @@
/*
* 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.actions.webhook;
import com.carrotsearch.randomizedtesting.annotations.Repeat;
import org.elasticsearch.common.joda.time.DateTime;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.env.Environment;
import org.elasticsearch.node.settings.NodeSettingsService;
import org.elasticsearch.script.ScriptEngineService;
import org.elasticsearch.script.ScriptService;
import org.elasticsearch.script.mustache.MustacheScriptEngineService;
import org.elasticsearch.test.ElasticsearchTestCase;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.watcher.ResourceWatcherService;
import org.elasticsearch.watcher.actions.Action;
import org.elasticsearch.watcher.actions.ActionException;
import org.elasticsearch.watcher.actions.TransformMocks;
import org.elasticsearch.watcher.actions.email.service.Authentication;
import org.elasticsearch.watcher.actions.email.service.Email;
import org.elasticsearch.watcher.actions.email.service.EmailService;
import org.elasticsearch.watcher.actions.email.service.Profile;
import org.elasticsearch.watcher.support.Script;
import org.elasticsearch.watcher.support.http.HttpClient;
import org.elasticsearch.watcher.support.http.HttpMethod;
import org.elasticsearch.watcher.support.http.HttpResponse;
import org.elasticsearch.watcher.support.init.proxy.ClientProxy;
import org.elasticsearch.watcher.support.init.proxy.ScriptServiceProxy;
import org.elasticsearch.watcher.support.template.ScriptTemplate;
import org.elasticsearch.watcher.support.template.Template;
import org.elasticsearch.watcher.test.WatcherTestUtils;
import org.elasticsearch.watcher.transform.Transform;
import org.elasticsearch.watcher.transform.TransformRegistry;
import org.elasticsearch.watcher.trigger.schedule.ScheduleTriggerEvent;
import org.elasticsearch.watcher.watch.Payload;
import org.elasticsearch.watcher.watch.Watch;
import org.elasticsearch.watcher.watch.WatchExecutionContext;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import javax.mail.internet.AddressException;
import java.io.IOException;
import java.net.URI;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.lessThanOrEqualTo;
import static org.hamcrest.core.Is.is;
import static org.mockito.AdditionalMatchers.not;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
*/
public class WebhookActionTests extends ElasticsearchTestCase {
static final String TEST_BODY = "ERROR HAPPENED";
static final String TEST_URL = "http://test.com/testurl";
private ThreadPool tp = null;
private ScriptServiceProxy scriptService;
@Before
public void init() throws IOException {
tp = new ThreadPool(ThreadPool.Names.SAME);
Settings settings = ImmutableSettings.settingsBuilder().build();
MustacheScriptEngineService mustacheScriptEngineService = new MustacheScriptEngineService(settings);
Set<ScriptEngineService> engineServiceSet = new HashSet<>();
engineServiceSet.add(mustacheScriptEngineService);
scriptService = ScriptServiceProxy.of(new ScriptService(settings, new Environment(), engineServiceSet, new ResourceWatcherService(settings, tp), new NodeSettingsService(settings)));
}
@After
public void cleanup() {
tp.shutdownNow();
}
@Test @Repeat(iterations = 30)
public void testExecute() throws Exception {
ClientProxy client = mock(ClientProxy.class);
ExecuteScenario scenario = randomFrom(ExecuteScenario.values());
HttpClient httpClient = scenario.client();
HttpMethod method = randomFrom(HttpMethod.GET, HttpMethod.POST, HttpMethod.PUT);
final Transform transform = randomBoolean() ? null : new TransformMocks.TransformMock();
final String account = "account1";
WebhookAction webhookAction =
new WebhookAction(logger,
transform,
httpClient,
method,
new ScriptTemplate(scriptService, TEST_URL),
new ScriptTemplate(scriptService, TEST_BODY));
Watch watch = createWatch("test_watch", client, account);
WatchExecutionContext ctx = new WatchExecutionContext("testid", watch, new DateTime(), new ScheduleTriggerEvent(new DateTime(), new DateTime()));
WebhookAction.Result actionResult = webhookAction.execute(ctx, new Payload.Simple());
scenario.assertResult(actionResult);
}
@Test @Repeat(iterations = 10)
public void testParser() throws Exception {
final Transform transform = randomBoolean() ? null : new TransformMocks.TransformMock();
TransformRegistry transformRegistry = transform == null ? mock(TransformRegistry.class) : new TransformMocks.TransformRegistryMock(transform);
Template body = randomBoolean() ? new ScriptTemplate(scriptService, "_subject") : null;
Template url = new ScriptTemplate(scriptService, "_url");
HttpMethod method = randomFrom(HttpMethod.GET, HttpMethod.POST, HttpMethod.PUT, null);
XContentBuilder builder = jsonBuilder();
builder.startObject();
{
builder.field(WebhookAction.Parser.URL_FIELD.getPreferredName(), url);
if (method != null) {
builder.field(WebhookAction.Parser.METHOD_FIELD.getPreferredName(), method.method());
}
if (body != null) {
builder.field(WebhookAction.Parser.BODY_FIELD.getPreferredName(), body);
}
if (transform != null) {
builder.startObject(Transform.Parser.TRANSFORM_FIELD.getPreferredName())
.field(transform.type(), transform);
}
}
builder.endObject();
WebhookAction.Parser actionParser = new WebhookAction.Parser(ImmutableSettings.EMPTY,
new ScriptTemplate.Parser(ImmutableSettings.EMPTY, scriptService),
ExecuteScenario.Success.client(), transformRegistry);
XContentParser parser = JsonXContent.jsonXContent.createParser(builder.bytes());
parser.nextToken();
WebhookAction action = actionParser.parse(parser);
if (method != null) {
assertThat(action.method, equalTo(method));
} else {
assertThat(action.method, equalTo(HttpMethod.POST));
}
if (body != null) {
assertThat(action.body, equalTo(body));
}
assertThat(action.url, equalTo(url));
if (transform != null) {
assertThat(action.transform(), equalTo(transform));
}
}
@Test @Repeat(iterations = 10)
public void testParser_SelfGenerated() throws Exception {
final Transform transform = randomBoolean() ? null : new TransformMocks.TransformMock();
TransformRegistry transformRegistry = transform == null ? mock(TransformRegistry.class) : new TransformMocks.TransformRegistryMock(transform);
Template body = randomBoolean() ? new ScriptTemplate(scriptService, "_body") : null;
Template url = new ScriptTemplate(scriptService, "_url");
HttpMethod method = randomFrom(HttpMethod.GET, HttpMethod.POST, HttpMethod.PUT);
WebhookAction webhookAction = new WebhookAction(logger, transform, ExecuteScenario.Success.client(), method, url, body);
XContentBuilder builder = jsonBuilder();
webhookAction.toXContent(builder, ToXContent.EMPTY_PARAMS);
WebhookAction.Parser actionParser = new WebhookAction.Parser(ImmutableSettings.EMPTY,
new ScriptTemplate.Parser(ImmutableSettings.EMPTY, scriptService),
ExecuteScenario.Success.client(), transformRegistry);
XContentParser parser = JsonXContent.jsonXContent.createParser(builder.bytes());
parser.nextToken();
WebhookAction parsedAction = actionParser.parse(parser);
assertThat(webhookAction.body, equalTo(parsedAction.body));
assertThat(webhookAction.method, equalTo(parsedAction.method));
assertThat(webhookAction.url, equalTo(parsedAction.url));
assertThat(webhookAction.transform(), equalTo(parsedAction.transform()));
}
@Test @Repeat(iterations = 10)
public void testParser_SourceBuilder() throws Exception {
Script body = randomBoolean() ? new Script("_body", ScriptService.ScriptType.INLINE, "mustache") : null;
Script url = new Script("_url", ScriptService.ScriptType.INLINE, "mustache");
HttpMethod method = randomFrom(HttpMethod.GET, HttpMethod.POST, HttpMethod.PUT);
WebhookAction.SourceBuilder sourceBuilder = new WebhookAction.SourceBuilder(url);
sourceBuilder.method(method);
sourceBuilder.body(body);
XContentBuilder builder = jsonBuilder();
sourceBuilder.toXContent(builder, ToXContent.EMPTY_PARAMS);
WebhookAction.Parser actionParser = new WebhookAction.Parser(ImmutableSettings.EMPTY,
new ScriptTemplate.Parser(ImmutableSettings.EMPTY, scriptService),
ExecuteScenario.Success.client(), null);
XContentParser parser = JsonXContent.jsonXContent.createParser(builder.bytes());
parser.nextToken();
Map<String, Object> emptyModel = new HashMap<>();
WebhookAction parsedAction = actionParser.parse(parser);
if (body != null) {
assertThat(body.script(), equalTo(parsedAction.body.render(emptyModel)));
} else {
assertThat(parsedAction.body, equalTo(null));
}
assertThat(url.script(), equalTo(parsedAction.url.render(emptyModel)));
assertThat(method, equalTo(parsedAction.method));
}
@Test(expected = ActionException.class)
public void testParser_Failure() throws Exception {
final Transform transform = randomBoolean() ? null : new TransformMocks.TransformMock();
TransformRegistry transformRegistry = transform == null ? mock(TransformRegistry.class) : new TransformMocks.TransformRegistryMock(transform);
Template body = randomBoolean() ? new ScriptTemplate(scriptService, "_subject") : null;
HttpMethod method = randomFrom(HttpMethod.GET, HttpMethod.POST, HttpMethod.PUT, null);
XContentBuilder builder = jsonBuilder();
builder.startObject();
{
if (method != null) {
builder.field(WebhookAction.Parser.METHOD_FIELD.getPreferredName(), method.method());
}
if (body != null) {
builder.field(WebhookAction.Parser.BODY_FIELD.getPreferredName(), body);
}
if (transform != null) {
builder.startObject(Transform.Parser.TRANSFORM_FIELD.getPreferredName())
.field(transform.type(), transform);
}
}
builder.endObject();
WebhookAction.Parser actionParser = new WebhookAction.Parser(ImmutableSettings.EMPTY,
new ScriptTemplate.Parser(ImmutableSettings.EMPTY, scriptService),
ExecuteScenario.Success.client(), transformRegistry);
XContentParser parser = JsonXContent.jsonXContent.createParser(builder.bytes());
parser.nextToken();
//This should fail since we are not supplying a url
actionParser.parse(parser);
}
@Test @Repeat(iterations = 30)
public void testParser_Result() throws Exception {
Transform.Result transformResult = randomBoolean() ? null : mock(Transform.Result.class);
if (transformResult != null) {
when(transformResult.type()).thenReturn("_transform_type");
when(transformResult.payload()).thenReturn(new Payload.Simple("_key", "_value"));
}
TransformRegistry transformRegistry = transformResult != null ? new TransformMocks.TransformRegistryMock(transformResult) : mock(TransformRegistry.class);
String body = "_body";
String url = "_url";
String reason = "_reason";
int responseCode = randomIntBetween(200, 599);
boolean error = randomBoolean();
boolean success = !error && responseCode < 400;
WebhookAction.Parser actionParser = new WebhookAction.Parser(ImmutableSettings.EMPTY,
new ScriptTemplate.Parser(ImmutableSettings.EMPTY, scriptService),
ExecuteScenario.Success.client(), transformRegistry);
XContentBuilder builder = jsonBuilder();
builder.startObject();
{
builder.field(Action.Result.SUCCESS_FIELD.getPreferredName(), success);
if (!error) {
builder.field(WebhookAction.Parser.HTTP_STATUS_FIELD.getPreferredName(), responseCode);
builder.field(WebhookAction.Parser.BODY_FIELD.getPreferredName(), body);
builder.field(WebhookAction.Parser.URL_FIELD.getPreferredName(), url);
if (transformResult != null) {
builder.startObject("transform_result")
.startObject("_transform_type")
.field("payload", new Payload.Simple("_key", "_value").data())
.endObject()
.endObject();
}
} else {
builder.field(WebhookAction.Parser.REASON_FIELD.getPreferredName(), reason);
}
}
builder.endObject();
XContentParser parser = JsonXContent.jsonXContent.createParser(builder.bytes());
parser.nextToken();
WebhookAction.Result result = actionParser.parseResult(parser);
assertThat(result.success(), equalTo(success));
if (!error) {
assertThat(result, instanceOf(WebhookAction.Result.Executed.class));
WebhookAction.Result.Executed executedResult = (WebhookAction.Result.Executed) result;
assertThat(executedResult.body(), equalTo(body));
assertThat(executedResult.httpStatus(), equalTo(responseCode));
assertThat(executedResult.url(), equalTo(url));
if (transformResult != null) {
assertThat(transformResult, equalTo(executedResult.transformResult()));
}
} else {
assertThat(result, Matchers.instanceOf(WebhookAction.Result.Failure.class));
WebhookAction.Result.Failure failedResult = (WebhookAction.Result.Failure) result;
assertThat(failedResult.reason(), equalTo(reason));
}
}
@Test @Repeat(iterations = 100)
public void testValidUrls() throws Exception {
HttpClient httpClient = ExecuteScenario.Success.client();
HttpMethod method = HttpMethod.GET;
WebhookAction webhookAction =
new WebhookAction(logger,
null,
httpClient,
method,
new ScriptTemplate(scriptService, "http://test.com/test_{{ctx.watch_name}}"),
new ScriptTemplate(scriptService, TEST_BODY));
String watchName = "test_url_encode" + randomAsciiOfLength(10);
Watch watch = createWatch(watchName, mock(ClientProxy.class), "account1");
WatchExecutionContext ctx = new WatchExecutionContext("testid", watch, new DateTime(), new ScheduleTriggerEvent(new DateTime(), new DateTime()));
WebhookAction.Result result = webhookAction.execute(ctx, new Payload.Simple());
assertThat(result, Matchers.instanceOf(WebhookAction.Result.Executed.class));
WebhookAction.Result.Executed executed = (WebhookAction.Result.Executed)result;
URI.create(executed.url()); //This will throw an IllegalArgumentException if the url is invalid
}
private Watch createWatch(String watchName, ClientProxy client, final String account) throws AddressException, IOException {
return WatcherTestUtils.createTestWatch(watchName,
client,
scriptService,
ExecuteScenario.Success.client(),
new EmailService() {
@Override
public EmailSent send(Email email, Authentication auth, Profile profile) {
return new EmailSent(account, email);
}
@Override
public EmailSent send(Email email, Authentication auth, Profile profile, String accountName) {
return new EmailSent(account, email);
}
},
logger);
}
private static enum ExecuteScenario {
ErrorCode() {
@Override
public HttpClient client() throws IOException {
HttpClient client = mock(HttpClient.class);
when(client.execute(any(HttpMethod.class), any(String.class), any(String.class)))
.thenReturn(new HttpResponse(randomIntBetween(400,599)));
return client;
}
@Override
public void assertResult(WebhookAction.Result actionResult) {
assertThat(actionResult.success(), is(false));
assertThat(actionResult, instanceOf(WebhookAction.Result.Executed.class));
WebhookAction.Result.Executed executedActionResult = (WebhookAction.Result.Executed) actionResult;
assertThat(executedActionResult.httpStatus(), greaterThanOrEqualTo(400));
assertThat(executedActionResult.httpStatus(), lessThanOrEqualTo(599));
assertThat(executedActionResult.body(), equalTo(TEST_BODY));
assertThat(executedActionResult.url(), equalTo(TEST_URL));
}
},
Error() {
@Override
public HttpClient client() throws IOException {
HttpClient client = mock(HttpClient.class);
when(client.execute(any(HttpMethod.class), any(String.class), any(String.class)))
.thenThrow(new IOException("Unable to connect"));
return client;
}
@Override
public void assertResult(WebhookAction.Result actionResult) {
assertThat(actionResult, instanceOf(WebhookAction.Result.Failure.class));
WebhookAction.Result.Failure failResult = (WebhookAction.Result.Failure) actionResult;
assertThat(failResult.success(), is(false));
}
},
Success() {
@Override
public HttpClient client() throws IOException{
HttpClient client = mock(HttpClient.class);
when(client.execute(any(HttpMethod.class), any(String.class), any(String.class)))
.thenReturn(new HttpResponse(randomIntBetween(200,399)));
return client;
}
@Override
public void assertResult(WebhookAction.Result actionResult) {
assertThat(actionResult, instanceOf(WebhookAction.Result.Executed.class));
assertThat(actionResult, instanceOf(WebhookAction.Result.Executed.class));
WebhookAction.Result.Executed executedActionResult = (WebhookAction.Result.Executed) actionResult;
assertThat(executedActionResult.httpStatus(), greaterThanOrEqualTo(200));
assertThat(executedActionResult.httpStatus(), lessThanOrEqualTo(399));
assertThat(executedActionResult.body(), equalTo(TEST_BODY));
assertThat(executedActionResult.url(), equalTo(TEST_URL));
}
},
SuccessVerify() {
@Override
public HttpClient client() throws IOException{
HttpClient client = mock(HttpClient.class);
when(client.execute(any(HttpMethod.class), eq(TEST_URL), eq(TEST_BODY)))
.thenReturn(new HttpResponse(200));
when(client.execute(any(HttpMethod.class), eq(not(TEST_URL)), eq(not(TEST_BODY)))).thenThrow(new IOException("bad url or body"));
return client;
}
@Override
public void assertResult(WebhookAction.Result actionResult) {
assertThat(actionResult, instanceOf(WebhookAction.Result.Executed.class));
assertThat(actionResult, instanceOf(WebhookAction.Result.Executed.class));
WebhookAction.Result.Executed executedActionResult = (WebhookAction.Result.Executed) actionResult;
assertThat(executedActionResult.httpStatus(), equalTo(200));
assertThat(executedActionResult.body(), equalTo(TEST_BODY));
assertThat(executedActionResult.url(), equalTo(TEST_URL));
}
};
public abstract HttpClient client() throws IOException;
public abstract void assertResult(WebhookAction.Result result);
}
}

View File

@ -81,8 +81,7 @@ public class HttpInputTests extends ElasticsearchTestCase {
request.body(mockBody);
HttpInput input = new HttpInput(logger, httpClient, request, null);
HttpResponse response = new HttpResponse();
response.status(123);
HttpResponse response = new HttpResponse(123);
response.inputStream(new ByteArrayInputStream("{\"key\" : \"value\"}".getBytes(UTF8)));
when(httpClient.execute(any(HttpRequest.class))).thenReturn(response);

View File

@ -25,6 +25,7 @@ import org.elasticsearch.watcher.actions.email.service.Profile;
import org.elasticsearch.watcher.actions.webhook.WebhookAction;
import org.elasticsearch.watcher.condition.script.ScriptCondition;
import org.elasticsearch.watcher.input.search.SearchInput;
import org.elasticsearch.watcher.input.simple.SimpleInput;
import org.elasticsearch.watcher.support.Script;
import org.elasticsearch.watcher.support.WatcherUtils;
import org.elasticsearch.watcher.support.clock.SystemClock;
@ -108,7 +109,14 @@ public final class WatcherTestUtils {
return ctx;
}
public static Watch createTestWatch(String watchName, ScriptServiceProxy scriptService, HttpClient httpClient, EmailService emailService, ESLogger logger) throws AddressException {
return createTestWatch(watchName, ClientProxy.of(ElasticsearchIntegrationTest.client()), scriptService, httpClient, emailService, logger);
}
public static Watch createTestWatch(String watchName, ClientProxy client, ScriptServiceProxy scriptService, HttpClient httpClient, EmailService emailService, ESLogger logger) throws AddressException {
SearchRequest conditionRequest = newInputSearchRequest("my-condition-index").source(searchSource().query(matchAllQuery()));
SearchRequest transformRequest = newInputSearchRequest("my-payload-index").source(searchSource().query(matchAllQuery()));
transformRequest.searchType(SearchTransform.DEFAULT_SEARCH_TYPE);
@ -140,13 +148,16 @@ public final class WatcherTestUtils {
Map<String, Object> metadata = new LinkedHashMap<>();
metadata.put("foo", "bar");
Map<String, Object> inputData = new LinkedHashMap<>();
inputData.put("bar", "foo");
return new Watch(
watchName,
SystemClock.INSTANCE,
new ScheduleTrigger(new CronSchedule("0/5 * * * * ? *")),
new SearchInput(logger, scriptService, ClientProxy.of(ElasticsearchIntegrationTest.client()), conditionRequest, null),
new SimpleInput(logger, new Payload.Simple(inputData)),
new ScriptCondition(logger, scriptService, new Script("return true")),
new SearchTransform(logger, scriptService, ClientProxy.of(ElasticsearchIntegrationTest.client()), transformRequest),
new SearchTransform(logger, scriptService, client, transformRequest),
new Actions(actions),
metadata,
new TimeValue(0),