/*
* Copyright (C) 2015
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.cleverbus.component.funnel;
import org.apache.camel.EndpointInject;
import org.apache.camel.Produce;
import org.apache.camel.ProducerTemplate;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.mock.MockEndpoint;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang.time.DateUtils;
import org.cleverbus.api.asynch.AsynchConstants;
import org.cleverbus.api.entity.Message;
import org.cleverbus.api.entity.MsgStateEnum;
import org.cleverbus.api.route.AbstractBasicRoute;
import org.cleverbus.component.AbstractComponentsDbTest;
import org.cleverbus.test.EntityTypeTestEnum;
import org.cleverbus.test.ExternalSystemTestEnum;
import org.cleverbus.test.ServiceTestEnum;
import org.hamcrest.CoreMatchers;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.transaction.annotation.Transactional;
import java.util.Arrays;
import java.util.Date;
import java.util.UUID;
import static org.apache.camel.component.mock.MockEndpoint.assertIsSatisfied;
/**
* Test suite for {@link MsgFunnelComponent}.
*
* @author <a href="mailto:petr.juza@cleverlance.com">Petr Juza</a>
*/
@Transactional
public class MsgFunnelComponentTest extends AbstractComponentsDbTest {
private static final String MSG_BODY = "some body";
private static final String FUNNEL_VALUE = "774724557";
private static final String DIFFERENT_FUNNEL_VALUE = "DIFFERENT_FUEL_VALUE";
private static final String FUNNEL_ID = "myFunnelId";
@Produce(uri = "direct:start")
private ProducerTemplate producer;
@Produce(uri = "direct:startGuaranteed")
private ProducerTemplate producerForGuaranteed;
@Produce(uri = "direct:startGuaranteedWithoutFailed")
private ProducerTemplate producerForGuaranteedWithoutFailed;
@Produce(uri = "direct:startFunnelValue")
private ProducerTemplate producerForFunnelValue;
@Produce(uri = "direct:startFunnelValueGuaranteed")
private ProducerTemplate producerForFunnelValueGuaranteed;
@Produce(uri = "direct:startDifferentFunnelValueGuaranteed")
private ProducerTemplate producerForDifferentFunnelValueGuaranteed;
@EndpointInject(uri = "mock:test")
private MockEndpoint mock;
private Message firstMsg;
@Before
public void prepareMessage() throws Exception {
firstMsg = createMessage(FUNNEL_VALUE);
em.persist(firstMsg);
em.flush();
}
protected static Message createMessage(String... funnelValue) {
Date currDate = new Date();
Message msg = new Message();
msg.setState(MsgStateEnum.PROCESSING);
msg.setMsgTimestamp(currDate);
msg.setReceiveTimestamp(currDate);
msg.setSourceSystem(ExternalSystemTestEnum.CRM);
msg.setCorrelationId(UUID.randomUUID().toString());
msg.setStartProcessTimestamp(currDate);
msg.setService(ServiceTestEnum.CUSTOMER);
msg.setOperationName("setCustomer");
msg.setPayload("payload");
msg.setLastUpdateTimestamp(currDate);
msg.setObjectId("objectID");
msg.setEntityType(EntityTypeTestEnum.ACCOUNT);
if (funnelValue != null && funnelValue.length != 0) {
msg.setFunnelValues(Arrays.asList(funnelValue));
}
msg.setFunnelComponentId(FUNNEL_ID);
return msg;
}
@Before
public void prepareRoutes() throws Exception {
RouteBuilder defaultRoute = new AbstractBasicRoute() {
@Override
public void doConfigure() throws Exception {
from("direct:start")
.to("msg-funnel:default?idleInterval=50&id=" + FUNNEL_ID)
.to("mock:test");
}
};
getCamelContext().addRoutes(defaultRoute);
RouteBuilder guaranteedRoute = new AbstractBasicRoute() {
@Override
public void doConfigure() throws Exception {
from("direct:startGuaranteed")
.to("msg-funnel:default?idleInterval=50&guaranteedOrder=true&id=" + FUNNEL_ID)
.to("mock:test");
}
};
getCamelContext().addRoutes(guaranteedRoute);
RouteBuilder guaranteedWithoutFailedRoute = new AbstractBasicRoute() {
@Override
public void doConfigure() throws Exception {
from("direct:startGuaranteedWithoutFailed")
.to("msg-funnel:default?idleInterval=50&guaranteedOrder=true&excludeFailedState=true&id=" + FUNNEL_ID)
.to("mock:test");
}
};
getCamelContext().addRoutes(guaranteedWithoutFailedRoute);
RouteBuilder funnelValueRoute = new AbstractBasicRoute() {
@Override
public void doConfigure() throws Exception {
from("direct:startFunnelValue")
.to("msg-funnel:default?idleInterval=50&id=" + FUNNEL_ID + "&funnelValue=" + FUNNEL_VALUE)
.to("mock:test");
}
};
getCamelContext().addRoutes(funnelValueRoute);
RouteBuilder guaranteedFunnelValueRoute = new AbstractBasicRoute() {
@Override
public void doConfigure() throws Exception {
from("direct:startFunnelValueGuaranteed")
.to("msg-funnel:default?idleInterval=50&guaranteedOrder=true&id=" + FUNNEL_ID
+ "&funnelValue=" + FUNNEL_VALUE)
.to("mock:test");
}
};
getCamelContext().addRoutes(guaranteedFunnelValueRoute);
RouteBuilder guaranteedDifferentFunnelValueRoute = new AbstractBasicRoute() {
@Override
public void doConfigure() throws Exception {
from("direct:startDifferentFunnelValueGuaranteed")
.to("msg-funnel:default?idleInterval=50&guaranteedOrder=true&id=" + FUNNEL_ID
+ "&funnelValue=" + DIFFERENT_FUNNEL_VALUE)
.to("mock:test");
}
};
getCamelContext().addRoutes(guaranteedDifferentFunnelValueRoute);
}
@Test
public void testFunnel() throws Exception {
mock.setExpectedMessageCount(0);
Message msg = createMessage(FUNNEL_VALUE);
em.persist(msg);
em.flush();
// send message with same funnel value => postpone it
producer.sendBodyAndHeader(MSG_BODY, AsynchConstants.MSG_HEADER, msg);
assertIsSatisfied(mock);
Assert.assertThat(em.find(Message.class, msg.getMsgId()).getState(), CoreMatchers.is(MsgStateEnum.POSTPONED));
}
@Test
public void testFunnel_waitingForResponse() throws Exception {
mock.setExpectedMessageCount(1);
Message msg = createMessage(FUNNEL_VALUE);
msg.setStartProcessTimestamp(DateUtils.addSeconds(new Date(), -MsgFunnelEndpoint.DEFAULT_IDLE_INTERVAL - 100));
msg.setState(MsgStateEnum.WAITING_FOR_RES);
em.persist(msg);
em.flush();
producer.sendBodyAndHeader(MSG_BODY, AsynchConstants.MSG_HEADER, msg);
assertIsSatisfied(mock);
Assert.assertThat(em.find(Message.class, msg.getMsgId()).getState(),
CoreMatchers.is(MsgStateEnum.WAITING_FOR_RES));
}
@Test
public void testFunnel_noFilter() throws Exception {
mock.setExpectedMessageCount(1);
Message msg = createMessage("777123456");
// send message with different funnel value
producer.sendBodyAndHeader(MSG_BODY, AsynchConstants.MSG_HEADER, msg);
mock.assertIsSatisfied();
}
@Test
public void testWithoutFunnel() throws Exception {
mock.setExpectedMessageCount(1);
Message msg = createMessage(null);
// input message doesn't have funnel value
producer.sendBodyAndHeader(MSG_BODY, AsynchConstants.MSG_HEADER, msg);
mock.assertIsSatisfied();
}
@Test
public void testFunnelForGuaranteedOrder_onlyCurrentMessage() throws Exception {
mock.setExpectedMessageCount(1);
// send one message only
producerForGuaranteed.sendBodyAndHeader(MSG_BODY, AsynchConstants.MSG_HEADER, firstMsg);
assertIsSatisfied(mock);
}
@Test
public void testFunnelForGuaranteedOrder_firstMessage() throws Exception {
mock.setExpectedMessageCount(1);
Message msg = createMessage(FUNNEL_VALUE);
msg.setMsgTimestamp(DateUtils.addSeconds(firstMsg.getMsgTimestamp(), -100)); // be before "first" message
msg.setState(MsgStateEnum.PROCESSING);
em.persist(msg);
em.flush();
// send message has "msgTimestamp" before another processing message
producerForGuaranteed.sendBodyAndHeader(MSG_BODY, AsynchConstants.MSG_HEADER, msg);
assertIsSatisfied(mock);
Assert.assertThat(em.find(Message.class, msg.getMsgId()).getState(), CoreMatchers.is(MsgStateEnum.PROCESSING));
}
@Test
public void testFunnelForGuaranteedOrder_postponeMessage() throws Exception {
mock.setExpectedMessageCount(0);
Message msg = createMessage(FUNNEL_VALUE);
msg.setMsgTimestamp(DateUtils.addSeconds(firstMsg.getMsgTimestamp(), 100)); // be after "first" message
msg.setState(MsgStateEnum.PROCESSING);
em.persist(msg);
em.flush();
// send message has "msgTimestamp" after another processing message => postpone it
producerForGuaranteed.sendBodyAndHeader(MSG_BODY, AsynchConstants.MSG_HEADER, msg);
assertIsSatisfied(mock);
Assert.assertThat(em.find(Message.class, msg.getMsgId()).getState(), CoreMatchers.is(MsgStateEnum.POSTPONED));
}
@Test
public void testFunnelForGuaranteedOrder_excludeFailedState() throws Exception {
mock.setExpectedMessageCount(1);
Message msg = createMessage(FUNNEL_VALUE);
msg.setMsgTimestamp(DateUtils.addSeconds(firstMsg.getMsgTimestamp(), 100)); // be after "first" message, but FAILED
msg.setState(MsgStateEnum.FAILED);
em.persist(msg);
em.flush();
// send message has "msgTimestamp" after another processing message but in FAILED state
// that is excluded => continue
producerForGuaranteedWithoutFailed.sendBodyAndHeader(MSG_BODY, AsynchConstants.MSG_HEADER, msg);
assertIsSatisfied(mock);
Assert.assertThat(em.find(Message.class, msg.getMsgId()).getState(), CoreMatchers.is(MsgStateEnum.FAILED));
}
@Test
public void testFunnelForOwnFunnelValue() throws Exception {
mock.setExpectedMessageCount(0);
//message without funnel value
Message msg = createMessage(null);
em.persist(msg);
em.flush();
// send message with setting funnel value in route => postpone it
producerForFunnelValue.sendBodyAndHeader(MSG_BODY, AsynchConstants.MSG_HEADER, msg);
assertIsSatisfied(mock);
msg = em.find(Message.class, msg.getMsgId());
Assert.assertNotNull(msg);
Assert.assertThat(msg.getState(), CoreMatchers.is(MsgStateEnum.POSTPONED));
Assert.assertTrue(msg.getFunnelValues().contains(FUNNEL_VALUE));
}
@Test
public void testFunnelForOwnFunnelValue_withValue() throws Exception {
mock.setExpectedMessageCount(0);
//message without different funnel value
Message msg = createMessage(DIFFERENT_FUNNEL_VALUE);
em.persist(msg);
em.flush();
// send message with setting funnel value in route => postpone it
producerForFunnelValue.sendBodyAndHeader(MSG_BODY, AsynchConstants.MSG_HEADER, msg);
assertIsSatisfied(mock);
msg = em.find(Message.class, msg.getMsgId());
Assert.assertNotNull(msg);
Assert.assertThat(msg.getState(), CoreMatchers.is(MsgStateEnum.POSTPONED));
Assert.assertEquals(2, msg.getFunnelValues().size());
Assert.assertTrue(msg.getFunnelValues().contains(FUNNEL_VALUE));
Assert.assertTrue(msg.getFunnelValues().contains(DIFFERENT_FUNNEL_VALUE));
}
@Test
public void testFunnelForOwnFunnelValueGuaranteed_Postponed() throws Exception {
mock.setExpectedMessageCount(0);
Message msg = createMessage(FUNNEL_VALUE);
msg.setMsgTimestamp(DateUtils.addSeconds(firstMsg.getMsgTimestamp(), 100)); // be after "first" message
msg.setState(MsgStateEnum.PROCESSING);
em.persist(msg);
em.flush();
// input message to route with different funnel value
producerForDifferentFunnelValueGuaranteed.sendBodyAndHeader(MSG_BODY, AsynchConstants.MSG_HEADER, msg);
mock.assertIsSatisfied();
msg = em.find(Message.class, msg.getMsgId());
Assert.assertNotNull(msg);
Assert.assertThat(msg.getState(), CoreMatchers.is(MsgStateEnum.POSTPONED));
Assert.assertTrue(CollectionUtils.isEqualCollection(msg.getFunnelValues(),
Arrays.asList(FUNNEL_VALUE, DIFFERENT_FUNNEL_VALUE)));
}
@Test
public void testFunnelForOwnFunnelValueGuaranteedNoValue_Processing() throws Exception {
mock.setExpectedMessageCount(1);
Message msg = createMessage(null);
msg.setMsgTimestamp(DateUtils.addSeconds(firstMsg.getMsgTimestamp(), 100)); // be after "first" message
msg.setState(MsgStateEnum.PROCESSING);
em.persist(msg);
em.flush();
// input message to route with different funnel value
producerForDifferentFunnelValueGuaranteed.sendBodyAndHeader(MSG_BODY, AsynchConstants.MSG_HEADER, msg);
mock.assertIsSatisfied();
msg = em.find(Message.class, msg.getMsgId());
Assert.assertNotNull(msg);
Assert.assertThat(msg.getState(), CoreMatchers.is(MsgStateEnum.PROCESSING));
Assert.assertTrue(msg.getFunnelValues().contains(DIFFERENT_FUNNEL_VALUE));
}
@Test
public void testFunnelForOwnFunnelValueGuaranteed_firstMessage() throws Exception {
mock.setExpectedMessageCount(1);
Message msg = createMessage(null);
msg.setMsgTimestamp(DateUtils.addSeconds(firstMsg.getMsgTimestamp(), -100)); // be before "first" message
msg.setState(MsgStateEnum.PROCESSING);
em.persist(msg);
em.flush();
// send message has "msgTimestamp" before another processing message
producerForFunnelValueGuaranteed.sendBodyAndHeader(MSG_BODY, AsynchConstants.MSG_HEADER, msg);
assertIsSatisfied(mock);
msg = em.find(Message.class, msg.getMsgId());
Assert.assertNotNull(msg);
Assert.assertThat(msg.getState(), CoreMatchers.is(MsgStateEnum.PROCESSING));
Assert.assertTrue(msg.getFunnelValues().contains(FUNNEL_VALUE));
}
@Test
public void testFunnelForOwnFunnelValueGuaranteed_postponeMessage() throws Exception {
mock.setExpectedMessageCount(0);
Message msg = createMessage(null);
msg.setMsgTimestamp(DateUtils.addSeconds(firstMsg.getMsgTimestamp(), 100)); // be after "first" message
msg.setState(MsgStateEnum.PROCESSING);
em.persist(msg);
em.flush();
// send message has "msgTimestamp" after another processing message => postpone it
producerForFunnelValueGuaranteed.sendBodyAndHeader(MSG_BODY, AsynchConstants.MSG_HEADER, msg);
assertIsSatisfied(mock);
msg = em.find(Message.class, msg.getMsgId());
Assert.assertNotNull(msg);
Assert.assertThat(msg.getState(), CoreMatchers.is(MsgStateEnum.POSTPONED));
Assert.assertTrue(msg.getFunnelValues().contains(FUNNEL_VALUE));
}
}