package org.marketcetera.modules.cep.system; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertSame; import java.math.BigDecimal; import java.util.*; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.marketcetera.core.ExpectedTestFailure; import org.marketcetera.core.notifications.Notification; import org.marketcetera.event.*; import org.marketcetera.event.impl.LogEventBuilder; import org.marketcetera.module.*; import org.marketcetera.quickfix.CurrentFIXDataDictionary; import org.marketcetera.quickfix.FIXDataDictionary; import org.marketcetera.quickfix.FIXMessageUtilTest; import org.marketcetera.quickfix.FIXVersion; import org.marketcetera.trade.*; import quickfix.Message; import quickfix.field.Symbol; import quickfix.field.Text; /** * Base case for CEP test classes - has some basic functionality for * running data flows through and checking for output * * @author toli@marketcetera.com * @version $Id: CEPTestBase.java 16841 2014-02-20 19:59:04Z colin $ * @since 1.0.0 */ public abstract class CEPTestBase extends ModuleTestBase { protected FIXDataDictionary fixDD; protected ModuleManager sManager; protected static BlockingSinkDataListener sSink; protected static Factory sFactory; // Subclasses shoudl specify protected abstract ModuleURN getModuleURN(); // List of all events that we send in to all the test cases along with individual nams for them protected List<Object> allSentEvents; protected BidEvent bid1, bid2; protected AskEvent ask1, ask2; protected TradeEvent trade1, trade2; protected LogEvent log1, log2; protected MarketstatEvent mStat1, mStat2; protected Suggestion sug1, sug2; protected Notification not1, not2; protected OrderSingle os1, os2; protected OrderCancel oc1, oc2; protected OrderReplace or1, or2; protected OrderCancelReject ocr1, ocr2; protected FIXOrder fo1, fo2; protected ExecutionReport er1, er2; protected Map<Integer, String> map1, map2; @Before public void before() throws Exception { sSink = new BlockingSinkDataListener(); sManager = new ModuleManager(); sManager.init(); sManager.addSinkListener(sSink); CurrentFIXDataDictionary.setCurrentFIXDataDictionary(new FIXDataDictionary(FIXVersion.FIX_SYSTEM.getDataDictionaryURL())); // pre-create and pre-specify all events ask1 = EventTestBase.generateEquityAskEvent(1, 2, new Equity("ABC"), "nyse", new BigDecimal("23"), new BigDecimal("23")); ask2 = EventTestBase.generateEquityAskEvent(1, 2, new Equity("BIDU"), "nyse", new BigDecimal("23"), new BigDecimal("23")); bid1 = EventTestBase.generateEquityBidEvent(1, 2, new Equity("CSCO"), "nyse", new BigDecimal("23"), new BigDecimal("23")); bid2 = EventTestBase.generateEquityBidEvent(1, 2, new Equity("DELL"), "nyse", new BigDecimal("23"), new BigDecimal("23")); trade1 = EventTestBase.generateEquityTradeEvent(1, 2, new Equity("ECHO"), "nyse", new BigDecimal("23"), new BigDecimal("23")); trade2 = EventTestBase.generateEquityTradeEvent(1, 2, new Equity("FIGA"), "nyse", new BigDecimal("23"), new BigDecimal("23")); log1 = LogEventBuilder.debug().withMessage(Messages.PROVIDER_DESCRIPTION).create(); log2 = LogEventBuilder.error().withMessage(Messages.PROVIDER_DESCRIPTION).create(); mStat1 = EventTestBase.generateEquityMarketstatEvent(new Equity("ABC"),new Date(), BigDecimal.ONE, BigDecimal.ONE, BigDecimal.ONE, BigDecimal.ONE, BigDecimal.ONE, BigDecimal.ONE, new Date(), new Date(), new Date(), new Date(), "OYSE", "HYSE", "LYSE","CYSE"); mStat2 = EventTestBase.generateEquityMarketstatEvent(new Equity("BIDU"),new Date(), BigDecimal.ONE, BigDecimal.ONE, BigDecimal.ONE, BigDecimal.ONE, BigDecimal.ONE, BigDecimal.ONE, new Date(), new Date(), new Date(), new Date(), "OYSE", "HYSE", "LYSE","CYSE"); sug1 = Factory.getInstance().createOrderSingleSuggestion(); sug1.setIdentifier("acura"); sug2 = Factory.getInstance().createOrderSingleSuggestion(); sug2.setIdentifier("integra"); not1 = Notification.low("kathmandu", "kathmandu", this.toString()); not2 = Notification.low("pokhara", "pokhara", this.toString()); os1 = sFactory.createOrderSingle(); os1.setBrokerID(new BrokerID("os1")); os2 = sFactory.createOrderSingle(); os2.setBrokerID(new BrokerID("os2")); // order cancel Message nos = FIXMessageUtilTest.createNOS("LADA", BigDecimal.ZERO, BigDecimal.ZERO, 'a', FIXVersion.FIX_SYSTEM.getMessageFactory()); Message can1 = FIXVersion.FIX_SYSTEM.getMessageFactory().newCancelFromMessage(nos); oc1 = sFactory.createOrderCancel(can1, new BrokerID("dest1")); Message can2 = FIXVersion.FIX_SYSTEM.getMessageFactory().newCancelFromMessage(nos); can2.setField(new Symbol("ZAPO")); oc2 = sFactory.createOrderCancel(can2, new BrokerID("dest2")); // order replace Message cxr1 = FIXVersion.FIX_SYSTEM.getMessageFactory().newCancelReplaceFromMessage(nos); or1 = sFactory.createOrderReplace(cxr1, new BrokerID("lada")); Message cxr2 = FIXVersion.FIX_SYSTEM.getMessageFactory().newCancelReplaceFromMessage(nos); cxr2.setField(new Symbol("ZAPO")); or2 = sFactory.createOrderReplace(cxr2, new BrokerID("zapo")); // fix order nos = FIXMessageUtilTest.createNOS("fixORDER", BigDecimal.ZERO, BigDecimal.ZERO, 'a', FIXVersion.FIX_SYSTEM.getMessageFactory()); fo1 = Factory.getInstance().createOrder(nos, new BrokerID("chuck")); fo2 = Factory.getInstance().createOrder(nos, new BrokerID("morgan")); // order cancel reject Message rej1 = FIXVersion.FIX42.getMessageFactory().newOrderCancelReject(); rej1.setField(new Text("GOOG")); ocr1 = sFactory.createOrderCancelReject(rej1, new BrokerID("dest"), Originator.Server, null, null); Message rej2 = FIXVersion.FIX42.getMessageFactory().newOrderCancelReject(); rej2.setField(new Text("CSCO")); ocr2 = sFactory.createOrderCancelReject(rej2, new BrokerID("dest"), Originator.Server, null, null); // execution report Message er1_fix = FIXVersion.FIX42.getMessageFactory().newExecutionReport("orderid", "clOrdID", "execID", 'a', 'b', BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, new Equity("IFLI"), "acct","text"); er1 = sFactory.createExecutionReport(er1_fix, new BrokerID("dest1"), Originator.Server, null, null); Message er2_fix = FIXVersion.FIX42.getMessageFactory().newExecutionReport("orderid", "clOrdID", "execID", 'a', 'b', BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, new Equity("GOOG"), "acct","text"); er2 = sFactory.createExecutionReport(er2_fix, new BrokerID("dest2"), Originator.Server, null, null); // map map1 = new HashMap<Integer, String>(); map1.put(0, "bob"); map1.put(1, "bubba"); map2 = new HashMap<Integer, String>(); map2.put(3, "fred"); map2.put(4, "fedya"); // initialize the mongo array we'll be passing in to all data flows, plus some random other objects allSentEvents = Arrays.asList(ask1, ask2, bid1, bid2, trade1, trade2, sug1, sug2, not1, not2, os1, 37, os2, oc1, oc2, or1, or2, ocr1, ocr2, fo1, fo2, er1, 42, er2, "pupkin", map1, map2, log1, log2, mStat1, mStat2); } @After public void after() throws Exception { sManager.removeSinkListener(sSink); sManager.stop(); } @Test public void testInvalidDataRequestArgument() throws Exception { // send in a null request new ExpectedTestFailure(IllegalRequestParameterValue.class, org.marketcetera.module.Messages.ILLEGAL_REQ_PARM_VALUE.getText(getModuleURN(), null)) { protected void execute() throws Throwable { sManager.createDataFlow(new DataRequest[] {new DataRequest(getModuleURN(), null)}); } }.run(); } /** See what happens when you send in a non-string request parameter - should error out */ @Test(timeout=120000) public void testNonStringRequestParameter() throws Exception { new ExpectedTestFailure(UnsupportedRequestParameterType.class, org.marketcetera.module.Messages.UNSUPPORTED_REQ_PARM_TYPE.getText(getModuleURN(), Integer.class.getName())) { protected void execute() throws Throwable { sManager.createDataFlow(new DataRequest[] { // Copier -> System: send 1 events new DataRequest(CopierModuleFactory.INSTANCE_URN, new Event[] { EventTestBase.generateEquityTradeEvent(3, 4, new Equity("IBM"), "NYSE", new BigDecimal("85"), new BigDecimal("200")), }), // System -> Sink: only get 1 bid event new DataRequest(getModuleURN(), 37) // invalid request param }); } }.run(); } /** Subclasses should specify the error class that's thrown in case of incorrect syntax for query */ protected abstract Class<?> getIncorrectQueryException(); /** unit test that verifies failure for incorrect query syntax and invalid type name */ @Test(timeout=120000) public void testIncorrectQuerySyntax() throws Exception { final String query = "man, is this syntax incorrect or what??"; new ExpectedTestFailure(getIncorrectQueryException()) { protected void execute() throws Throwable { sManager.createDataFlow(new DataRequest[] { // Copier -> System: send 1 events new DataRequest(CopierModuleFactory.INSTANCE_URN, new Event[] { EventTestBase.generateEquityTradeEvent(3, 4, new Equity("IBM"), "NYSE", new BigDecimal("85"), new BigDecimal("200")), }), // System -> Sink: only get 1 bid event new DataRequest(getModuleURN(), query) // invalid request param }); } }.run(); } /** Subclasses should implement a test that verifies the right exception is thrown in case of invalid type name in select */ public abstract void testUnknownAlias() throws Exception; /** Verify that a request with valid java class name can be created */ @Test(timeout=120000) public void testValidJavaClass() throws Exception { final DataFlowID flow1 = sManager.createDataFlow(new DataRequest[] { new DataRequest(CopierModuleFactory.INSTANCE_URN, new Event[] { EventTestBase.generateEquityTradeEvent(3, 4, new Equity("IBM"), "NYSE", new BigDecimal("85"), new BigDecimal("200")), EventTestBase.generateEquityBidEvent(1, 2, new Equity("IBM"), "NYSE", new BigDecimal("85"), new BigDecimal("100")), EventTestBase.generateEquityAskEvent(5, 6, new Equity("JAVA"), "NASDAQ", new BigDecimal("1.23"), new BigDecimal("300")) }), new DataRequest(getModuleURN(), "select * from "+java.awt.BorderLayout.class.getName()) }); sManager.cancel(flow1); } // send in an unmapped java object, like Integer for instance and verify that it gets filtered correctly. @Test(timeout=120000) public void testUnmappedJavaObject() throws Exception { final DataFlowID flow1 = sManager.createDataFlow(new DataRequest[] { new DataRequest(CopierModuleFactory.INSTANCE_URN, new Object[] { EventTestBase.generateEquityTradeEvent(3, 4, new Equity("IBM"), "NYSE", new BigDecimal("85"), new BigDecimal("200")), EventTestBase.generateEquityBidEvent(1, 2, new Equity("IBM"), "NYSE", new BigDecimal("85"), new BigDecimal("100")), 37, EventTestBase.generateEquityAskEvent(5, 6, new Equity("JAVA"), "NASDAQ", new BigDecimal("1.23"), new BigDecimal("300")) }), new DataRequest(getModuleURN(), "select * from "+java.lang.Integer.class.getName()) }); assertEquals(37, sSink.getNextData()); sManager.cancel(flow1); } /** Setup two data flows * Send some events through first one * Cancel it * verify that statements are gone, and then when you send similar events only 2nd data flow gets it * Then cancel the 2nd flow, and do a 3rd one that gets totally different events * verify that only 3rd-flow events are coming through, and not ones from 2nd or 1st flow */ @Test(timeout=120000) public void testCancel() throws Exception { final DataFlowID flow1 = sManager.createDataFlow(new DataRequest[] { // Copier -> System: send 3 events new DataRequest(CopierModuleFactory.INSTANCE_URN, new Event[] { EventTestBase.generateEquityTradeEvent(3, 4, new Equity("IBM"), "NYSE", new BigDecimal("85"), new BigDecimal("200")), EventTestBase.generateEquityBidEvent(1, 2, new Equity("IBM"), "NYSE", new BigDecimal("85"), new BigDecimal("100")), EventTestBase.generateEquityAskEvent(5, 6, new Equity("JAVA"), "NASDAQ", new BigDecimal("1.23"), new BigDecimal("300")) }), // System -> Sink: only get 1 bid event new DataRequest(getModuleURN(), "select * from "+BidEvent.class.getName()) }); BidEvent theBid = (BidEvent) sSink.getNextData(); assertEquals("didnt' get bid event", "IBM", theBid.getInstrumentAsString()); assertEquals("didnt' get right size", new BigDecimal("85"), theBid.getPrice()); assertEquals("CEP sent out extra events", 1, sManager.getDataFlowInfo(flow1).getFlowSteps()[1].getNumEmitted()); sManager.cancel(flow1); new ExpectedTestFailure(DataFlowNotFoundException.class, flow1.toString()) { protected void execute() throws Throwable { sManager.getDataFlowInfo(flow1); } }.run(); final DataFlowID flow2 = sManager.createDataFlow(new DataRequest[] { // Copier -> System: send 3 events new DataRequest(CopierModuleFactory.INSTANCE_URN, new Event[] { EventTestBase.generateEquityBidEvent(1, 2, new Equity("GOOG"), "NYSE", new BigDecimal("300"), new BigDecimal("100")), EventTestBase.generateEquityTradeEvent(3, 4, new Equity("IBM"), "NYSE", new BigDecimal("85"), new BigDecimal("200")), EventTestBase.generateEquityAskEvent(5, 6, new Equity("JAVA"), "NASDAQ", new BigDecimal("1.23"), new BigDecimal("300")) }), // System -> Sink: only get 1 bid event new DataRequest(getModuleURN(), "select * from "+BidEvent.class.getName()) }); theBid = (BidEvent) sSink.getNextData(); assertEquals("didnt' get bid event", "GOOG", theBid.getInstrumentAsString()); assertEquals("didnt' get right size", new BigDecimal("300"), theBid.getPrice()); assertEquals("CEP sent out extra events", 1, sManager.getDataFlowInfo(flow2).getFlowSteps()[1].getNumEmitted()); sManager.cancel(flow2); new ExpectedTestFailure(DataFlowNotFoundException.class, flow2.toString()) { protected void execute() throws Throwable { sManager.getDataFlowInfo(flow2); } }.run(); // now subscribe to a totally different set of events, send sme events through and verify that we only get 3rd kind of events TradeEvent tradeEvent = EventTestBase.generateEquityTradeEvent(3, 4, new Equity("IBM"), "NYSE", new BigDecimal("85"), new BigDecimal("200")); final DataFlowID flow3 = sManager.createDataFlow(new DataRequest[] { // Copier -> System: send 3 events new DataRequest(CopierModuleFactory.INSTANCE_URN, new Event[] { EventTestBase.generateEquityBidEvent(1, 2, new Equity("ZOOG"), "NYSE", new BigDecimal("300"), new BigDecimal("100")), EventTestBase.generateEquityAskEvent(5, 6, new Equity("JAVA"), "NASDAQ", new BigDecimal("1.23"), new BigDecimal("300")), tradeEvent, }), // CEP -> Sink: should only get trade event new DataRequest(getModuleURN(), "select * from "+TradeEvent.class.getName()) }); TradeEvent theTrade = (TradeEvent) sSink.getNextData(); assertSame("wrong event received", tradeEvent, theTrade); assertEquals("didnt' get bid event", "IBM", theTrade.getInstrumentAsString()); assertEquals("didnt' get right size", new BigDecimal("85"), theTrade.getPrice()); assertEquals("CEP didn't receive all events", 3, sManager.getDataFlowInfo(flow3).getFlowSteps()[1].getNumReceived()); assertEquals("CEP sent out extra events", 1, sManager.getDataFlowInfo(flow3).getFlowSteps()[1].getNumEmitted()); sManager.cancel(flow3); new ExpectedTestFailure(DataFlowNotFoundException.class, flow3.toString()) { protected void execute() throws Throwable { sManager.getDataFlowInfo(flow3); } }.run(); } /** Test multiple data flows with same queries but different sinks. Make sure that /** Run all the varous event types through */ @Test(timeout=120000) public void testAsk() throws Exception { flowTestHelperWrapper(CEPDataTypes.ASK, AskEvent.class.getName(), (Object[])new Event[] {ask1, ask2}); } public void testBid() throws Exception { flowTestHelperWrapper(CEPDataTypes.BID, BidEvent.class.getName(), (Object[])new Event[] {bid1, bid2}); } @Test(timeout=120000) public void testTrade() throws Exception { flowTestHelperWrapper(CEPDataTypes.TRADE, TradeEvent.class.getName(), (Object[])new Event[] {trade1, trade2}); } @Test(timeout=120000) public void testExecutionReport() throws Exception { flowTestHelperWrapper(CEPDataTypes.REPORT, ExecutionReport.class.getName(), er1, er2); } // validation inspects Text field @Test(timeout=120000) public void testOrderCancelReject() throws Exception { flowTestHelperWrapper(CEPDataTypes.CANCEL_REJECT, OrderCancelReject.class.getName(), ocr1, ocr2); } // on orders we look at destinations @Test(timeout=120000) public void testOrderSingle() throws Exception { flowTestHelperWrapper(CEPDataTypes.ORDER_SINGLE, OrderSingle.class.getName(), os1, os2); } // on orders we look at dest id @Test(timeout=120000) public void testOrderCancel() throws Exception { flowTestHelperWrapper(CEPDataTypes.ORDER_CANCEL, OrderCancel.class.getName(), oc1, oc2); } // on orders we look at dest id @Test(timeout=120000) public void testOrderReplace() throws Exception { flowTestHelperWrapper(CEPDataTypes.ORDER_REPLACE, OrderReplace.class.getName(), or1, or2); } // on orders we look at dest id @Test(timeout=120000) public void testFIXOrder() throws Exception { flowTestHelperWrapper(CEPDataTypes.FIX_ORDER, FIXOrder.class.getName(), fo1, fo2); } // checks on body @Test(timeout=120000) public void testNotification() throws Exception { flowTestHelperWrapper(CEPDataTypes.NOTIFICATION, Notification.class.getName(), not1, not2); } // checks on identifier @Test(timeout=120000) public void testSuggestion() throws Exception { flowTestHelperWrapper(CEPDataTypes.SUGGEST, Suggestion.class.getName(), sug1, sug2); } @Test(timeout=120000) public void testMap() throws Exception { flowTestHelperWrapper(CEPDataTypes.MAP, Map.class.getName(), map1, map2); } @Test(timeout=120000) public void testMarketData() throws Exception { flowTestHelperWrapper(CEPDataTypes.MARKET_DATA, MarketDataEvent.class.getName(), ask1, ask2, bid1, bid2, trade1, trade2); } @Test(timeout=120000) public void testLog() throws Exception { flowTestHelperWrapper(CEPDataTypes.LOG, LogEvent.class.getName(), log1, log2); } @Test(timeout=120000) public void testMarketstatEvent() throws Exception { flowTestHelperWrapper(CEPDataTypes.MARKET_STAT, MarketstatEvent.class.getName(), mStat1, mStat2); } protected void flowTestHelperWrapper(String expectedAlias, String expectedClass, Object... expectedEvents) throws Exception { flowTestHelper(expectedAlias, expectedEvents); flowTestHelper(expectedClass, expectedEvents); } /** Helper to run multiple data types through the flow. We will be matching on 'symbol', or something * similar in the particular event type if that's available */ protected void flowTestHelper(String type, Object[] expectedEvents) throws Exception { // verify module not found before data flow is started new ExpectedTestFailure(ModuleNotFoundException.class) { protected void execute() throws Throwable { sManager.getModuleInfo(getModuleURN()); } }.run(); DataFlowID flowID = sManager.createDataFlow(new DataRequest[] { // Copier -> CEP - send all the events in new DataRequest(CopierModuleFactory.INSTANCE_URN, allSentEvents), // System -> Sink: only get events that are specified in the incoming type new DataRequest(getModuleURN(), "select * from "+type) }); for (int i = 0; i < expectedEvents.length; i++) { Object event = sSink.getNextData(); if(expectedEvents[i] instanceof Map) { // for some reason, instead of returning the same Map esper re-creates it. so do this the hard way Object[] eventKeys = ((Map<?,?>) event).keySet().toArray(); Arrays.sort(eventKeys); Object[] expectedKeys = ((Map<?,?>) expectedEvents[i]).keySet().toArray(); Arrays.sort(expectedKeys); assertArrayEquals("keys not equal", eventKeys, expectedKeys); for (Object expectedKey : expectedKeys) { assertEquals("value for key not the same"+expectedKey, ((Map<?,?>)expectedEvents[i]).get(expectedKey), ((Map<?,?>)event).get(expectedKey)); } } else { assertSame("Wrong event received in["+i+"] " + event, expectedEvents[i], event); } } assertEquals("CEP didn't send out right # of events", expectedEvents.length, sManager.getDataFlowInfo(flowID).getFlowSteps()[1].getNumEmitted()); sManager.cancel(flowID); // verify module not found after data flow is started new ExpectedTestFailure(ModuleNotFoundException.class) { protected void execute() throws Throwable { sManager.getModuleInfo(getModuleURN()); } }.run(); } }