/**
* Copyright (c) 2011-2012 Optimax Software Ltd.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Optimax Software, ElasticInbox, nor the names
* of its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.elasticinbox.itests;
import static org.ops4j.pax.exam.CoreOptions.*;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import org.apache.commons.net.pop3.POP3Client;
import org.apache.commons.net.pop3.POP3MessageInfo;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.ops4j.pax.exam.Option;
import org.ops4j.pax.exam.junit.Configuration;
import org.ops4j.pax.exam.junit.ExamReactorStrategy;
import org.ops4j.pax.exam.junit.JUnit4TestRunner;
import org.ops4j.pax.exam.spi.reactors.EagerSingleStagedReactorFactory;
import com.elasticinbox.core.model.ReservedLabels;
import com.elasticinbox.core.utils.Base64UUIDUtils;
import com.google.common.collect.ObjectArrays;
/**
* Integration test for POP3.
*
* Some parts of these code are taken from Apache JAMES Protocols project.
*
* @author Rustam Aliyev
*/
@RunWith(JUnit4TestRunner.class)
@ExamReactorStrategy(EagerSingleStagedReactorFactory.class)
public class Pop3IT extends AbstractIntegrationTest
{
private final static int POP3_PORT = 2110;
private final static String POP3_HOST = "localhost";
/**
* Append POP3 Specific config options
*
* @return
*/
@Configuration()
public Option[] config()
{
return ObjectArrays.concat(super.config(), options(
// POP3 Test Bundles
mavenBundle().groupId("commons-net").artifactId("commons-net").versionAsInProject(),
mavenBundle().groupId("org.apache.james.protocols").artifactId("protocols-netty").versionAsInProject(),
mavenBundle().groupId("org.apache.james.protocols").artifactId("protocols-api").versionAsInProject(),
mavenBundle().groupId("org.apache.james.protocols").artifactId("protocols-pop3").versionAsInProject(),
mavenBundle().groupId("io.netty").artifactId("netty").versionAsInProject(),
// ElasticInbox Bundles
scanDir("../modules/pop3/target/")
), Option.class);
}
@Test
public void testListUidl() throws IOException
{
initAccount();
// load messages with POP3 label
long mailSizeRegular = getResourceSize(EMAIL_REGULAR);
long mailSizeAttach = getResourceSize(EMAIL_LARGE_ATT);
Map<String, UUID> messages = new HashMap<String, UUID>(2);
Integer labelId = ReservedLabels.POP3.getId();
messages.put("headers", RestV2IT.addMessage(EMAIL_REGULAR, labelId));
messages.put("attach", RestV2IT.addMessage(EMAIL_LARGE_ATT, labelId));
// initialize POP3 client
POP3Client client = new POP3Client();
client.connect(POP3_HOST, POP3_PORT);
boolean loginSuccess = client.login(TEST_ACCOUNT, TEST_PASSWORD);
assertThat(loginSuccess, is(true));
// LIST all messages
POP3MessageInfo[] info = client.listMessages();
assertThat(info.length, equalTo(2));
assertThat((int) mailSizeAttach, equalTo(info[0].size));
assertThat((int) mailSizeRegular, equalTo(info[1].size));
assertThat(info[0].number, equalTo(1));
assertThat(info[1].number, equalTo(2));
// LIST one message
POP3MessageInfo msgInfo = client.listMessage(1);
assertThat((int) mailSizeAttach, equalTo(msgInfo.size));
assertThat(msgInfo.number, equalTo(1));
// LIST message that does not exist
msgInfo = client.listMessage(10);
assertThat(msgInfo, nullValue());
// UIDL all messages
info = client.listUniqueIdentifiers();
assertThat(info.length, equalTo(2));
assertThat(info[0].identifier,
equalTo(Base64UUIDUtils.encode(messages.get("attach"))));
assertThat(info[1].identifier,
equalTo(Base64UUIDUtils.encode(messages.get("headers"))));
assertThat(info[0].number, equalTo(1));
assertThat(info[1].number, equalTo(2));
// UIDL one message
msgInfo = client.listUniqueIdentifier(1);
assertThat(msgInfo.identifier,
equalTo(Base64UUIDUtils.encode(messages.get("attach"))));
assertThat(msgInfo.number, equalTo(1));
// UIDL message that does not exist
msgInfo = client.listUniqueIdentifier(10);
assertThat(msgInfo, nullValue());
boolean logoutSuccess = client.logout();
assertThat(logoutSuccess, is(true));
}
@Test
public void testDele() throws IOException
{
initAccount();
Map<String, UUID> messages = new HashMap<String, UUID>(2);
Integer labelId = ReservedLabels.POP3.getId();
messages.put("headers", RestV2IT.addMessage(EMAIL_REGULAR, labelId));
messages.put("attach", RestV2IT.addMessage(EMAIL_LARGE_ATT, labelId));
// initialize POP3 client
POP3Client client = new POP3Client();
client.connect(POP3_HOST, POP3_PORT);
// Login
boolean loginSuccess = client.login(TEST_ACCOUNT, TEST_PASSWORD);
assertThat(loginSuccess, is(true));
// LIST all messages
POP3MessageInfo[] info = client.listMessages();
assertThat(info.length, equalTo(2));
// DELE message 1
boolean deleteResult = client.deleteMessage(1);
assertThat(deleteResult, is(true));
// LIST remaining
info = client.listMessages();
assertThat(info.length, equalTo(1));
// DELE message 1 again
deleteResult = client.deleteMessage(1);
assertThat(deleteResult, is(false));
// LIST remaining
info = client.listMessages();
assertThat(info.length, equalTo(1));
// DELE message 2
deleteResult = client.deleteMessage(2);
assertThat(deleteResult, is(true));
info = client.listMessages();
assertThat(info.length, equalTo(0));
// QUIT so the messages get expunged
boolean logoutSuccess = client.logout();
assertThat(logoutSuccess, is(true));
// reconnect
client.connect(POP3_HOST, POP3_PORT);
// Login
loginSuccess = client.login(TEST_ACCOUNT, TEST_PASSWORD);
assertThat(loginSuccess, is(true));
info = client.listMessages();
assertThat(info.length, equalTo(0));
logoutSuccess = client.logout();
assertThat(logoutSuccess, is(true));
}
@Test
public void testRset() throws IOException
{
initAccount();
Integer labelId = ReservedLabels.POP3.getId();
RestV2IT.addMessage(EMAIL_REGULAR, labelId);
// initialize POP3 client
POP3Client client = new POP3Client();
client.connect(POP3_HOST, POP3_PORT);
// Login
boolean loginSuccess = client.login(TEST_ACCOUNT, TEST_PASSWORD);
assertThat(loginSuccess, is(true));
// LIST all messages
POP3MessageInfo[] info = client.listMessages();
assertThat(info.length, equalTo(1));
// DELE message 1
boolean deleteResult = client.deleteMessage(1);
assertThat(deleteResult, is(true));
// LIST remaining
info = client.listMessages();
assertThat(info.length, equalTo(0));
// RSET. After this the deleted mark should be removed again
boolean resetRestult = client.reset();
assertThat(resetRestult, is(true));
// LIST all messages
info = client.listMessages();
assertThat(info.length, equalTo(1));
// Logout
boolean logoutSuccess = client.logout();
assertThat(logoutSuccess, is(true));
}
@Test
public void testStat() throws IOException
{
initAccount();
// load messages with POP3 label
long mailSizeRegular = getResourceSize(EMAIL_REGULAR);
long mailSizeAttach = getResourceSize(EMAIL_LARGE_ATT);
Integer labelId = ReservedLabels.POP3.getId();
RestV2IT.addMessage(EMAIL_REGULAR, labelId);
RestV2IT.addMessage(EMAIL_LARGE_ATT, labelId);
// initialize POP3 client
POP3Client client = new POP3Client();
client.connect(POP3_HOST, POP3_PORT);
boolean loginSuccess = client.login(TEST_ACCOUNT, TEST_PASSWORD);
assertThat(loginSuccess, is(true));
POP3MessageInfo info = client.status();
assertThat(info.size, equalTo((int) (mailSizeRegular + mailSizeAttach)));
assertThat(info.number, equalTo(2));
// Logout
boolean logoutSuccess = client.logout();
assertThat(logoutSuccess, is(true));
}
@Test
public void testRetr() throws IOException
{
initAccount();
Integer labelId = ReservedLabels.POP3.getId();
RestV2IT.addMessage(EMAIL_SIMPLE, labelId);
// initialize POP3 client
POP3Client client = new POP3Client();
client.connect(POP3_HOST, POP3_PORT);
// Login
boolean loginSuccess = client.login(TEST_ACCOUNT, TEST_PASSWORD);
assertThat(loginSuccess, is(true));
// RETR message
Reader retrMessage = client.retrieveMessage(1);
assertThat(retrMessage, notNullValue());
checkMessage(EMAIL_SIMPLE, retrMessage);
retrMessage.close();
// RETR non-existing message
retrMessage = client.retrieveMessage(10);
assertThat(retrMessage, nullValue());
// Delete and RETR the message again
boolean deleteResult = client.deleteMessage(1);
assertThat(deleteResult, is(true));
retrMessage = client.retrieveMessage(1);
assertThat(retrMessage, nullValue());
}
@Test
public void testTop() throws IOException
{
initAccount();
Integer labelId = ReservedLabels.POP3.getId();
RestV2IT.addMessage(EMAIL_SIMPLE, labelId);
// initialize POP3 client
POP3Client client = new POP3Client();
client.connect(POP3_HOST, POP3_PORT);
// Login
boolean loginSuccess = client.login(TEST_ACCOUNT, TEST_PASSWORD);
assertThat(loginSuccess, is(true));
// TOP message with all lines
Reader topMessage = client.retrieveMessageTop(1, 1000);
assertThat(topMessage, notNullValue());
checkMessage(EMAIL_SIMPLE, topMessage);
topMessage.close();
// TOP message with 5 lines
topMessage = client.retrieveMessageTop(1, 5);
assertThat(topMessage, notNullValue());
checkMessage(EMAIL_SIMPLE, topMessage, 5);
topMessage.close();
// TOP non-existing message
topMessage = client.retrieveMessageTop(10, 10);
assertThat(topMessage, nullValue());
// Delete and TOP the message again
boolean deleteResult = client.deleteMessage(1);
assertThat(deleteResult, is(true));
topMessage = client.retrieveMessageTop(1, 1000);
assertThat(topMessage, nullValue());
}
private void checkMessage(String message, Reader reader) throws IOException {
checkMessage(message, reader, null);
}
private void checkMessage(String message, Reader reader, Integer limit) throws IOException
{
Reader orig;
if (limit != null) {
orig = new InputStreamReader(new CountingBodyInputStream(
this.getClass().getResourceAsStream(message), limit), "US-ASCII");
} else {
orig = new InputStreamReader(this.getClass().getResourceAsStream(message), "US-ASCII");
}
int i = -1;
int j = -1;
while (((i = reader.read()) != -1) && ((j = orig.read()) != -1)) {
assertThat(j, equalTo(i));
}
// read final byte and assert
j = orig.read();
assertThat(j, equalTo(i));
orig.close();
}
/**
* This {@link InputStream} implementation can be used to return all message
* headers and limit the body lines which will be read from the wrapped
* {@link InputStream}.
*/
private final class CountingBodyInputStream extends InputStream
{
private int count = 0;
private int limit = -1;
private int lastChar;
private InputStream in;
private boolean isBody = false; // starting from header
private boolean isEmptyLine = false;
/**
*
* @param in
* InputStream to read from
* @param limit
* the lines to read. -1 is used for no limits
*/
public CountingBodyInputStream(InputStream in, int limit) {
this.in = in;
this.limit = limit;
}
@Override
public int read() throws IOException
{
if (limit != -1) {
if (count <= limit) {
int a = in.read();
// check for empty line
if (!isBody && isEmptyLine && lastChar == '\r' && a == '\n') {
// reached body
isBody = true;
}
if (lastChar == '\r' && a == '\n') {
// reset empty line flag
isEmptyLine = true;
if (isBody) {
count++;
}
} else if (lastChar == '\n' && a != '\r') {
isEmptyLine = false;
}
lastChar = a;
return a;
} else {
return -1;
}
} else {
return in.read();
}
}
@Override
public long skip(long n) throws IOException {
return in.skip(n);
}
@Override
public int available() throws IOException {
return in.available();
}
@Override
public void close() throws IOException {
in.close();
}
@Override
public void mark(int readlimit) {
// not supported
}
@Override
public void reset() throws IOException {
// do nothing as mark is not supported
}
@Override
public boolean markSupported() {
return false;
}
}
}