/*
* 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.core.service;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.streamreduce.AbstractServiceTestCase;
import com.streamreduce.ConnectionNotFoundException;
import com.streamreduce.Constants;
import com.streamreduce.ProviderIdConstants;
import com.streamreduce.connections.AuthType;
import com.streamreduce.connections.ConnectionProvider;
import com.streamreduce.connections.ConnectionProviderFactory;
import com.streamreduce.core.event.EventId;
import com.streamreduce.core.model.Account;
import com.streamreduce.core.model.Connection;
import com.streamreduce.core.model.ConnectionCredentials;
import com.streamreduce.core.model.Event;
import com.streamreduce.core.model.InventoryItem;
import com.streamreduce.core.model.ObjectWithId;
import com.streamreduce.core.model.SobaObject;
import com.streamreduce.core.model.User;
import com.streamreduce.core.model.messages.SobaMessage;
import com.streamreduce.core.service.exception.AccountNotFoundException;
import com.streamreduce.core.service.exception.InventoryItemNotFoundException;
import com.streamreduce.core.service.exception.MessageNotFoundException;
import com.streamreduce.core.service.exception.UserNotFoundException;
import org.apache.shiro.authc.AuthenticationException;
import org.bson.types.ObjectId;
import org.jclouds.domain.Location;
import org.jclouds.domain.LocationScope;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.util.ReflectionTestUtils;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Test to ensure {@link EventService} works as expected.
*/
public class EventServiceITCase extends AbstractServiceTestCase {
@Autowired
ConnectionProviderFactory connectionProviderFactory;
/**
* Tests that the events created to this point for the test account and test user
* are as expected.
*
* @throws Exception if anything goes wrong
*/
@Test
@Ignore("Integration Tests depended on sensitive account keys, ignoring until better harness is in place.")
public void testExpectedEventsForTestAccountAndTestUser() throws Exception {
EventService eventService = applicationManager.getEventService();
List<Event> events = eventService.getEventsForAccount(getTestAccount());
// At this point, there should be exactly 3 CREATE events
List<Event> createEvents = Lists.newArrayList(Iterables.filter(events, new Predicate<Event>() {
@Override
public boolean apply(@Nullable Event input) {
return input != null && input.getEventId() == EventId.CREATE;
}
}));
Assert.assertEquals(3, createEvents.size());
}
/**
* Tests that {@link EventService#createEvent(com.streamreduce.core.event.EventId, com.streamreduce.core.model.ObjectWithId,
* java.util.Map)}
* works as expected when a user is not logged in and we are creating an event.
*/
@Test
@Ignore("Integration Tests depended on sensitive account keys, ignoring until better harness is in place.")
public void testCreateEvent_ReadEventWithNoLoggedInUser() throws Exception {
// Test creating an event with no logged in user
EventService eventService = applicationManager.getEventService();
SecurityService mockSecurityService = mock(SecurityService.class);
when(mockSecurityService.getCurrentUser()).thenThrow(new AuthenticationException());
ReflectionTestUtils.setField(eventService, "securityService", mockSecurityService);
Event event = eventService.createEvent(EventId.READ, testUser, null);
Assert.assertNull(event);
}
/**
* Tests that {@link EventService#createEvent(com.streamreduce.core.event.EventId, com.streamreduce.core.model.ObjectWithId,
* java.util.Map)}
* works as expected when a user is not logged in and we are creating a SobaMessage
*/
@Test
@Ignore("Integration Tests depended on sensitive account keys, ignoring until better harness is in place.")
public void testCreateEvent_CreateSobaMessageWithNoLoggedInUser() throws Exception {
// Test creating an event with no logged in user
EventService eventService = applicationManager.getEventService();
SecurityService mockSecurityService = mock(SecurityService.class);
when(mockSecurityService.getCurrentUser()).thenThrow(new AuthenticationException());
ReflectionTestUtils.setField(eventService, "securityService", mockSecurityService);
// Test creating a SobaObject event with no logged in user
Connection connection = new Connection.Builder()
.provider(connectionProviderFactory.connectionProviderFromId(ProviderIdConstants.GITHUB_PROVIDER_ID))
.account(testAccount)
.alias("Test GitHub Connection")
.description("This is a test GitHub connection.")
.user(testUser)
.authType(AuthType.USERNAME_PASSWORD)
.credentials(new ConnectionCredentials("somegithubusername", "somegithubpassword"))
.build();
Event event = eventService.createEvent(EventId.CREATE, connection, null);
Assert.assertNotNull(event);
}
/**
* Tests that {@link EventService#createEvent(com.streamreduce.core.event.EventId, com.streamreduce.core.model.ObjectWithId,
* java.util.Map)}
*/
@Test
@Ignore("Integration Tests depended on sensitive account keys, ignoring until better harness is in place.")
public void testCreateEvent_CreateEventWithLoggedInUser() throws Exception {
// Test creating an event as a logged in user
// (We have to mock a few things since logging a user in programmatically isn't an option)
EventService esMock = getEventServiceMock();
Event event = esMock.createEvent(EventId.CREATE_GLOBAL_MESSAGE, null, null);
verifyEvent(event, EventId.CREATE_GLOBAL_MESSAGE, testUser, testAccount, null, null);
}
/**
* Test that will attempt to retrieve all {@link Event} objects and verify them.
*
* @throws Exception if anything goes wrong
*/
@Test
@Ignore("Integration Tests depended on sensitive account keys, ignoring until better harness is in place.")
public void testAllGeneratedEvents() throws Exception {
EventService eventService = applicationManager.getEventService();
List<Event> allEvents = eventService.getAllEvents();
// TODO: It would be nice to test every event scenario when time permits
for (Event event : allEvents) {
verifyEvent(event);
}
}
/**
* Creates a mock {@link EventService} that is mocked so a user is logged in.
*
* @return the mock ApplicationManager
*/
private EventService getEventServiceMock() {
SecurityService ssMock = Mockito.mock(SecurityService.class);
UserService usMock = Mockito.spy(applicationManager.getUserService());
EventService esMock = Mockito.spy(applicationManager.getEventService());
ReflectionTestUtils.setField(esMock, "userService", usMock);
ReflectionTestUtils.setField(esMock, "securityService", ssMock);
ReflectionTestUtils.setField(usMock, "eventService", esMock);
Mockito.when(ssMock.getCurrentUser()).thenReturn(testUser);
return esMock;
}
/**
* Validates historical events, where possible, by looking up the event's user, account and target
* and then using that information in calling {@link #verifyEvent(com.streamreduce.core.model.Event,
* com.streamreduce.core.event.EventId, com.streamreduce.core.model.User, com.streamreduce.core.model.Account,
* com.streamreduce.core.model.ObjectWithId, java.util.Map)}.
*
* @param event the event to verify
*/
private void verifyEvent(Event event) {
Map<String, Object> eventMetadata = event.getMetadata();
String eventType = (String) eventMetadata.get("targetType");
User eventUser = null;
Account eventAccount = null;
ObjectWithId eventTarget = null;
// TODO: Bubble the event participants lookup to event service methods
if (event.getUserId() != null) {
try {
eventUser = applicationManager.getUserService().getUserById(event.getUserId());
} catch (UserNotFoundException e) {
// Do nothing
}
}
if (event.getAccountId() != null) {
try {
eventAccount = applicationManager.getUserService().getAccount(event.getAccountId());
} catch (AccountNotFoundException e) {
// Do nothing
}
}
if (event.getTargetId() != null) {
ObjectId eventTargetId = event.getTargetId();
if (eventType.equals(Account.class.getSimpleName())) {
try {
eventTarget = applicationManager.getUserService().getAccount(eventTargetId);
} catch (AccountNotFoundException e) {
// Do nothing
}
} else if (eventType.equals(InventoryItem.class.getSimpleName())) {
try {
eventTarget = applicationManager.getInventoryService().getInventoryItem(eventTargetId);
} catch (InventoryItemNotFoundException e) {
// Do nothing
}
} else if (eventType.equals(Connection.class.getSimpleName())) {
try {
eventTarget = applicationManager.getConnectionService().getConnection(eventTargetId);
} catch (ConnectionNotFoundException e) {
// Do nothing
}
} else if (eventType.equals(SobaMessage.class.getSimpleName())) {
try {
eventTarget = applicationManager.getMessageService().getMessage(eventAccount, eventTargetId);
} catch (MessageNotFoundException e) {
// Do nothing
}
} else if (eventType.equals(User.class.getSimpleName())) {
try {
eventTarget = applicationManager.getUserService().getUserById(eventTargetId);
} catch (UserNotFoundException e) {
// Do nothing
}
} else if (eventType.equals(SobaMessage.class.getSimpleName())) {
try {
eventTarget = applicationManager.getMessageService().getMessage(eventAccount, eventTargetId);
} catch (MessageNotFoundException e) {
// Do nothing
}
} else {
Assert.fail("Unable to handle target class of type: " + eventType + ".");
}
}
// We cannot actually validate events where we cannot lookup the object by id for historical events
if (event.getUserId() != null && eventUser != null &&
event.getAccountId() != null && eventAccount != null &&
event.getTargetId() != null && eventTarget != null) {
verifyEvent(event, event.getEventId(), eventUser, eventAccount, eventTarget, eventMetadata);
} else {
System.out.println("[TEST WARN] Historical Event (" + event.getId() + ") could not be validated for " +
"content due to the user, account and/or target no longer being in the " +
"database.");
}
}
/**
* Validates the event against expected event pieces.
*
* @param event the event to validate
* @param expectedEventId the expected event id
* @param expectedUser the expected user
* @param expectedAccount the expected account
* @param expectedTarget the expected target
* @param expectedExtraMetadata the expected extra metadata
*/
private <T extends ObjectWithId> void verifyEvent(Event event, EventId expectedEventId, User expectedUser,
Account expectedAccount, T expectedTarget,
Map<String, Object> expectedExtraMetadata) {
Map<String, Object> eventMetadata = event.getMetadata();
// Validate the event id
Assert.assertEquals(expectedEventId, event.getEventId());
// Validate the user
Assert.assertEquals(expectedUser != null ? expectedUser.getId() : null, event.getUserId());
// Validate the account
Assert.assertEquals(expectedAccount != null ? expectedAccount.getId() : null, event.getAccountId());
// Validate the target
Assert.assertEquals(expectedTarget != null ? expectedTarget.getId() : null, event.getTargetId());
// Validate the timestamp
Assert.assertNotNull(event.getTimestamp());
// Validate the extra metadata
if (expectedExtraMetadata != null) {
for (String key : expectedExtraMetadata.keySet()) {
Assert.assertEquals(expectedExtraMetadata.get(key), eventMetadata.get(key));
}
}
// Validate metadata automatically generated at event creation
// Validate user generated event metadata
Map<String, Object> expectedUserMetadata = new HashMap<>();
expectedUserMetadata.put("sourceAlias", expectedUser != null ? expectedUser.getAlias() : null);
expectedUserMetadata.put("sourceFuid", expectedUser != null ? expectedUser.getFuid() : null);
expectedUserMetadata.put("sourceFullname", expectedUser != null ? expectedUser.getFullname() : null);
expectedUserMetadata.put("sourceUsername", expectedUser != null ? expectedUser.getUsername() : null);
expectedUserMetadata.put("sourceVersion", expectedUser != null ? expectedUser.getVersion() : null);
validateEventMetadata(eventMetadata, expectedUserMetadata, expectedUser != null);
// Validate account generated event metadata
Map<String, Object> expectedAccountMetadata = new HashMap<>();
expectedAccountMetadata.put("accountFuid", expectedAccount != null ? expectedAccount.getFuid() : null);
expectedAccountMetadata.put("accountName", expectedAccount != null ? expectedAccount.getName() : null);
expectedAccountMetadata.put("accountVersion", expectedAccount != null ? expectedAccount.getVersion() : null);
validateEventMetadata(eventMetadata, expectedAccountMetadata, expectedAccount != null);
// Validate target generated metadata
if (expectedTarget != null) {
// Validate object version
Assert.assertEquals(eventMetadata.get("targetVersion"), expectedTarget.getVersion());
// Validate object type (Class's simple name)
Assert.assertEquals(eventMetadata.get("targetType"), expectedTarget.getClass().getSimpleName());
// Validate SobaObject generated metadata
if (expectedTarget instanceof SobaObject) {
SobaObject tSobaObject = (SobaObject) expectedTarget;
Map<String, Object> sExpectedMetadata = new HashMap<>();
sExpectedMetadata.put("targetAlias", tSobaObject.getAlias());
sExpectedMetadata.put("targetHashtags", tSobaObject.getHashtags());
validateEventMetadata(eventMetadata, sExpectedMetadata, true);
}
// Validate the generated metadata based on object type
Map<String, Object> expectedObjectMetadata = new HashMap<>();
if (expectedTarget instanceof Account) {
Account tAccount = (Account) expectedTarget;
expectedObjectMetadata.put("targetFuid", tAccount.getFuid());
expectedObjectMetadata.put("targetName", tAccount.getName());
} else if (expectedTarget instanceof InventoryItem) {
InventoryItem tInventoryItem = (InventoryItem) expectedTarget;
Connection tConnection = tInventoryItem.getConnection();
ConnectionProvider tConnectionProvider =
applicationManager.getConnectionProviderFactory()
.connectionProviderFromId(tConnection.getProviderId());
Map<String, Object> expectedInventoryItemMetadata = new HashMap<>();
InventoryService inventoryService = applicationManager.getInventoryService();
expectedObjectMetadata.put("targetExternalId", tInventoryItem.getExternalId());
expectedInventoryItemMetadata.put("targetConnectionId", tConnection.getId());
expectedInventoryItemMetadata.put("targetConnectionAlias", tConnection.getAlias());
expectedInventoryItemMetadata.put("targetConnectionHashtags", tConnection.getHashtags());
expectedInventoryItemMetadata.put("targetConnectionVersion", tConnection.getVersion());
expectedInventoryItemMetadata.put("targetProviderId", tConnectionProvider.getId());
expectedInventoryItemMetadata.put("targetProviderDisplayName", tConnectionProvider.getDisplayName());
expectedInventoryItemMetadata.put("targetProviderType", tConnectionProvider.getType());
if (tInventoryItem.getConnection().getProviderId().equals(ProviderIdConstants.AWS_PROVIDER_ID)) {
Location zone = inventoryService.getLocationByScope(tInventoryItem, LocationScope.ZONE);
Location region = inventoryService.getLocationByScope(tInventoryItem, LocationScope.REGION);
Set<String> iso3166Codes = zone != null ? zone.getIso3166Codes() : null;
String iso3166Code = iso3166Codes != null && iso3166Codes.size() >= 1 ?
iso3166Codes.iterator().next() :
null;
if (tInventoryItem.getType().equals(Constants.COMPUTE_INSTANCE_TYPE)) {
expectedObjectMetadata.put("targetIP",
inventoryService.getComputeInstanceIPAddress(tInventoryItem));
expectedObjectMetadata.put("targetOS",
inventoryService.getComputeInstanceOSName(tInventoryItem));
}
if (iso3166Code != null) {
expectedObjectMetadata.put("targetISO3166Code", iso3166Code);
}
if (zone != null) {
expectedObjectMetadata.put("targetZone", zone.getId());
}
if (region != null) {
expectedObjectMetadata.put("targetRegion", region.getId());
}
}
validateEventMetadata(eventMetadata, expectedInventoryItemMetadata, true);
} else if (expectedTarget instanceof Connection) {
Connection tConnection = (Connection) expectedTarget;
ConnectionProvider tConnectionProvider = applicationManager.getConnectionProviderFactory()
.connectionProviderFromId(
tConnection.getProviderId());
expectedObjectMetadata.put("targetProviderId", tConnectionProvider.getId());
expectedObjectMetadata.put("targetProviderDisplayName", tConnectionProvider.getDisplayName());
expectedObjectMetadata.put("targetProviderType", tConnectionProvider.getType());
} else if (expectedTarget instanceof User) {
User tUser = (User) expectedTarget;
expectedObjectMetadata.put("targetFuid", tUser.getFuid());
expectedObjectMetadata.put("targetFullname", tUser.getFullname());
expectedObjectMetadata.put("targetUsername", tUser.getUsername());
}
if (expectedObjectMetadata.size() > 0) {
validateEventMetadata(eventMetadata, expectedObjectMetadata, true);
}
}
}
/**
* Helper method to check the event metadata for values or make sure the values aren't there.
*
* @param eventMetadata the event's actual metadata
* @param expectedEventMetadata the expected event metadata
* @param keysExpected whether the expected event metadata keys should or should not exist
*/
private void validateEventMetadata(Map<String, Object> eventMetadata, Map<String, Object> expectedEventMetadata,
boolean keysExpected) {
if (keysExpected) {
for (Map.Entry<String, Object> entry : expectedEventMetadata.entrySet()) {
String key = entry.getKey();
Object actualValue = eventMetadata.get(entry.getKey());
Object expectedValue = expectedEventMetadata.get(key);
try {
if (actualValue instanceof Collection || expectedValue instanceof Collection) {
Assert.assertEquals(actualValue != null ? ((Collection) actualValue).size() : 0,
expectedValue != null ? ((Collection) expectedValue).size() : 0);
} else {
Assert.assertEquals(actualValue, expectedValue);
}
} catch (AssertionError ae) {
System.out.println("Expected event metadata key (" + key + ") did not match expected " +
"value: " + expectedEventMetadata.get(key));
throw ae;
}
}
} else {
for (String key : expectedEventMetadata.keySet()) {
try {
Assert.assertFalse(eventMetadata.containsKey(key));
} catch (AssertionError ae) {
System.out.println("Unexpected event metadata key was present: " + key);
}
}
}
}
}