package com.thinkbiganalytics.alerts.api.core;
/*-
* #%L
* thinkbig-alerts-core
* %%
* Copyright (C) 2017 ThinkBig Analytics
* %%
* 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.
* #L%
*/
import com.google.common.base.Function;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.MoreExecutors;
import com.thinkbiganalytics.alerts.api.Alert;
import com.thinkbiganalytics.alerts.api.Alert.State;
import com.thinkbiganalytics.alerts.api.AlertChangeEvent;
import com.thinkbiganalytics.alerts.api.AlertCriteria;
import com.thinkbiganalytics.alerts.api.AlertListener;
import com.thinkbiganalytics.alerts.api.AlertResponder;
import com.thinkbiganalytics.alerts.api.AlertResponse;
import com.thinkbiganalytics.alerts.api.core.AggregatingAlertProvider.AlertInvocationHandler;
import com.thinkbiganalytics.alerts.api.core.AggregatingAlertProvider.SourceAlertID;
import com.thinkbiganalytics.alerts.spi.AlertManager;
import com.thinkbiganalytics.alerts.spi.AlertSource;
import com.thinkbiganalytics.metadata.event.reactor.ReactorConfiguration;
import com.thinkbiganalytics.security.UsernamePrincipal;
import org.joda.time.DateTime;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Scope;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.io.Serializable;
import java.lang.reflect.Proxy;
import java.net.URI;
import java.security.Principal;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = {ReactorConfiguration.class, AggregatingAlertProviderTest.TestConfig.class})
public class AggregatingAlertProviderTest {
@Inject
private AggregatingAlertProvider provider;
@Mock
private AlertSource source;
@Mock
private AlertManager manager;
@Mock
private AlertResponse response;
@Mock
private AlertListener listener;
@Mock
private AlertResponder responder;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
provider.addListener(this.listener);
provider.addResponder(this.responder);
when(this.source.criteria()).thenReturn(new BaseAlertCriteria());
when(this.manager.criteria()).thenReturn(new BaseAlertCriteria());
}
@Test
public void testResolve() {
TestAlert mgrAlert = new TestAlert(this.manager);
TestAlert srcAlert = new TestAlert(this.source);
SourceAlertID mgrId = new SourceAlertID(mgrAlert.getId(), this.manager);
SourceAlertID srcId = new SourceAlertID(srcAlert.getId(), this.source);
String mgrIdStr = mgrId.toString();
String srcIdStr = srcId.toString();
this.provider.addAlertSource(this.source);
this.provider.addAlertManager(this.manager);
when(this.source.resolve(any(TestID.class))).thenReturn(srcAlert.getId());
when(this.manager.resolve(any(TestID.class))).thenReturn(mgrAlert.getId());
when(this.source.resolve(any(String.class))).thenReturn(srcAlert.getId());
when(this.manager.resolve(any(String.class))).thenReturn(mgrAlert.getId());
Alert.ID mgrObjResolved = this.provider.resolve(mgrId);
Alert.ID srcObjResolved = this.provider.resolve(srcId);
Alert.ID mgrStrResolved = this.provider.resolve(mgrIdStr);
Alert.ID srcStrResolved = this.provider.resolve(srcIdStr);
assertThat(mgrObjResolved).isInstanceOf(SourceAlertID.class).isEqualTo(mgrId).isEqualTo(mgrStrResolved);
assertThat(srcObjResolved).isInstanceOf(SourceAlertID.class).isEqualTo(srcId).isEqualTo(srcStrResolved);
}
@Test
public void testGetAlert() {
TestAlert mgrAlert = new TestAlert(this.manager);
TestAlert srcAlert = new TestAlert(this.source);
SourceAlertID mgrId = new SourceAlertID(mgrAlert.getId(), this.manager);
SourceAlertID srcId = new SourceAlertID(srcAlert.getId(), this.source);
this.provider.addAlertSource(this.source);
this.provider.addAlertManager(this.manager);
when(this.source.getAlert(any(Alert.ID.class))).thenReturn(Optional.of(srcAlert));
when(this.manager.getAlert(any(Alert.ID.class))).thenReturn(Optional.of(mgrAlert));
Alert getMgrAlert = providerToSourceAlertFunction().apply(this.provider.getAlert(mgrId).get());
Alert getSrcAlert = providerToSourceAlertFunction().apply(this.provider.getAlert(srcId).get());
assertThat(getMgrAlert).isEqualTo(mgrAlert);
assertThat(getSrcAlert).isEqualTo(srcAlert);
}
@Test
public void testGetAlertsSinceTimeSourceOnly() {
DateTime since = DateTime.now().minusSeconds(1);
TestAlert srcAlert = new TestAlert(this.source);
this.provider.addAlertSource(this.source);
when(this.source.getAlerts(any(AlertCriteria.class))).thenAnswer(iteratorAnswer(srcAlert));
Iterator<? extends Alert> results = this.provider.getAlertsAfter(since);
Iterator<? extends Alert> itr = Iterators.transform(results, providerToSourceAlertFunction());
Alert alert = itr.next();
assertThat(alert).isEqualTo(srcAlert);
}
@Test
public void testGetAlertsSinceTimeManagerOnly() {
DateTime since = DateTime.now().minusSeconds(1);
TestAlert mgrAlert = new TestAlert(this.manager);
this.provider.addAlertManager(this.manager);
when(this.manager.getAlerts(any(AlertCriteria.class))).thenAnswer(iteratorAnswer(mgrAlert));
Iterator<? extends Alert> results = this.provider.getAlertsAfter(since);
Iterator<? extends Alert> itr = Iterators.transform(results, providerToSourceAlertFunction());
Alert alert = itr.next();
assertThat(alert).isEqualTo(mgrAlert);
}
@Test
public void testGetAlertsSinceTimeAllSources() {
DateTime since = DateTime.now().minusSeconds(1);
TestAlert mgrAlert = new TestAlert(this.manager);
TestAlert srcAlert = new TestAlert(this.source);
this.provider.addAlertSource(this.source);
this.provider.addAlertManager(this.manager);
when(this.source.getAlerts(any(AlertCriteria.class))).thenAnswer(iteratorAnswer(srcAlert));
when(this.manager.getAlerts(any(AlertCriteria.class))).thenAnswer(iteratorAnswer(mgrAlert));
Iterator<? extends Alert> results = this.provider.getAlertsAfter(since);
Iterator<? extends Alert> itr = Iterators.transform(results, providerToSourceAlertFunction());
List<Alert> alerts = Lists.newArrayList(itr);
assertThat(alerts).hasSize(2).contains(srcAlert, mgrAlert);
}
@Test
public void testRespondToActionable() {
TestAlert mgrAlert = new TestAlert(this.manager, true);
this.provider.addAlertManager(this.manager);
when(this.manager.getAlert(any(Alert.ID.class))).thenReturn(Optional.of(mgrAlert));
this.provider.respondTo(new SourceAlertID(mgrAlert.getId(), this.manager), this.responder);
verify(this.responder).alertChange(any(Alert.class), any(AlertResponse.class));
}
@Test
public void testRespondToNonActionable() {
TestAlert mgrAlert = new TestAlert(this.manager, false);
this.provider.addAlertManager(this.manager);
when(this.manager.getAlert(any(Alert.ID.class))).thenReturn(Optional.of(mgrAlert));
this.provider.respondTo(new SourceAlertID(mgrAlert.getId(), this.manager), this.responder);
verify(this.responder, never()).alertChange(any(Alert.class), any(AlertResponse.class));
}
@Test
public void testRespondToChange() throws InterruptedException {
TestAlert initMgrAlert = new TestAlert(this.manager, true);
CountDownLatch latch = new CountDownLatch(1);
this.provider.addAlertManager(this.manager);
when(this.manager.getAlert(any(Alert.ID.class))).thenReturn(Optional.of(initMgrAlert));
when(this.manager.getResponse(any(Alert.class))).thenReturn(this.response);
when(this.response.handle(any(String.class), any(Serializable.class))).thenReturn(initMgrAlert);
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
latch.countDown();
return null;
}
}).when(this.listener).alertChange(any(Alert.class));
this.provider.respondTo(new SourceAlertID(initMgrAlert.getId(), this.manager), new AlertResponder() {
@Override
public void alertChange(Alert alert, AlertResponse response) {
response.handle(null, "handled");
}
});
latch.await(10, TimeUnit.SECONDS);
verify(this.response).handle(eq(null), eq("handled"));
verify(this.listener, atLeastOnce()).alertChange(any(Alert.class));
}
@Test
public void testAlertsAvailable() throws InterruptedException {
TestAlert mgrAlert = new TestAlert(this.manager, true);
TestAlert srcAlert = new TestAlert(this.source);
CountDownLatch latch = new CountDownLatch(3);
this.provider.addAlertSource(this.source);
this.provider.addAlertManager(this.manager);
when(this.source.getAlerts(any(AlertCriteria.class))).thenAnswer(iteratorAnswer(srcAlert));
when(this.manager.getAlerts(any(AlertCriteria.class))).thenAnswer(iteratorAnswer(mgrAlert));
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
latch.countDown();
return null;
}
}).when(this.listener).alertChange(any(Alert.class));
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
latch.countDown();
return null;
}
}).when(this.responder).alertChange(any(Alert.class), any(AlertResponse.class));
this.provider.alertsAvailable(2);
latch.await(10, TimeUnit.SECONDS);
verify(this.listener, times(2)).alertChange(any(Alert.class));
verify(this.responder, times(1)).alertChange(any(Alert.class), any(AlertResponse.class));
}
private Answer<Iterator<? extends Alert>> iteratorAnswer(final Alert... alerts) {
return new Answer<Iterator<? extends Alert>>() {
@Override
public Iterator<? extends Alert> answer(InvocationOnMock invocation) throws Throwable {
return interator(alerts);
}
};
}
private Iterator<? extends Alert> interator(Alert... alerts) {
return Arrays.asList(alerts).iterator();
}
private Function<Alert, Alert> providerToSourceAlertFunction() {
return new Function<Alert, Alert>() {
@Override
public Alert apply(Alert input) {
if (Proxy.isProxyClass(input.getClass())) {
AlertInvocationHandler handler = (AlertInvocationHandler) Proxy.getInvocationHandler(input);
return handler.getWrappedAlert();
} else {
return input;
}
}
};
}
private static class TestID implements Alert.ID {
@Override
public String toString() {
return "TestID:" + hashCode();
}
}
@Configuration
public static class TestConfig {
@Bean(name = "alertProvider")
@Primary
@Scope("prototype")
public AggregatingAlertProvider alertProvider() {
AggregatingAlertProvider provider = new AggregatingAlertProvider();
provider.setAvailableAlertsExecutor(MoreExecutors.directExecutor());
return provider;
}
}
private class TestAlert implements Alert {
private TestID id = new TestID();
private AlertSource source;
private boolean actionable;
private DateTime createdTime;
private boolean cleared = false;
public TestAlert(AlertSource src) {
this(src, src instanceof AlertManager);
}
public TestAlert(AlertSource src, boolean actionable) {
this(src, actionable, DateTime.now());
}
public TestAlert(AlertSource src, DateTime created) {
this(src, src instanceof AlertManager, created);
}
public TestAlert(AlertSource src, boolean actionable, DateTime created) {
this.source = src;
this.actionable = actionable;
this.createdTime = created;
}
@Override
@SuppressWarnings("serial")
public ID getId() {
return this.id;
}
@Override
public URI getType() {
return URI.create("http://com.example/alert/test");
}
@Override
public String getDescription() {
return "";
}
@Override
public Level getLevel() {
return Level.INFO;
}
@Override
public DateTime getCreatedTime() {
return this.createdTime;
}
@Override
public AlertSource getSource() {
return this.source;
}
@Override
public boolean isActionable() {
return this.actionable;
}
@Override
public State getState() {
return this.getEvents().get(0).getState();
}
@Override
public boolean isCleared() {
return this.cleared;
}
@Override
public List<AlertChangeEvent> getEvents() {
return Collections.singletonList(new TestChangeEvent(this));
}
@Override
@SuppressWarnings("unchecked")
public <C extends Serializable> C getContent() {
return (C) new Boolean(this.actionable);
}
}
private class TestChangeEvent implements AlertChangeEvent {
private TestAlert alert;
private DateTime time = DateTime.now();
public TestChangeEvent(TestAlert alert) {
this.alert = alert;
this.time = alert.createdTime;
}
@Override
public DateTime getChangeTime() {
return this.time;
}
@Override
public String getDescription() {
return null;
}
@Override
public State getState() {
return alert.isActionable() ? State.CREATED : State.UNHANDLED;
}
@Override
public Principal getUser() {
return new UsernamePrincipal("test");
}
@Override
public <C extends Serializable> C getContent() {
return null;
}
}
}