/* * Copyright 2012 Nodeable Inc * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.streamreduce.rest; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import com.google.common.base.Predicate; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.mongodb.BasicDBList; import com.mongodb.util.JSON; import com.streamreduce.AbstractInContainerTestCase; import com.streamreduce.ProviderIdConstants; import com.streamreduce.connections.AuthType; import com.streamreduce.core.model.Account; import com.streamreduce.core.model.Connection; import com.streamreduce.core.model.Event; import com.streamreduce.core.model.InventoryItem; import com.streamreduce.core.service.EventService; import com.streamreduce.core.service.InventoryService; import com.streamreduce.connections.CustomProvider; import com.streamreduce.connections.GatewayProvider; import com.streamreduce.rest.dto.response.ConnectionResponseDTO; import com.streamreduce.rest.resource.ErrorMessage; import net.sf.json.JSONArray; import net.sf.json.JSONObject; import org.bson.types.ObjectId; import org.codehaus.jackson.map.type.TypeFactory; import org.junit.After; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; public class GatewayResourceITCase extends AbstractInContainerTestCase { @Autowired private EventService eventService; @Autowired private InventoryService inventoryService; private Account account; private ConnectionResponseDTO connection; private String apiKey; @Before @Override public void setUp() throws Exception { super.setUp(); String authnToken = login(testUsername, testUsername); JSONObject json = new JSONObject(); json.put("providerId", ProviderIdConstants.CUSTOM_PROVIDER_ID); json.put("type", CustomProvider.TYPE); json.put("alias", "Test Generic Connection"); json.put("authType", AuthType.API_KEY); try { // Create a new IMG connection connection = jsonToObject(makeRequest(connectionsBaseUrl, "POST", json, authnToken), TypeFactory.defaultInstance().constructType(ConnectionResponseDTO.class)); // Get the IMG connection API key apiKey = connection.getIdentity(); // Validate the connection was created Connection cConnection = applicationManager.getSecurityService().getByApiKey(apiKey, GatewayProvider.TYPE); assertNotNull(cConnection); account = cConnection.getAccount(); // Enable inbound IMG support account.setConfigValue(Account.ConfigKey.DISABLE_INBOUND_API, false); applicationManager.getUserService().updateAccount(account); } catch (Exception e) { logger.error(e.getMessage(), e); } } @After @Override public void tearDown() throws Exception { if (connection != null) { applicationManager.getConnectionService().deleteConnection( applicationManager.getConnectionService().getConnection(connection.getId())); } super.tearDown(); } @Test @Ignore public void testGMGInvalidAuth() throws Exception { assertTrue(makeRequest(imgBaseUrl, "POST", new JSONObject(), "some_bogus_key", AuthTokenType.GATEWAY) .contains("Authentication failed")); } @Test @Ignore public void testGMGInboundDisabled() throws Exception { // Disable inbound IMG support account.setConfigValue(Account.ConfigKey.DISABLE_INBOUND_API, true); applicationManager.getUserService().updateAccount(account); ErrorMessage error = jsonToObject(makeRequest(imgBaseUrl, "POST", new JSONObject(), apiKey, AuthTokenType.GATEWAY), TypeFactory.defaultInstance().constructType(ErrorMessage.class)); assertTrue(error.getErrorMessage().equals("This account is not provisioned for inbound payloads, please " + "contact support@nodeable.com.")); } @Test @Ignore public void testInvalidGMGPayloads() throws Exception { ErrorMessage error = jsonToObject(makeRequest(imgBaseUrl, "POST", new JSONObject(), apiKey, AuthTokenType.GATEWAY), TypeFactory.defaultInstance().constructType(ErrorMessage.class)); // Invalid message because the message or metrics attributes were missing assertTrue(error.getErrorMessage().equals("You must supply at least a 'message' or 'metrics' attribute in " + "the payload.")); // Invalid message because hashtags is a non-array JSONObject json = new JSONObject(); json.put("message", "This message doesn't matter."); json.put("hashtags", 12345); error = jsonToObject(makeRequest(imgBaseUrl, "POST", json, apiKey, AuthTokenType.GATEWAY), TypeFactory.defaultInstance().constructType(ErrorMessage.class)); assertTrue(error.getErrorMessage().equals("'hashtags' must be a string array.")); // Invalid message because hashtags is an array with non-string values JSONArray jsonArray = new JSONArray(); json = new JSONObject(); jsonArray.add(12345); json.put("message", "This message doesn't matter."); json.put("hashtags", jsonArray); error = jsonToObject(makeRequest(imgBaseUrl, "POST", json, apiKey, AuthTokenType.GATEWAY), TypeFactory.defaultInstance().constructType(ErrorMessage.class)); assertTrue(error.getErrorMessage() .equals("All hashtags specified in the 'hashtags' attribute must be strings.")); // Invalid message because metrics is a non-array json = new JSONObject(); json.put("message", "This message doesn't matter."); json.put("metrics", 12345); error = jsonToObject(makeRequest(imgBaseUrl, "POST", json, apiKey, AuthTokenType.GATEWAY), TypeFactory.defaultInstance().constructType(ErrorMessage.class)); assertTrue(error.getErrorMessage().equals("'metrics' must be an object array.")); // Invalid message because metrics is an array with non-object values json = new JSONObject(); jsonArray = new JSONArray(); jsonArray.add(12345); json.put("message", "This message doesn't matter."); json.put("metrics", jsonArray); error = jsonToObject(makeRequest(imgBaseUrl, "POST", json, apiKey, AuthTokenType.GATEWAY), TypeFactory.defaultInstance().constructType(ErrorMessage.class)); assertTrue(error.getErrorMessage() .equals("All metrics specified in the 'metrics' attribute must be objects.")); // Invalid message because metric in metrics doesn't contain name JSONObject metric = new JSONObject(); json = new JSONObject(); jsonArray = new JSONArray(); jsonArray.add(metric); json.put("message", "This message doesn't matter."); json.put("metrics", jsonArray); error = jsonToObject(makeRequest(imgBaseUrl, "POST", json, apiKey, AuthTokenType.GATEWAY), TypeFactory.defaultInstance().constructType(ErrorMessage.class)); assertTrue(error.getErrorMessage() .equals("'name' is required for each metric in the 'metrics' attribute.")); // Invalid message because metric in metrics doesn't contain type metric = new JSONObject(); json = new JSONObject(); jsonArray = new JSONArray(); metric.put("name", "This name doesn't matter."); jsonArray.add(metric); json.put("message", "This message doesn't matter."); json.put("metrics", jsonArray); error = jsonToObject(makeRequest(imgBaseUrl, "POST", json, apiKey, AuthTokenType.GATEWAY), TypeFactory.defaultInstance().constructType(ErrorMessage.class)); assertTrue(error.getErrorMessage() .equals("'type' is required for each metric in the 'metrics' attribute and must be " + "either 'ABSOLUTE' or 'DELTA'.")); // Invalid message because metric in metrics doesn't contain valid type metric = new JSONObject(); json = new JSONObject(); jsonArray = new JSONArray(); metric.put("name", "This name doesn't matter."); metric.put("type", "FAKE"); jsonArray.add(metric); json.put("message", "This message doesn't matter."); json.put("metrics", jsonArray); error = jsonToObject(makeRequest(imgBaseUrl, "POST", json, apiKey, AuthTokenType.GATEWAY), TypeFactory.defaultInstance().constructType(ErrorMessage.class)); assertTrue(error.getErrorMessage() .equals("'type' is required for each metric in the 'metrics' attribute and must be " + "either 'ABSOLUTE' or 'DELTA'.")); // Invalid message because metric in metrics doesn't contain value metric = new JSONObject(); json = new JSONObject(); jsonArray = new JSONArray(); metric.put("name", "This name doesn't matter."); metric.put("type", "ABSOLUTE"); jsonArray.add(metric); json.put("message", "This message doesn't matter."); json.put("metrics", jsonArray); error = jsonToObject(makeRequest(imgBaseUrl, "POST", json, apiKey, AuthTokenType.GATEWAY), TypeFactory.defaultInstance().constructType(ErrorMessage.class)); assertTrue(error.getErrorMessage() .equals("'value' is required for each metric in the 'metrics' attribute and must " + "be a valid numerical value.")); // Invalid message because metric in metrics doesn't contain valid value metric = new JSONObject(); json = new JSONObject(); jsonArray = new JSONArray(); metric.put("name", "This name doesn't matter."); metric.put("type", "ABSOLUTE"); metric.put("value", "fake"); jsonArray.add(metric); json.put("message", "This message doesn't matter."); json.put("metrics", jsonArray); error = jsonToObject(makeRequest(imgBaseUrl, "POST", json, apiKey, AuthTokenType.GATEWAY), TypeFactory.defaultInstance().constructType(ErrorMessage.class)); assertTrue(error.getErrorMessage() .equals("'value' for each metric in the 'metrics' attribute must be a numerical value.")); // Invalid because it's a multi-entry request but the data attribute isn't a JSONArray json = new JSONObject(); json.put("data", "This should be a JSONArray."); error = jsonToObject(makeRequest(imgBaseUrl, "POST", json, apiKey, AuthTokenType.GATEWAY), TypeFactory.defaultInstance().constructType(ErrorMessage.class)); assertTrue(error.getErrorMessage().equals("'data' must be an object array.")); // Invalid because it's a multi-entry request but the data entry isn't an object json = new JSONObject(); jsonArray = new JSONArray(); jsonArray.add("This should be a JSONObject."); json.put("data", jsonArray); error = jsonToObject(makeRequest(imgBaseUrl, "POST", json, apiKey, AuthTokenType.GATEWAY), TypeFactory.defaultInstance().constructType(ErrorMessage.class)); assertTrue(error.getErrorMessage().equals("Every object in the 'data' array should be an object.")); // Invalid because dateGenerated is not a number json = new JSONObject(); json.put("message", "This is a test message."); json.put("dateGenerated", "This should be a number."); error = jsonToObject(makeRequest(imgBaseUrl, "POST", json, apiKey, AuthTokenType.GATEWAY), TypeFactory.defaultInstance().constructType(ErrorMessage.class)); assertTrue(error.getErrorMessage().equals("'dateGenerated' must be an number.")); } @Test @Ignore public void testSuccessfulConnectionMessages() throws Exception { List<Event> events = getEventsForTarget(connection.getId()); JSONObject json = new JSONObject(); int originalEventCount = events.size(); json.put("message", "This is a test message."); assertEquals("201", makeRequest(imgBaseUrl, "POST", json, apiKey, AuthTokenType.GATEWAY)); events = getEventsForTarget(connection.getId()); assertEquals(originalEventCount + 1, events.size()); Event event = events.get(events.size() - 1); validateEvent(event, json); json = new JSONObject(); json.put("message", "This is another test message."); json.put("hashtags", ImmutableSet.of("#testing", "#test")); json.put("superfluous", "This is a superfluous property."); JSONArray metrics = new JSONArray(); for (int i = 0; i < 2; i++) { JSONObject metric = new JSONObject(); metric.put("name", "Test Metric" + (i + 1)); metric.put("type", "ABSOLUTE"); metric.put("value", (i + 1)); metrics.add(metric); } json.put("metrics", metrics); assertEquals("201", makeRequest(imgBaseUrl, "POST", json, apiKey, AuthTokenType.GATEWAY)); events = getEventsForTarget(connection.getId()); event = events.get(events.size() - 1); assertEquals(originalEventCount + 2, events.size()); validateEvent(event, json); } @Test @Ignore public void testSuccessfulInventoryItemMessages() throws Exception { List<InventoryItem> inventoryItems = inventoryService.getInventoryItems(connection.getId()); int connectionEventCount = getEventsForTarget(connection.getId()).size(); JSONObject json = new JSONObject(); List<Event> events; InventoryItem inventoryItem; int originalEventCount; assertEquals(0, inventoryItems.size()); json.put("message", "Creating a new IMG inventory item."); json.put("inventoryItemId", "testNodeId"); assertEquals("201", makeRequest(imgBaseUrl, "POST", json, apiKey, AuthTokenType.GATEWAY)); inventoryItems = inventoryService.getInventoryItems(connection.getId()); // Make sure the connection isn't receiving the event assertEquals(connectionEventCount, getEventsForTarget(connection.getId()).size()); // Make sure the inventory item was created assertEquals(1, inventoryItems.size()); inventoryItem = inventoryItems.get(0); events = getEventsForTarget(inventoryItem.getId()); originalEventCount = events.size(); validateEvent(events.get(events.size() - 1), json); json = new JSONObject(); json.put("inventoryItemId", "testNodeId"); json.put("hashtags", ImmutableSet.of("#testing", "#test")); json.put("superfluous", "This is a superfluous property."); JSONArray metrics = new JSONArray(); for (int i = 0; i < 2; i++) { JSONObject metric = new JSONObject(); metric.put("name", "Test Metric" + (i + 1)); metric.put("type", "DELTA"); metric.put("value", (i + 1)); metrics.add(metric); } json.put("metrics", metrics); assertEquals("201", makeRequest(imgBaseUrl, "POST", json, apiKey, AuthTokenType.GATEWAY)); events = getEventsForTarget(inventoryItem.getId()); validateEvent(events.get(events.size() - 1), json); // Make sure the connection isn't receiving the event assertEquals(connectionEventCount, getEventsForTarget(connection.getId()).size()); // Make sure the inventory item event count incremented by two (read of inventory item and new message/metrics) assertEquals(originalEventCount + 2, events.size()); } @Test @Ignore public void testMultipleMessagesInOneRequest() throws Exception { int originalEventCount = getEventsForTarget(connection.getId()).size(); JSONObject json = new JSONObject(); JSONArray jsonArray = new JSONArray(); for (int i = 0; i < 2; i++) { JSONObject message = new JSONObject(); message.put("message", "Test message " + (i + 1) + "."); message.put("dateGenerated", new Date().getTime()); jsonArray.add(message); } json.put("data", jsonArray); assertEquals("201", makeRequest(imgBaseUrl, "POST", json, apiKey, AuthTokenType.GATEWAY)); List<Event> events = getEventsForTarget(connection.getId()); assertEquals(originalEventCount + 2, events.size()); } private void validateEvent(Event event, JSONObject json) { Map<String, Object> eventMetadata = event.getMetadata(); // Validate the hashtags if (json.containsKey("hashtags")) { BasicDBList targetHashtags = (BasicDBList)eventMetadata.get("targetHashtags"); JSONArray messageHashtags = json.getJSONArray("hashtags"); Set<String> expectedHashtags = new HashSet<>(); for (Object rawHashtag : targetHashtags) { expectedHashtags.add(rawHashtag.toString()); } // Account for the inherited '#custom' hashtag from the target assertEquals(messageHashtags.size() + 1, targetHashtags.size()); // Ensure all message hashtags are in the targetHashtags for (Object rawHashtag : messageHashtags) { assertTrue(expectedHashtags.contains(rawHashtag.toString())); } } // Validate the payload assertEquals(JSON.parse(json.toString()).toString(), eventMetadata.get("payload").toString()); } /** * Returns all events for the given target object id. * * @param targetId the target object id * * @return the list of events */ private List<Event> getEventsForTarget(final ObjectId targetId) { return Lists.newArrayList(Iterables.filter(eventService.getEventsForAccount(testAccount), new Predicate<Event>() { @Override public boolean apply(Event event) { return event.getTargetId().equals(targetId); } })); } }