/**
*
*/
package net.frontlinesms.messaging;
import static org.mockito.Mockito.*;
import java.util.Arrays;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import org.smslib.CIncomingMessage;
import net.frontlinesms.FrontlineSMS;
import net.frontlinesms.data.domain.Contact;
import net.frontlinesms.data.domain.Group;
import net.frontlinesms.data.domain.Keyword;
import net.frontlinesms.data.domain.KeywordAction;
import net.frontlinesms.data.domain.FrontlineMessage;
import net.frontlinesms.data.repository.ContactDao;
import net.frontlinesms.data.repository.KeywordActionDao;
import net.frontlinesms.data.repository.KeywordDao;
import net.frontlinesms.data.repository.MessageDao;
import net.frontlinesms.junit.BaseTestCase;
import net.frontlinesms.listener.UIListener;
import net.frontlinesms.messaging.IncomingMessageProcessor;
import net.frontlinesms.messaging.sms.SmsService;
/**
* Tests for the {@link IncomingMessageProcessor} class.
* @author Alex Anderson <alex@frontlinesms.com>
*/
public class IncomingMessageProcessorTest extends BaseTestCase {
/** A phone number used for initialising {@link CIncomingMessage}s */
private static final String TEST_ORIGINATOR = "+123456789";
private FrontlineSMS frontline;
private ContactDao contactDao;
private MessageDao messageDao;
private KeywordDao keywordDao;
private KeywordActionDao keywordActionDao;
private IncomingMessageProcessor imp;
private BlockingIncomingMessageEventListener bimel;
//> TEST META METHODS
@Override
protected void setUp() throws Exception {
super.setUp();
// Set up the Frontline controller
frontline = mock(FrontlineSMS.class);
contactDao = mock(ContactDao.class);
when(frontline.getContactDao()).thenReturn(contactDao);
messageDao = mock(MessageDao.class);
when(frontline.getMessageDao()).thenReturn(messageDao);
keywordDao = mock(KeywordDao.class);
when(frontline.getKeywordDao()).thenReturn(keywordDao);
keywordActionDao = mock(KeywordActionDao.class);
when(frontline.getKeywordActionDao()).thenReturn(keywordActionDao);
imp = new IncomingMessageProcessor(frontline);
bimel = new BlockingIncomingMessageEventListener();
imp.setUiListener(bimel);
imp.start();
}
@Override
protected void tearDown() throws Exception {
super.tearDown();
imp.die();
imp = null;
frontline = null;
}
//> TESTS
public void testActionProcessing() {
FrontlineMessage mockMessage = mock(FrontlineMessage.class);
Keyword mockKeyword = mock(Keyword.class);
when(keywordDao.getFromMessageText(anyString())).thenReturn(mockKeyword);
UIListener uiListener = mock(UIListener.class);
imp.setUiListener(uiListener);
KeywordAction goodAction1 = mockKeywordAction(true);
KeywordAction goodAction2 = mockKeywordAction(true);
KeywordAction deadAction = mockKeywordAction(false);
KeywordAction badAction = mockKeywordAction(true);
when(badAction.getType()).thenReturn(KeywordAction.Type.REPLY);
when(keywordActionDao.getActions(mockKeyword)).thenReturn(Arrays.asList(goodAction1, deadAction, badAction, goodAction2));
imp.handleMessage(mockMessage);
verify(keywordActionDao).incrementCounter(goodAction1);
verify(uiListener).keywordActionExecuted(goodAction1);
verify(keywordActionDao).incrementCounter(goodAction2);
verify(uiListener).keywordActionExecuted(goodAction2);
verify(keywordActionDao, never()).incrementCounter(deadAction);
verify(uiListener, never()).keywordActionExecuted(deadAction);
verify(keywordActionDao, never()).incrementCounter(badAction);
verify(uiListener, never()).keywordActionExecuted(badAction);
}
private KeywordAction mockKeywordAction(boolean isAlive) {
KeywordAction action = mock(KeywordAction.class);
when(action.isAlive(anyLong())).thenReturn(isAlive);
when(action.getType()).thenReturn(KeywordAction.Type.NO_ACTION);
return action;
}
/**
* Verify that new message objects are created and saved for messages which have no
* keywords linked to them.
*/
public void testSimpleTextMessages() {
testTextMessage("");
testTextMessage("A short message.");
testTextMessage("\ni have a newline\nin me and one at the start.");
}
/**
* Test the basic processing of a text message, including whether it is persisted, that keyword matching is
* attempted, and that the created {@link FrontlineMessage} object contained the expected text.
* @param messageText
* @return
*/
private FrontlineMessage testTextMessage(String messageText) {
// Create and queue the message
CIncomingMessage message = new CIncomingMessage(TEST_ORIGINATOR, messageText);
FrontlineMessage mess = testMessageReceive(message);
verify(keywordDao).getFromMessageText(messageText);
assertEquals("Created " + FrontlineMessage.class + " message had unexpected text content.", messageText, mess.getTextContent());
assertNull(mess.getBinaryContent());
return mess;
}
public void testSimpleBinaryMessages() {
// Test empty message
testBinaryMessage(new byte[0]);
// Test a message with some data
byte[] bytes1 = new byte[128];
for(int i=0; i<bytes1.length; ++i) bytes1[i] = (byte) i;
testBinaryMessage(bytes1);
// Test a message with lots of data
byte[] bytes2 = new byte[512];
for(int i=0; i<bytes2.length; ++i) bytes2[i] = (byte) i;
testBinaryMessage(bytes2);
}
/**
* Test the basic processing of an incoming binary message.
* @param data
* @return
*/
private FrontlineMessage testBinaryMessage(byte[] data) {
CIncomingMessage message = new CIncomingMessage(TEST_ORIGINATOR, data);
FrontlineMessage mess = testMessageReceive(message);
assertEquals("Message contains incorrect data.", data, message.getBinary());
return mess;
}
/**
* Receive a {@link CIncomingMessage}, and return the corresponding {@link FrontlineMessage}
* @param message
* @return
*/
private FrontlineMessage testMessageReceive(CIncomingMessage message) {
SmsService receiver = mock(SmsService.class);
imp.queue(receiver, message);
// Wait for the message to be processed, and then check that the expected steps were taken
FrontlineMessage mess = bimel.getIncomingMessage();
verify(messageDao).saveMessage(mess);
return mess;
}
}
/**
* This class is used as the {@link UIListener} for the {@link IncomingMessageProcessor} that we are testing.
* Using the method {@link BlockingIncomingMessageEventListener#getIncomingMessage()} we can wait until the
* {@link IncomingMessageProcessor} has finished processing a message before checking the state of the test
* objects.
* @author aga
*/
class BlockingIncomingMessageEventListener implements UIListener {
private final BlockingQueue<FrontlineMessage> incomingMessages = new LinkedBlockingQueue<FrontlineMessage>();
public void contactAddedToGroup(Contact contact, Group group) { /* ignore */ }
public void contactRemovedFromGroup(Contact contact, Group group) { /* ignore */ }
public void keywordActionExecuted(KeywordAction action) { /* ignore */ }
public void outgoingMessageEvent(FrontlineMessage message) { /* ignore */ }
public void incomingMessageEvent(FrontlineMessage message) {
incomingMessages.add(message);
}
/**
* Gets the next message from {@link #incomingMessages}, blocking until there is a message
* available.
* @return The head of {@link #incomingMessages}.
*/
public FrontlineMessage getIncomingMessage() {
try {
return incomingMessages.take();
} catch (InterruptedException ex) {
throw new RuntimeException("Unexpected interruption while queuing for message.", ex);
}
}
}