/**
*
*/
package net.frontlinesms.messaging.sms;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import org.mockito.internal.verification.NoMoreInteractions;
import org.mockito.internal.verification.Times;
import org.smslib.CIncomingMessage;
import net.frontlinesms.data.domain.FrontlineMessage;
import net.frontlinesms.data.domain.FrontlineMessage.Status;
import net.frontlinesms.events.EventBus;
import net.frontlinesms.junit.BaseTestCase;
import net.frontlinesms.messaging.sms.MessageType;
import net.frontlinesms.messaging.sms.SmsServiceManager;
import net.frontlinesms.messaging.sms.events.SmsServiceStatusNotification;
import net.frontlinesms.messaging.sms.internet.SmsInternetService;
import net.frontlinesms.messaging.sms.modem.SmsModem;
import net.frontlinesms.messaging.sms.modem.SmsModemStatus;
import static org.mockito.Mockito.*;
/**
* Test class for {@link SmsServiceManager}.
* @author aga
*/
public class SmsServiceManagerTest extends BaseTestCase {
/**
* Test that all messages sent will be given to the {@link SmsInternetService} in preference to
* any {@link SmsModem}s available.
*/
public void testMessageDispatchPriorities_text() {
SmsServiceManager manager = new SmsServiceManager();
SmsInternetService sisNoSend = createMockSmsInternetService(false, true);
manager.addSmsInternetService(sisNoSend);
SmsInternetService sisNoSendNoBinary = createMockSmsInternetService(false, false);
manager.addSmsInternetService(sisNoSendNoBinary);
SmsInternetService sisBinary = createMockSmsInternetService(true, true);
manager.addSmsInternetService(sisBinary);
SmsInternetService sisNoBinary = createMockSmsInternetService(true, false);
manager.addSmsInternetService(sisNoBinary);
SmsModem modem = createMockModem(true, true, true, true);
addModem(manager, modem, "TestModem1");
sendSms(manager, generateMessages(20, MessageType.GSM7BIT_TEXT));
manager.doRun();
// Check that all messages were sent with the TWO functioning internet services, and nothing else
verify(modem, never()).sendSMS(any(FrontlineMessage.class));
verify(sisNoSend, never()).sendSMS(any(FrontlineMessage.class));
verify(sisNoSendNoBinary, never()).sendSMS(any(FrontlineMessage.class));
verify(sisBinary, times(10)).sendSMS(any(FrontlineMessage.class));
verify(sisNoBinary, times(10)).sendSMS(any(FrontlineMessage.class));
}
/**
* Test that all messages sent will be given to the {@link SmsInternetService} in preference to
* any {@link SmsModem}s available.
*/
public void testMessageDispatchPriorities_binary() {
SmsServiceManager manager = new SmsServiceManager();
SmsInternetService sisNoSend = createMockSmsInternetService(false, true);
manager.addSmsInternetService(sisNoSend);
SmsInternetService sisNoSendNoBinary = createMockSmsInternetService(false, false);
manager.addSmsInternetService(sisNoSendNoBinary);
SmsInternetService sisBinary = createMockSmsInternetService(true, true);
manager.addSmsInternetService(sisBinary);
SmsInternetService sisNoBinary = createMockSmsInternetService(true, false);
manager.addSmsInternetService(sisNoBinary);
SmsModem modem = createMockModem(true, true, true, true);
addModem(manager, modem, "TestModem1");
sendSms(manager, generateMessages(20, MessageType.BINARY));
manager.doRun();
// Check that all messages were sent with the ONE internet services which is functioning and sends binary, and nothing else
verify(modem, never()).sendSMS(any(FrontlineMessage.class));
verify(sisNoSend, never()).sendSMS(any(FrontlineMessage.class));
verify(sisNoSendNoBinary, never()).sendSMS(any(FrontlineMessage.class));
verify(sisNoBinary, never()).sendSMS(any(FrontlineMessage.class));
verify(sisBinary, times(20)).sendSMS(any(FrontlineMessage.class));
}
/** Test that text messages are sent only with suitable modems. */
public void testModemSend_text() {
SmsServiceManager manager = new SmsServiceManager();
SmsModem disconnectedModem = createMockModem(false, false, true, true);
addModem(manager, disconnectedModem, "Disconnected.");
SmsModem gsmOnlyModem = createMockModem(true, true, false, false);
addModem(manager, gsmOnlyModem, "GsmOnly");
SmsModem ucs2Modem = createMockModem(true, true, false, true);
addModem(manager, ucs2Modem, "ucs2");
SmsModem binaryModem = createMockModem(true, true, true, false);
addModem(manager, binaryModem, "binary");
SmsModem everythingModem = createMockModem(true, true, true, true);
addModem(manager, everythingModem, "everything");
// Sending no messages
manager.doRun();
verify(disconnectedModem, never()).sendSMS(any(FrontlineMessage.class));
verify(gsmOnlyModem, never()).sendSMS(any(FrontlineMessage.class));
verify(ucs2Modem, never()).sendSMS(any(FrontlineMessage.class));
verify(binaryModem, never()).sendSMS(any(FrontlineMessage.class));
verify(everythingModem, never()).sendSMS(any(FrontlineMessage.class));
// Send some simple text messages, and make sure that they were send with the expected modems
Collection<FrontlineMessage> gsm7bitMessages = generateMessages(8, MessageType.GSM7BIT_TEXT);
sendSms(manager, gsm7bitMessages);
manager.doRun();
verify(disconnectedModem, never()).sendSMS(any(FrontlineMessage.class));
verify(gsmOnlyModem, times(2)).sendSMS(any(FrontlineMessage.class));
verify(ucs2Modem, times(2)).sendSMS(any(FrontlineMessage.class));
verify(binaryModem, times(2)).sendSMS(any(FrontlineMessage.class));
verify(everythingModem, times(2)).sendSMS(any(FrontlineMessage.class));
}
/** Test that binary messages are sent only with suitable modems. */
public void testModemSend_binary() {
SmsServiceManager manager = new SmsServiceManager();
SmsModem disconnectedModem = createMockModem(false, false, true, true);
addModem(manager, disconnectedModem, "Disconnected.");
SmsModem gsmOnlyModem = createMockModem(true, true, false, false);
addModem(manager, gsmOnlyModem, "GsmOnly");
SmsModem ucs2Modem = createMockModem(true, true, false, true);
addModem(manager, ucs2Modem, "ucs2");
SmsModem binaryModem = createMockModem(true, true, true, false);
addModem(manager, binaryModem, "binary");
SmsModem everythingModem = createMockModem(true, true, true, true);
addModem(manager, everythingModem, "everything");
// Send some binary messages
Collection<FrontlineMessage> binaryMessages = generateMessages(8, MessageType.BINARY);
sendSms(manager, binaryMessages);
manager.doRun();
verify(disconnectedModem, never()).sendSMS(any(FrontlineMessage.class));
verify(gsmOnlyModem, never()).sendSMS(any(FrontlineMessage.class));
verify(ucs2Modem, never()).sendSMS(any(FrontlineMessage.class));
verify(binaryModem, times(4)).sendSMS(any(FrontlineMessage.class));
verify(everythingModem, times(4)).sendSMS(any(FrontlineMessage.class));
}
/** Test that binary messages are sent only with suitable modems. */
public void testModemSend_ucs2() {
SmsServiceManager manager = new SmsServiceManager();
SmsModem disconnectedModem = createMockModem(false, false, true, true);
addModem(manager, disconnectedModem, "Disconnected.");
SmsModem gsmOnlyModem = createMockModem(true, true, false, false);
addModem(manager, gsmOnlyModem, "GsmOnly");
SmsModem ucs2Modem = createMockModem(true, true, false, true);
addModem(manager, ucs2Modem, "ucs2");
SmsModem binaryModem = createMockModem(true, true, true, false);
addModem(manager, binaryModem, "binary");
SmsModem everythingModem = createMockModem(true, true, true, true);
addModem(manager, everythingModem, "everything");
// Send some UCS2 messages
Collection<FrontlineMessage> ucs2Messages = generateMessages(8, MessageType.UCS2_TEXT);
sendSms(manager, ucs2Messages);
manager.doRun();
verify(disconnectedModem, never()).sendSMS(any(FrontlineMessage.class));
verify(gsmOnlyModem, never()).sendSMS(any(FrontlineMessage.class));
verify(ucs2Modem, times(4)).sendSMS(any(FrontlineMessage.class));
verify(binaryModem, never()).sendSMS(any(FrontlineMessage.class));
verify(everythingModem, times(4)).sendSMS(any(FrontlineMessage.class));
}
/** Test that messages are polled from all modems who have message receiving enabled. */
public void testModemMessageReceive() {
SmsServiceManager manager = new SmsServiceManager();
SmsModem[] receiveModems = new SmsModem[10];
for (int i = 0; i < receiveModems.length; i++) {
SmsModem modem = createMockModem(i%2==0, true, i%3==0, i%5==0);
receiveModems[i] = modem;
addModem(manager, modem, "Receive " + i);
}
SmsModem[] nonReceiveModems = new SmsModem[10];
for (int i = 0; i < nonReceiveModems.length; i++) {
SmsModem modem = createMockModem(i%2==0, false, i%3==0, i%5==0);
nonReceiveModems[i] = modem;
addModem(manager, modem, "NonReceive " + i);
}
// Now create some modems with messages
CIncomingMessage mockMessage = mock(CIncomingMessage.class);
SmsModem modemWith1Message = createMockModem(false, true, false, false);
when(modemWith1Message.nextIncomingMessage())
.thenReturn(mockMessage)
.thenReturn(null);
addModem(manager, modemWith1Message, "ModemWith1Message");
SmsModem modemWith3Messages = createMockModem(false, true, false, false);
when(modemWith3Messages.nextIncomingMessage())
.thenReturn(mockMessage)
.thenReturn(mockMessage)
.thenReturn(mockMessage)
.thenReturn(null);
addModem(manager, modemWith3Messages, "ModemWith3Messages");
manager.doRun();
for(SmsModem modem : receiveModems) {
verify(modem).nextIncomingMessage();
}
for(SmsModem modem : nonReceiveModems) {
verify(modem, never()).nextIncomingMessage();
}
verify(modemWith1Message, times(2)).nextIncomingMessage();
verify(modemWith3Messages, times(4)).nextIncomingMessage();
}
/** Tests that when there are no SMS devices, the messages are left in outbox. */
public void testNoSmsDevices() {
SmsServiceManager manager = new SmsServiceManager();
FrontlineMessage m = FrontlineMessage.createOutgoingMessage(System.currentTimeMillis(), "+123456", "+987654", "Hi");
manager.sendSMS(m);
manager.doRun();
assertEquals(Status.OUTBOX, m.getStatus());
}
public void testSmsDeviceEvent () {
SmsServiceManager manager = new SmsServiceManager();
EventBus mockEventBus = mock(EventBus.class);
manager.setEventBus(mockEventBus);
// Testing if the event is triggered with one failed-status device and a DORMANT device <SHOULD NOT>
SmsModem modem1 = mock(SmsModem.class);
addModem(manager, modem1, "will_fail");
SmsModem modem2 = mock(SmsModem.class);
addModem(manager, modem2, "will_be_owned");
when(modem1.getStatus()).thenReturn(SmsModemStatus.FAILED_TO_CONNECT);
when(modem2.getStatus()).thenReturn(SmsModemStatus.DORMANT);
manager.smsDeviceEvent(modem1, SmsModemStatus.FAILED_TO_CONNECT);
verify(mockEventBus, new NoMoreInteractions()).notifyObservers(any(SmsServiceStatusNotification.class));
// Testing if the event is triggered with one failed-status device and a CONNECTING device <SHOULD NOT>
when(modem2.getStatus()).thenReturn(SmsModemStatus.CONNECTING);
manager.smsDeviceEvent(modem1, SmsModemStatus.FAILED_TO_CONNECT);
verify(mockEventBus, new NoMoreInteractions()).notifyObservers(any(SmsServiceStatusNotification.class));
// Testing if the event is triggered with the connecting device failing <SHOULD>
when(modem2.getStatus()).thenReturn(SmsModemStatus.OWNED_BY_SOMEONE_ELSE);
manager.smsDeviceEvent(modem2, SmsModemStatus.OWNED_BY_SOMEONE_ELSE);
verify(mockEventBus, new Times(1)).notifyObservers(any(SmsServiceStatusNotification.class));
// Testing if the event is triggered with one failed-status device and a CONNECTED device <SHOULD NOT>
SmsModem modem3 = createMockModem(true, true, true, true);
addModem(manager, modem3, "connected");
manager.smsDeviceEvent(modem1, SmsModemStatus.FAILED_TO_CONNECT);
verify(mockEventBus, new NoMoreInteractions()).notifyObservers(any(SmsServiceStatusNotification.class));
}
//> PRIVATE HELPER METHODS
/** @return a mock {@link SmsInternetService} with certain important methods stubbed */
private SmsInternetService createMockSmsInternetService(boolean useForSending, boolean supportsBinary) {
SmsInternetService mock = mock(SmsInternetService.class);
when(mock.isConnected()).thenReturn(true);
when(mock.isUseForSending()).thenReturn(useForSending);
when(mock.isBinarySendingSupported()).thenReturn(supportsBinary);
return mock;
}
/** @return a mock {@link SmsModem} with certain important methods stubbed */
private SmsModem createMockModem(boolean useForSending, boolean useForReceiving, boolean supportsBinary, boolean supportsUcs2) {
SmsModem mock = mock(SmsModem.class);
when(mock.isConnected()).thenReturn(true);
when(mock.isUseForSending()).thenReturn(useForSending);
when(mock.isUseForReceiving()).thenReturn(useForReceiving);
when(mock.isBinarySendingSupported()).thenReturn(supportsBinary);
when(mock.isUcs2SendingSupported()).thenReturn(supportsUcs2);
when(mock.getStatus()).thenReturn(SmsModemStatus.DORMANT);
return mock;
}
/** Adds a {@link SmsModem} to {@link SmsServiceManager#phoneHandlers} by reflection. */
@SuppressWarnings("unchecked")
private void addModem(SmsServiceManager manager, SmsModem modem, String modemId) {
try {
Field handlerField = SmsServiceManager.class.getDeclaredField("phoneHandlers");
handlerField.setAccessible(true);
Map<String, SmsModem> handlers = (Map<String, SmsModem>) handlerField.get(manager);
handlers.put(modemId, modem);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
/** @return some generated SMS messages */
private Collection<FrontlineMessage> generateMessages(int count, MessageType type) {
HashSet<FrontlineMessage> messages = new HashSet<FrontlineMessage>();
while(--count >= 0) {
FrontlineMessage m;
long now = System.currentTimeMillis();
String recipientMsisdn = "Recipient " + count;
String senderMsisdn = "Sender " + count;
if(type == MessageType.BINARY) {
byte[] data = new byte[count];
for (int i = 0; i < data.length; i++) {
data[i] = (byte) i;
}
m = FrontlineMessage.createBinaryOutgoingMessage(now, senderMsisdn, recipientMsisdn, 0, data);
} else {
String content = "Content " + count;
if(type == MessageType.UCS2_TEXT) {
// Add some random arabic letters to the text content
content += "\u0634\u0626\u0647\u0629";
}
m = FrontlineMessage.createOutgoingMessage(now, senderMsisdn, recipientMsisdn, content);
}
messages.add(m);
}
return messages;
}
/** Send multiple SMS to the manager */
private void sendSms(SmsServiceManager manager, Collection<FrontlineMessage> messages) {
for(FrontlineMessage m : messages) manager.sendSMS(m);
}
}