Merge pull request #6805 from thundernest/convert_to_kotlin
Convert `ImapResponseParserTest` to Kotlin
This commit is contained in:
commit
87ff2338ba
2 changed files with 585 additions and 563 deletions
|
@ -1,563 +0,0 @@
|
||||||
package com.fsck.k9.mail.store.imap;
|
|
||||||
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import com.fsck.k9.mail.filter.FixedLengthInputStream;
|
|
||||||
import com.fsck.k9.mail.filter.PeekableInputStream;
|
|
||||||
import kotlin.text.Charsets;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import static java.util.Arrays.asList;
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertNotNull;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
import static org.junit.Assert.fail;
|
|
||||||
|
|
||||||
|
|
||||||
public class ImapResponseParserTest {
|
|
||||||
private PeekableInputStream peekableInputStream;
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSimpleOkResponse() throws IOException {
|
|
||||||
ImapResponseParser parser = createParser("* OK\r\n");
|
|
||||||
|
|
||||||
ImapResponse response = parser.readResponse();
|
|
||||||
|
|
||||||
assertNotNull(response);
|
|
||||||
assertEquals(1, response.size());
|
|
||||||
assertEquals("OK", response.get(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testOkResponseWithText() throws IOException {
|
|
||||||
ImapResponseParser parser = createParser("* OK Some text here\r\n");
|
|
||||||
|
|
||||||
ImapResponse response = parser.readResponse();
|
|
||||||
|
|
||||||
assertNotNull(response);
|
|
||||||
assertEquals(2, response.size());
|
|
||||||
assertEquals("OK", response.get(0));
|
|
||||||
assertEquals("Some text here", response.get(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testOkResponseWithRespTextCode() throws IOException {
|
|
||||||
ImapResponseParser parser = createParser("* OK [UIDVALIDITY 3857529045]\r\n");
|
|
||||||
|
|
||||||
ImapResponse response = parser.readResponse();
|
|
||||||
|
|
||||||
assertNotNull(response);
|
|
||||||
assertEquals(2, response.size());
|
|
||||||
assertEquals("OK", response.get(0));
|
|
||||||
assertTrue(response.get(1) instanceof ImapList);
|
|
||||||
|
|
||||||
ImapList respTextCode = (ImapList) response.get(1);
|
|
||||||
assertEquals(2, respTextCode.size());
|
|
||||||
assertEquals("UIDVALIDITY", respTextCode.get(0));
|
|
||||||
assertEquals("3857529045", respTextCode.get(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testOkResponseWithRespTextCodeAndText() throws IOException {
|
|
||||||
ImapResponseParser parser = createParser("* OK [token1 token2] {x} test [...]\r\n");
|
|
||||||
|
|
||||||
ImapResponse response = parser.readResponse();
|
|
||||||
|
|
||||||
assertNotNull(response);
|
|
||||||
assertEquals(3, response.size());
|
|
||||||
assertEquals("OK", response.get(0));
|
|
||||||
assertTrue(response.get(1) instanceof ImapList);
|
|
||||||
assertEquals("{x} test [...]", response.get(2));
|
|
||||||
|
|
||||||
ImapList respTextCode = (ImapList) response.get(1);
|
|
||||||
assertEquals(2, respTextCode.size());
|
|
||||||
assertEquals("token1", respTextCode.get(0));
|
|
||||||
assertEquals("token2", respTextCode.get(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testReadStatusResponseWithOKResponse() throws Exception {
|
|
||||||
ImapResponseParser parser = createParser("* COMMAND BAR\tBAZ\r\n" +
|
|
||||||
"TAG OK COMMAND completed\r\n");
|
|
||||||
|
|
||||||
List<ImapResponse> responses = parser.readStatusResponse("TAG", null, null, null);
|
|
||||||
|
|
||||||
assertEquals(2, responses.size());
|
|
||||||
assertEquals(asList("COMMAND", "BAR", "BAZ"), responses.get(0));
|
|
||||||
assertEquals(asList("OK", "COMMAND completed"), responses.get(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testReadStatusResponseUntaggedHandlerGetsUntaggedOnly() throws Exception {
|
|
||||||
ImapResponseParser parser = createParser(
|
|
||||||
"* UNTAGGED\r\n" +
|
|
||||||
"A2 OK COMMAND completed\r\n");
|
|
||||||
TestUntaggedHandler untaggedHandler = new TestUntaggedHandler();
|
|
||||||
|
|
||||||
parser.readStatusResponse("A2", null, null, untaggedHandler);
|
|
||||||
|
|
||||||
assertEquals(1, untaggedHandler.responses.size());
|
|
||||||
assertEquals(asList("UNTAGGED"), untaggedHandler.responses.get(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testReadStatusResponseSkippingWrongTag() throws Exception {
|
|
||||||
ImapResponseParser parser = createParser("* UNTAGGED\r\n" +
|
|
||||||
"* 0 EXPUNGE\r\n" +
|
|
||||||
"* 42 EXISTS\r\n" +
|
|
||||||
"A1 COMMAND BAR BAZ\r\n" +
|
|
||||||
"A2 OK COMMAND completed\r\n");
|
|
||||||
TestUntaggedHandler untaggedHandler = new TestUntaggedHandler();
|
|
||||||
|
|
||||||
List<ImapResponse> responses = parser.readStatusResponse("A2", null, null, untaggedHandler);
|
|
||||||
|
|
||||||
assertEquals(3, responses.size());
|
|
||||||
assertEquals(asList("0", "EXPUNGE"), responses.get(0));
|
|
||||||
assertEquals(asList("42", "EXISTS"), responses.get(1));
|
|
||||||
assertEquals(asList("OK", "COMMAND completed"), responses.get(2));
|
|
||||||
assertEquals(asList("UNTAGGED"), untaggedHandler.responses.get(0));
|
|
||||||
assertEquals(responses.get(0), untaggedHandler.responses.get(1));
|
|
||||||
assertEquals(responses.get(1), untaggedHandler.responses.get(2));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testReadStatusResponseUntaggedHandlerStillCalledOnNegativeReply() throws Exception {
|
|
||||||
ImapResponseParser parser = createParser(
|
|
||||||
"+ text\r\n" +
|
|
||||||
"A2 NO Bad response\r\n");
|
|
||||||
TestUntaggedHandler untaggedHandler = new TestUntaggedHandler();
|
|
||||||
|
|
||||||
try {
|
|
||||||
List<ImapResponse> responses = parser.readStatusResponse("A2", null, null, untaggedHandler);
|
|
||||||
} catch (NegativeImapResponseException e) {
|
|
||||||
}
|
|
||||||
|
|
||||||
assertEquals(1, untaggedHandler.responses.size());
|
|
||||||
assertEquals(asList("text"), untaggedHandler.responses.get(0));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = NegativeImapResponseException.class)
|
|
||||||
public void testReadStatusResponseWithErrorResponse() throws Exception {
|
|
||||||
ImapResponseParser parser = createParser("* COMMAND BAR BAZ\r\nTAG ERROR COMMAND errored\r\n");
|
|
||||||
|
|
||||||
parser.readStatusResponse("TAG", null, null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testRespTextCodeWithList() throws Exception {
|
|
||||||
ImapResponseParser parser = createParser("* OK [PERMANENTFLAGS (\\Answered \\Flagged \\Deleted \\Seen " +
|
|
||||||
"\\Draft NonJunk $MDNSent \\*)] Flags permitted.\r\n");
|
|
||||||
|
|
||||||
ImapResponse response = parser.readResponse();
|
|
||||||
|
|
||||||
assertEquals(3, response.size());
|
|
||||||
assertTrue(response.get(1) instanceof ImapList);
|
|
||||||
assertEquals(2, response.getList(1).size());
|
|
||||||
assertEquals("PERMANENTFLAGS", response.getList(1).getString(0));
|
|
||||||
assertTrue(response.getList(1).get(1) instanceof ImapList);
|
|
||||||
assertEquals("\\Answered", response.getList(1).getList(1).getString(0));
|
|
||||||
assertEquals("\\Flagged", response.getList(1).getList(1).getString(1));
|
|
||||||
assertEquals("\\Deleted", response.getList(1).getList(1).getString(2));
|
|
||||||
assertEquals("\\Seen", response.getList(1).getList(1).getString(3));
|
|
||||||
assertEquals("\\Draft", response.getList(1).getList(1).getString(4));
|
|
||||||
assertEquals("NonJunk", response.getList(1).getList(1).getString(5));
|
|
||||||
assertEquals("$MDNSent", response.getList(1).getList(1).getString(6));
|
|
||||||
assertEquals("\\*", response.getList(1).getList(1).getString(7));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testExistsResponse() throws Exception {
|
|
||||||
ImapResponseParser parser = createParser("* 23 EXISTS\r\n");
|
|
||||||
|
|
||||||
ImapResponse response = parser.readResponse();
|
|
||||||
|
|
||||||
assertEquals(2, response.size());
|
|
||||||
assertEquals(23, response.getNumber(0));
|
|
||||||
assertEquals("EXISTS", response.getString(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = IOException.class)
|
|
||||||
public void testReadStringUntilEndOfStream() throws IOException {
|
|
||||||
ImapResponseParser parser = createParser("* OK Some text ");
|
|
||||||
|
|
||||||
parser.readResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testCommandContinuation() throws Exception {
|
|
||||||
ImapResponseParser parser = createParser("+ Ready for additional command text\r\n");
|
|
||||||
|
|
||||||
ImapResponse response = parser.readResponse();
|
|
||||||
|
|
||||||
assertEquals(1, response.size());
|
|
||||||
assertEquals("Ready for additional command text", response.getString(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testParseLiteral() throws Exception {
|
|
||||||
ImapResponseParser parser = createParser("* {4}\r\ntest\r\n");
|
|
||||||
|
|
||||||
ImapResponse response = parser.readResponse();
|
|
||||||
|
|
||||||
assertEquals(1, response.size());
|
|
||||||
assertEquals("test", response.getString(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testParseLiteralWithEmptyString() throws Exception {
|
|
||||||
ImapResponseParser parser = createParser("* {0}\r\n\r\n");
|
|
||||||
|
|
||||||
ImapResponse response = parser.readResponse();
|
|
||||||
|
|
||||||
assertEquals(1, response.size());
|
|
||||||
assertEquals("", response.getString(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = IOException.class)
|
|
||||||
public void testParseLiteralToEndOfStream() throws Exception {
|
|
||||||
ImapResponseParser parser = createParser("* {4}\r\nabc");
|
|
||||||
|
|
||||||
parser.readResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testParseLiteralWithConsumingCallbackReturningNull() throws Exception {
|
|
||||||
ImapResponseParser parser = createParser("* {4}\r\ntest\r\n");
|
|
||||||
TestImapResponseCallback callback = TestImapResponseCallback.readBytesAndReturn(4, "cheeseburger");
|
|
||||||
|
|
||||||
ImapResponse response = parser.readResponse(callback);
|
|
||||||
|
|
||||||
assertEquals(1, response.size());
|
|
||||||
assertEquals("cheeseburger", response.getString(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testParseLiteralWithNonConsumingCallbackReturningNull() throws Exception {
|
|
||||||
ImapResponseParser parser = createParser("* {4}\r\ntest\r\n");
|
|
||||||
TestImapResponseCallback callback = TestImapResponseCallback.readBytesAndReturn(0, null);
|
|
||||||
|
|
||||||
ImapResponse response = parser.readResponse(callback);
|
|
||||||
|
|
||||||
assertEquals(1, response.size());
|
|
||||||
assertEquals("test", response.getString(0));
|
|
||||||
assertTrue(callback.foundLiteralCalled);
|
|
||||||
assertAllInputConsumed();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void readResponse_withPartlyConsumingCallbackReturningNull_shouldThrow() throws Exception {
|
|
||||||
ImapResponseParser parser = createParser("* {4}\r\ntest\r\n");
|
|
||||||
TestImapResponseCallback callback = TestImapResponseCallback.readBytesAndReturn(2, null);
|
|
||||||
|
|
||||||
try {
|
|
||||||
parser.readResponse(callback);
|
|
||||||
fail();
|
|
||||||
} catch (AssertionError e) {
|
|
||||||
assertEquals("Callback consumed some data but returned no result", e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void readResponse_withPartlyConsumingCallbackThatThrows_shouldReadAllDataAndThrow() throws Exception {
|
|
||||||
ImapResponseParser parser = createParser("* {4}\r\ntest\r\n");
|
|
||||||
TestImapResponseCallback callback = TestImapResponseCallback.readBytesAndThrow(2);
|
|
||||||
|
|
||||||
try {
|
|
||||||
parser.readResponse(callback);
|
|
||||||
fail();
|
|
||||||
} catch (ImapResponseParserException e) {
|
|
||||||
assertEquals("readResponse(): Exception in callback method", e.getMessage());
|
|
||||||
assertEquals(ImapResponseParserTestException.class, e.getCause().getClass());
|
|
||||||
}
|
|
||||||
|
|
||||||
assertAllInputConsumed();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void readResponse_withCallbackThatThrowsRepeatedly_shouldConsumeAllInputAndThrowFirstException()
|
|
||||||
throws Exception {
|
|
||||||
ImapResponseParser parser = createParser("* {3}\r\none {3}\r\ntwo\r\n");
|
|
||||||
TestImapResponseCallback callback = TestImapResponseCallback.readBytesAndThrow(3);
|
|
||||||
|
|
||||||
try {
|
|
||||||
parser.readResponse(callback);
|
|
||||||
fail();
|
|
||||||
} catch (ImapResponseParserException e) {
|
|
||||||
assertEquals("readResponse(): Exception in callback method", e.getMessage());
|
|
||||||
assertEquals(ImapResponseParserTestException.class, e.getCause().getClass());
|
|
||||||
assertEquals(0, ((ImapResponseParserTestException) e.getCause()).instanceNumber);
|
|
||||||
}
|
|
||||||
|
|
||||||
assertAllInputConsumed();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testParseLiteralWithIncompleteConsumingCallbackReturningString() throws Exception {
|
|
||||||
ImapResponseParser parser = createParser("* {4}\r\ntest\r\n");
|
|
||||||
TestImapResponseCallback callback = TestImapResponseCallback.readBytesAndReturn(2, "ninja");
|
|
||||||
|
|
||||||
ImapResponse response = parser.readResponse(callback);
|
|
||||||
|
|
||||||
assertEquals(1, response.size());
|
|
||||||
assertEquals("ninja", response.getString(0));
|
|
||||||
assertAllInputConsumed();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testParseLiteralWithThrowingCallback() throws Exception {
|
|
||||||
ImapResponseParser parser = createParser("* {4}\r\ntest\r\n");
|
|
||||||
ImapResponseCallback callback = TestImapResponseCallback.readBytesAndThrow(0);
|
|
||||||
|
|
||||||
try {
|
|
||||||
parser.readResponse(callback);
|
|
||||||
fail();
|
|
||||||
} catch (ImapResponseParserException e) {
|
|
||||||
assertEquals("readResponse(): Exception in callback method", e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
assertAllInputConsumed();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = IOException.class)
|
|
||||||
public void testParseLiteralWithCallbackThrowingIOException() throws Exception {
|
|
||||||
ImapResponseParser parser = createParser("* {4}\r\ntest\r\n");
|
|
||||||
ImapResponseCallback callback = new ImapResponseCallback() {
|
|
||||||
@Override
|
|
||||||
public Object foundLiteral(ImapResponse response, FixedLengthInputStream literal) throws Exception {
|
|
||||||
throw new IOException();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
parser.readResponse(callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testParseQuoted() throws Exception {
|
|
||||||
ImapResponseParser parser = createParser("* \"qu\\\"oted\"\r\n");
|
|
||||||
|
|
||||||
ImapResponse response = parser.readResponse();
|
|
||||||
|
|
||||||
assertEquals(1, response.size());
|
|
||||||
assertEquals("qu\"oted", response.getString(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void utf8InQuotedString() throws Exception {
|
|
||||||
ImapResponseParser parser = createParser("* \"quöted\"\r\n");
|
|
||||||
|
|
||||||
ImapResponse response = parser.readResponse();
|
|
||||||
|
|
||||||
assertEquals(1, response.size());
|
|
||||||
assertEquals("quöted", response.getString(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = IOException.class)
|
|
||||||
public void testParseQuotedToEndOfStream() throws Exception {
|
|
||||||
ImapResponseParser parser = createParser("* \"abc");
|
|
||||||
|
|
||||||
parser.readResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = IOException.class)
|
|
||||||
public void testParseAtomToEndOfStream() throws Exception {
|
|
||||||
ImapResponseParser parser = createParser("* abc");
|
|
||||||
|
|
||||||
parser.readResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = IOException.class)
|
|
||||||
public void testParseUntaggedResponseWithoutSpace() throws Exception {
|
|
||||||
ImapResponseParser parser = createParser("*\r\n");
|
|
||||||
|
|
||||||
parser.readResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testListResponseContainingFolderNameWithBrackets() throws Exception {
|
|
||||||
ImapResponseParser parser = createParser("* LIST (\\HasNoChildren) \".\" [FolderName]\r\n");
|
|
||||||
|
|
||||||
ImapResponse response = parser.readResponse();
|
|
||||||
|
|
||||||
assertEquals(4, response.size());
|
|
||||||
assertEquals("LIST", response.get(0));
|
|
||||||
assertEquals(1, response.getList(1).size());
|
|
||||||
assertEquals("\\HasNoChildren", response.getList(1).getString(0));
|
|
||||||
assertEquals(".", response.get(2));
|
|
||||||
assertEquals("[FolderName]", response.get(3));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = IOException.class)
|
|
||||||
public void testListResponseContainingFolderNameContainingBracketsThrowsException() throws Exception {
|
|
||||||
ImapResponseParser parser = createParser(
|
|
||||||
"* LIST (\\NoInferiors) \"/\" Root/Folder/Subfolder()\r\n");
|
|
||||||
|
|
||||||
parser.readResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void readResponseShouldReadWholeListResponseLine() throws Exception {
|
|
||||||
ImapResponseParser parser = createParser("* LIST (\\HasNoChildren) \".\" [FolderName]\r\n" +
|
|
||||||
"TAG OK [List complete]\r\n");
|
|
||||||
parser.readResponse();
|
|
||||||
|
|
||||||
ImapResponse responseTwo = parser.readResponse();
|
|
||||||
|
|
||||||
assertEquals("TAG", responseTwo.getTag());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void readResponse_withListResponseContainingNil() throws Exception {
|
|
||||||
ImapResponseParser parser = createParser("* LIST (\\NoInferiors) NIL INBOX\r\n");
|
|
||||||
|
|
||||||
ImapResponse response = parser.readResponse();
|
|
||||||
|
|
||||||
assertEquals(4, response.size());
|
|
||||||
assertEquals("LIST", response.get(0));
|
|
||||||
assertEquals(1, response.getList(1).size());
|
|
||||||
assertEquals("\\NoInferiors", response.getList(1).getString(0));
|
|
||||||
assertEquals(null, response.get(2));
|
|
||||||
assertEquals("INBOX", response.get(3));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void readResponse_withListAsFirstToken_shouldThrow() throws Exception {
|
|
||||||
ImapResponseParser parser = createParser("* [1 2] 3\r\n");
|
|
||||||
|
|
||||||
try {
|
|
||||||
parser.readResponse();
|
|
||||||
fail("Expected exception");
|
|
||||||
} catch (IOException e) {
|
|
||||||
assertEquals("Unexpected non-string token: ImapList - [1, 2]", e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testFetchResponse() throws Exception {
|
|
||||||
ImapResponseParser parser = createParser("* 1 FETCH (" +
|
|
||||||
"UID 23 " +
|
|
||||||
"INTERNALDATE \"01-Jul-2015 12:34:56 +0200\" " +
|
|
||||||
"RFC822.SIZE 3456 " +
|
|
||||||
"BODY[HEADER.FIELDS (date subject from)] \"<headers>\" " +
|
|
||||||
"FLAGS (\\Seen))\r\n");
|
|
||||||
|
|
||||||
ImapResponse response = parser.readResponse();
|
|
||||||
|
|
||||||
assertEquals(3, response.size());
|
|
||||||
assertEquals("1", response.getString(0));
|
|
||||||
assertEquals("FETCH", response.getString(1));
|
|
||||||
assertEquals("UID", response.getList(2).getString(0));
|
|
||||||
assertEquals(23, response.getList(2).getNumber(1));
|
|
||||||
assertEquals("INTERNALDATE", response.getList(2).getString(2));
|
|
||||||
assertEquals("01-Jul-2015 12:34:56 +0200", response.getList(2).getString(3));
|
|
||||||
assertEquals("RFC822.SIZE", response.getList(2).getString(4));
|
|
||||||
assertEquals(3456, response.getList(2).getNumber(5));
|
|
||||||
assertEquals("BODY", response.getList(2).getString(6));
|
|
||||||
assertEquals(2, response.getList(2).getList(7).size());
|
|
||||||
assertEquals("HEADER.FIELDS", response.getList(2).getList(7).getString(0));
|
|
||||||
assertEquals(3, response.getList(2).getList(7).getList(1).size());
|
|
||||||
assertEquals("date", response.getList(2).getList(7).getList(1).getString(0));
|
|
||||||
assertEquals("subject", response.getList(2).getList(7).getList(1).getString(1));
|
|
||||||
assertEquals("from", response.getList(2).getList(7).getList(1).getString(2));
|
|
||||||
assertEquals("<headers>", response.getList(2).getString(8));
|
|
||||||
assertEquals("FLAGS", response.getList(2).getString(9));
|
|
||||||
assertEquals(1, response.getList(2).getList(10).size());
|
|
||||||
assertEquals("\\Seen", response.getList(2).getList(10).getString(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void readStatusResponse_withNoResponse_shouldThrow() throws Exception {
|
|
||||||
ImapResponseParser parser = createParser("1 NO\r\n");
|
|
||||||
|
|
||||||
try {
|
|
||||||
parser.readStatusResponse("1", "COMMAND", "[logId]", null);
|
|
||||||
fail("Expected exception");
|
|
||||||
} catch (NegativeImapResponseException e) {
|
|
||||||
assertEquals("Command: COMMAND; response: #1# [NO]", e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void readStatusResponse_withNoResponseAndAlertText_shouldThrowWithAlertText() throws Exception {
|
|
||||||
ImapResponseParser parser = createParser("1 NO [ALERT] Access denied\r\n");
|
|
||||||
|
|
||||||
try {
|
|
||||||
parser.readStatusResponse("1", "COMMAND", "[logId]", null);
|
|
||||||
fail("Expected exception");
|
|
||||||
} catch (NegativeImapResponseException e) {
|
|
||||||
assertEquals("Access denied", e.getAlertText());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ImapResponseParser createParser(String response) {
|
|
||||||
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(response.getBytes(Charsets.UTF_8));
|
|
||||||
peekableInputStream = new PeekableInputStream(byteArrayInputStream);
|
|
||||||
return new ImapResponseParser(peekableInputStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assertAllInputConsumed() throws IOException {
|
|
||||||
assertEquals(0, peekableInputStream.available());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static class TestImapResponseCallback implements ImapResponseCallback {
|
|
||||||
private final int readNumberOfBytes;
|
|
||||||
private final Object returnValue;
|
|
||||||
private final boolean throwException;
|
|
||||||
private int exceptionCount = 0;
|
|
||||||
public boolean foundLiteralCalled = false;
|
|
||||||
|
|
||||||
public static TestImapResponseCallback readBytesAndReturn(int readNumberOfBytes, Object returnValue) {
|
|
||||||
return new TestImapResponseCallback(readNumberOfBytes, returnValue, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TestImapResponseCallback readBytesAndThrow(int readNumberOfBytes) {
|
|
||||||
return new TestImapResponseCallback(readNumberOfBytes, null, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private TestImapResponseCallback(int readNumberOfBytes, Object returnValue, boolean throwException) {
|
|
||||||
this.readNumberOfBytes = readNumberOfBytes;
|
|
||||||
this.returnValue = returnValue;
|
|
||||||
this.throwException = throwException;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object foundLiteral(ImapResponse response, FixedLengthInputStream literal) throws Exception {
|
|
||||||
foundLiteralCalled = true;
|
|
||||||
|
|
||||||
int skipBytes = readNumberOfBytes;
|
|
||||||
while (skipBytes > 0) {
|
|
||||||
long skippedBytes = literal.skip(skipBytes);
|
|
||||||
skipBytes -= skippedBytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (throwException) {
|
|
||||||
throw new ImapResponseParserTestException(exceptionCount++);
|
|
||||||
}
|
|
||||||
|
|
||||||
return returnValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static class ImapResponseParserTestException extends RuntimeException {
|
|
||||||
public final int instanceNumber;
|
|
||||||
|
|
||||||
public ImapResponseParserTestException(int instanceNumber) {
|
|
||||||
this.instanceNumber = instanceNumber;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static class TestUntaggedHandler implements UntaggedHandler {
|
|
||||||
public final List<ImapResponse> responses = new ArrayList<>();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handleAsyncUntaggedResponse(ImapResponse response) {
|
|
||||||
responses.add(response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,585 @@
|
||||||
|
package com.fsck.k9.mail.store.imap
|
||||||
|
|
||||||
|
import assertk.all
|
||||||
|
import assertk.assertThat
|
||||||
|
import assertk.assertions.cause
|
||||||
|
import assertk.assertions.containsExactly
|
||||||
|
import assertk.assertions.hasMessage
|
||||||
|
import assertk.assertions.hasSize
|
||||||
|
import assertk.assertions.index
|
||||||
|
import assertk.assertions.isEqualTo
|
||||||
|
import assertk.assertions.isFailure
|
||||||
|
import assertk.assertions.isInstanceOf
|
||||||
|
import assertk.assertions.isNotNull
|
||||||
|
import assertk.assertions.isNull
|
||||||
|
import assertk.assertions.isTrue
|
||||||
|
import assertk.assertions.prop
|
||||||
|
import com.fsck.k9.mail.filter.FixedLengthInputStream
|
||||||
|
import com.fsck.k9.mail.filter.PeekableInputStream
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
|
import java.io.IOException
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class ImapResponseParserTest {
|
||||||
|
private var peekableInputStream: PeekableInputStream? = null
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `readResponse() with untagged OK response`() {
|
||||||
|
val parser = createParserWithResponses("* OK")
|
||||||
|
|
||||||
|
val response = parser.readResponse()
|
||||||
|
|
||||||
|
assertThat(response).containsExactly("OK")
|
||||||
|
assertThatAllInputWasConsumed()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `readResponse() with untagged OK response containing text`() {
|
||||||
|
val parser = createParserWithResponses("* OK Some text here")
|
||||||
|
|
||||||
|
val response = parser.readResponse()
|
||||||
|
|
||||||
|
assertThat(response).containsExactly("OK", "Some text here")
|
||||||
|
assertThatAllInputWasConsumed()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `readResponse() with untagged OK response containing resp-text code`() {
|
||||||
|
val parser = createParserWithResponses("* OK [UIDVALIDITY 3857529045]")
|
||||||
|
|
||||||
|
val response = parser.readResponse()
|
||||||
|
|
||||||
|
assertThat(response).hasSize(2)
|
||||||
|
assertThat(response).index(0).isEqualTo("OK")
|
||||||
|
assertThat(response).index(1).isInstanceOf(ImapList::class).containsExactly("UIDVALIDITY", "3857529045")
|
||||||
|
assertThatAllInputWasConsumed()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `readResponse() with untagged OK response containing resp-text code and text`() {
|
||||||
|
val parser = createParserWithResponses("* OK [token1 token2] {x} test [...]")
|
||||||
|
|
||||||
|
val response = parser.readResponse()
|
||||||
|
|
||||||
|
assertThat(response).hasSize(3)
|
||||||
|
assertThat(response).index(0).isEqualTo("OK")
|
||||||
|
assertThat(response).index(1).isInstanceOf(ImapList::class).containsExactly("token1", "token2")
|
||||||
|
assertThat(response).index(2).isEqualTo("{x} test [...]")
|
||||||
|
assertThatAllInputWasConsumed()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `readStatusResponse() with OK response`() {
|
||||||
|
val parser = createParserWithResponses(
|
||||||
|
"* COMMAND BAR\tBAZ",
|
||||||
|
"TAG OK COMMAND completed",
|
||||||
|
)
|
||||||
|
|
||||||
|
val responses = parser.readStatusResponse("TAG", null, null, null)
|
||||||
|
|
||||||
|
assertThat(responses).hasSize(2)
|
||||||
|
assertThat(responses).index(0).containsExactly("COMMAND", "BAR", "BAZ")
|
||||||
|
assertThat(responses).index(1).containsExactly("OK", "COMMAND completed")
|
||||||
|
assertThatAllInputWasConsumed()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `readStatusResponse() should only deliver untagged responses to UntaggedHandler`() {
|
||||||
|
val parser = createParserWithResponses(
|
||||||
|
"* UNTAGGED",
|
||||||
|
"A2 OK COMMAND completed",
|
||||||
|
)
|
||||||
|
val untaggedHandler = TestUntaggedHandler()
|
||||||
|
|
||||||
|
parser.readStatusResponse("A2", null, null, untaggedHandler)
|
||||||
|
|
||||||
|
assertThat(untaggedHandler.responses).hasSize(1)
|
||||||
|
assertThat(untaggedHandler.responses).index(0).containsExactly("UNTAGGED")
|
||||||
|
assertThatAllInputWasConsumed()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `readStatusResponse() should skip tagged response that does not match tag`() {
|
||||||
|
val parser = createParserWithResponses(
|
||||||
|
"* UNTAGGED",
|
||||||
|
"* 0 EXPUNGE",
|
||||||
|
"* 42 EXISTS",
|
||||||
|
"A1 COMMAND BAR BAZ",
|
||||||
|
"A2 OK COMMAND completed",
|
||||||
|
)
|
||||||
|
val untaggedHandler = TestUntaggedHandler()
|
||||||
|
|
||||||
|
val responses = parser.readStatusResponse("A2", null, null, untaggedHandler)
|
||||||
|
|
||||||
|
assertThat(responses).hasSize(3)
|
||||||
|
assertThat(responses).index(0).containsExactly("0", "EXPUNGE")
|
||||||
|
assertThat(responses).index(1).containsExactly("42", "EXISTS")
|
||||||
|
assertThat(responses).index(2).containsExactly("OK", "COMMAND completed")
|
||||||
|
|
||||||
|
assertThat(untaggedHandler.responses).hasSize(3)
|
||||||
|
assertThat(untaggedHandler.responses).index(0).containsExactly("UNTAGGED")
|
||||||
|
assertThat(untaggedHandler.responses).index(1).containsExactly("0", "EXPUNGE")
|
||||||
|
assertThat(untaggedHandler.responses).index(2).containsExactly("42", "EXISTS")
|
||||||
|
assertThatAllInputWasConsumed()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `readStatusResponse() should deliver untagged responses to UntaggedHandler even on negative tagged response`() {
|
||||||
|
val parser = createParserWithResponses(
|
||||||
|
"* untagged",
|
||||||
|
"A2 NO Bad response",
|
||||||
|
)
|
||||||
|
val untaggedHandler = TestUntaggedHandler()
|
||||||
|
|
||||||
|
try {
|
||||||
|
parser.readStatusResponse("A2", null, null, untaggedHandler)
|
||||||
|
} catch (ignored: NegativeImapResponseException) {
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThat(untaggedHandler.responses).hasSize(1)
|
||||||
|
assertThat(untaggedHandler.responses).index(0).containsExactly("untagged")
|
||||||
|
assertThatAllInputWasConsumed()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `readStatusResponse() with error response should throw`() {
|
||||||
|
val parser = createParserWithResponses(
|
||||||
|
"* COMMAND BAR BAZ",
|
||||||
|
"TAG ERROR COMMAND errored",
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat {
|
||||||
|
parser.readStatusResponse("TAG", null, null, null)
|
||||||
|
}.isFailure()
|
||||||
|
.isInstanceOf(NegativeImapResponseException::class)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `readResponse() with resp-text code containing a list`() {
|
||||||
|
val parser = createParserWithResponses(
|
||||||
|
"""* OK [PERMANENTFLAGS (\Answered \Flagged \Deleted \Seen \Draft NonJunk ${"$"}MDNSent \*)] """ +
|
||||||
|
"Flags permitted.",
|
||||||
|
)
|
||||||
|
|
||||||
|
val response = parser.readResponse()
|
||||||
|
|
||||||
|
assertThat(response).hasSize(3)
|
||||||
|
assertThat(response).index(0).isEqualTo("OK")
|
||||||
|
assertThat(response).index(1).isInstanceOf(ImapList::class).all {
|
||||||
|
index(0).isEqualTo("PERMANENTFLAGS")
|
||||||
|
index(1).isInstanceOf(ImapList::class).containsExactly(
|
||||||
|
"""\Answered""",
|
||||||
|
"""\Flagged""",
|
||||||
|
"""\Deleted""",
|
||||||
|
"""\Seen""",
|
||||||
|
"""\Draft""",
|
||||||
|
"NonJunk",
|
||||||
|
"\$MDNSent",
|
||||||
|
"""\*""",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThat(response).index(2).isEqualTo("Flags permitted.")
|
||||||
|
assertThatAllInputWasConsumed()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `readResponse() with untagged EXISTS response`() {
|
||||||
|
val parser = createParserWithResponses("* 23 EXISTS")
|
||||||
|
|
||||||
|
val response = parser.readResponse()
|
||||||
|
|
||||||
|
assertThat(response).hasSize(2)
|
||||||
|
assertThat(response).transform { it.getNumber(0) }.isEqualTo(23)
|
||||||
|
assertThat(response).transform { it.getString(1) }.isEqualTo("EXISTS")
|
||||||
|
assertThatAllInputWasConsumed()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `readResponse() should throw if stream ends before end of line is found`() {
|
||||||
|
val parser = createParserWithData("* OK Some text ")
|
||||||
|
|
||||||
|
assertThat {
|
||||||
|
parser.readResponse()
|
||||||
|
}.isFailure()
|
||||||
|
.isInstanceOf(IOException::class)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `readResponse() with command continuation`() {
|
||||||
|
val parser = createParserWithResponses("+ Ready for additional command text")
|
||||||
|
|
||||||
|
val response = parser.readResponse()
|
||||||
|
|
||||||
|
assertThat(response.isContinuationRequested).isTrue()
|
||||||
|
assertThat(response).containsExactly("Ready for additional command text")
|
||||||
|
assertThatAllInputWasConsumed()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `readResponse() with literal`() {
|
||||||
|
val parser = createParserWithResponses("* {4}\r\ntest")
|
||||||
|
|
||||||
|
val response = parser.readResponse()
|
||||||
|
|
||||||
|
assertThat(response).containsExactly("test")
|
||||||
|
assertThatAllInputWasConsumed()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `readResponse() with empty literal`() {
|
||||||
|
val parser = createParserWithResponses("* {0}\r\n")
|
||||||
|
|
||||||
|
val response = parser.readResponse()
|
||||||
|
|
||||||
|
assertThat(response).containsExactly("")
|
||||||
|
assertThatAllInputWasConsumed()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `readResponse() should throw when end of stream is reached while reading literal`() {
|
||||||
|
val parser = createParserWithData("* {4}\r\nabc")
|
||||||
|
|
||||||
|
assertThat {
|
||||||
|
parser.readResponse()
|
||||||
|
}.isFailure()
|
||||||
|
.isInstanceOf(IOException::class)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `readResponse() with literal should include return value of ImapResponseCallback_foundLiteral() in response`() {
|
||||||
|
val parser = createParserWithResponses("* {4}\r\ntest")
|
||||||
|
val callback = TestImapResponseCallback.readBytesAndReturn(4, "replacement value")
|
||||||
|
|
||||||
|
val response = parser.readResponse(callback)
|
||||||
|
|
||||||
|
assertThat(response).containsExactly("replacement value")
|
||||||
|
assertThatAllInputWasConsumed()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `readResponse() with literal should read literal when ImapResponseCallback_foundLiteral() returns null`() {
|
||||||
|
val parser = createParserWithResponses("* {4}\r\ntest")
|
||||||
|
val callback = TestImapResponseCallback.readBytesAndReturn(0, null)
|
||||||
|
|
||||||
|
val response = parser.readResponse(callback)
|
||||||
|
|
||||||
|
assertThat(response).containsExactly("test")
|
||||||
|
assertThat(callback.foundLiteralCalled).isTrue()
|
||||||
|
assertThatAllInputWasConsumed()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `readResponse() with partly consuming callback returning null should throw`() {
|
||||||
|
val parser = createParserWithResponses("* {4}\r\ntest")
|
||||||
|
val callback = TestImapResponseCallback.readBytesAndReturn(2, null)
|
||||||
|
|
||||||
|
assertThat {
|
||||||
|
parser.readResponse(callback)
|
||||||
|
}.isFailure()
|
||||||
|
.isInstanceOf(AssertionError::class)
|
||||||
|
.hasMessage("Callback consumed some data but returned no result")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `readResponse() with partly consuming callback that throws should read all data and throw`() {
|
||||||
|
val parser = createParserWithResponses("* {4}\r\ntest")
|
||||||
|
val callback = TestImapResponseCallback.readBytesAndThrow(2)
|
||||||
|
|
||||||
|
assertThat {
|
||||||
|
parser.readResponse(callback)
|
||||||
|
}.isFailure()
|
||||||
|
.isInstanceOf(ImapResponseParserException::class)
|
||||||
|
.all {
|
||||||
|
hasMessage("readResponse(): Exception in callback method")
|
||||||
|
cause().isNotNull().isInstanceOf(ImapResponseParserTestException::class)
|
||||||
|
}
|
||||||
|
assertThatAllInputWasConsumed()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `readResponse() with callback that throws repeatedly should consume all input and throw first exception`() {
|
||||||
|
val parser = createParserWithResponses("* {3}\r\none {3}\r\ntwo")
|
||||||
|
val callback = TestImapResponseCallback.readBytesAndThrow(3)
|
||||||
|
|
||||||
|
assertThat {
|
||||||
|
parser.readResponse(callback)
|
||||||
|
}.isFailure()
|
||||||
|
.isInstanceOf(ImapResponseParserException::class)
|
||||||
|
.all {
|
||||||
|
hasMessage("readResponse(): Exception in callback method")
|
||||||
|
cause().isNotNull().isInstanceOf(ImapResponseParserTestException::class)
|
||||||
|
.prop(ImapResponseParserTestException::instanceNumber).isEqualTo(0)
|
||||||
|
}
|
||||||
|
assertThatAllInputWasConsumed()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `readResponse() with callback not consuming the entire literal should skip the rest of the literal`() {
|
||||||
|
val parser = createParserWithResponses("* {3}\r\none two")
|
||||||
|
val callback = TestImapResponseCallback.readBytesAndReturn(2, "replacement value")
|
||||||
|
|
||||||
|
val response = parser.readResponse(callback)
|
||||||
|
|
||||||
|
assertThat(response).containsExactly("replacement value", "two")
|
||||||
|
assertThatAllInputWasConsumed()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `readResponse() with callback not consuming and throwing should read response and throw`() {
|
||||||
|
val parser = createParserWithResponses("* {4}\r\ntest")
|
||||||
|
val callback = TestImapResponseCallback.readBytesAndThrow(0)
|
||||||
|
|
||||||
|
assertThat {
|
||||||
|
parser.readResponse(callback)
|
||||||
|
}.isFailure()
|
||||||
|
.isInstanceOf(ImapResponseParserException::class)
|
||||||
|
.hasMessage("readResponse(): Exception in callback method")
|
||||||
|
assertThatAllInputWasConsumed()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `readResponse() with callback throwing IOException should re-throw that exception`() {
|
||||||
|
val parser = createParserWithResponses("* {4}\r\ntest")
|
||||||
|
val exception = IOException()
|
||||||
|
val callback = ImapResponseCallback { _, _ -> throw exception }
|
||||||
|
|
||||||
|
assertThat {
|
||||||
|
parser.readResponse(callback)
|
||||||
|
}.isFailure()
|
||||||
|
.isEqualTo(exception)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `readResponse() with quoted string containing an escaped quote character`() {
|
||||||
|
val parser = createParserWithResponses("""* "qu\"oted"""")
|
||||||
|
|
||||||
|
val response = parser.readResponse()
|
||||||
|
|
||||||
|
assertThat(response).containsExactly("""qu"oted""")
|
||||||
|
assertThatAllInputWasConsumed()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `readResponse() with UTF-8 data in quoted string`() {
|
||||||
|
val parser = createParserWithResponses("""* "quöted"""")
|
||||||
|
|
||||||
|
val response = parser.readResponse()
|
||||||
|
|
||||||
|
assertThat(response).containsExactly("quöted")
|
||||||
|
assertThatAllInputWasConsumed()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `readResponse() should throw when end of stream is reached before end of quoted string`() {
|
||||||
|
val parser = createParserWithResponses("* \"abc")
|
||||||
|
|
||||||
|
assertThat {
|
||||||
|
parser.readResponse()
|
||||||
|
}.isFailure()
|
||||||
|
.isInstanceOf(IOException::class)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `readResponse() should throw if end of stream is reached before end of atom`() {
|
||||||
|
val parser = createParserWithData("* abc")
|
||||||
|
|
||||||
|
assertThat {
|
||||||
|
parser.readResponse()
|
||||||
|
}.isFailure()
|
||||||
|
.isInstanceOf(IOException::class)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `readResponse() should throw if untagged response indicator is not followed by a space`() {
|
||||||
|
val parser = createParserWithResponses("*")
|
||||||
|
|
||||||
|
assertThat {
|
||||||
|
parser.readResponse()
|
||||||
|
}.isFailure()
|
||||||
|
.isInstanceOf(IOException::class)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `readResponse() with LIST response containing folder name with brackets`() {
|
||||||
|
val parser = createParserWithResponses("""* LIST (\HasNoChildren) "." [FolderName]""")
|
||||||
|
|
||||||
|
val response = parser.readResponse()
|
||||||
|
|
||||||
|
assertThat(response).hasSize(4)
|
||||||
|
assertThat(response).index(0).isEqualTo("LIST")
|
||||||
|
assertThat(response).index(1).isInstanceOf(ImapList::class).containsExactly("""\HasNoChildren""")
|
||||||
|
assertThat(response).index(2).isEqualTo(".")
|
||||||
|
assertThat(response).index(3).isEqualTo("[FolderName]")
|
||||||
|
assertThatAllInputWasConsumed()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `readResponse() with LIST response containing folder name with parentheses should throw`() {
|
||||||
|
val parser = createParserWithResponses("""* LIST (\NoInferiors) "/" Root/Folder/Subfolder()""")
|
||||||
|
|
||||||
|
assertThat {
|
||||||
|
parser.readResponse()
|
||||||
|
}.isFailure()
|
||||||
|
.isInstanceOf(IOException::class)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `readResponse() should read whole LIST response line`() {
|
||||||
|
val parser = createParserWithResponses(
|
||||||
|
"""* LIST (\HasNoChildren) "." [FolderName]""",
|
||||||
|
"TAG OK [List complete]",
|
||||||
|
)
|
||||||
|
parser.readResponse()
|
||||||
|
|
||||||
|
val responseTwo = parser.readResponse()
|
||||||
|
|
||||||
|
assertThat(responseTwo.tag).isEqualTo("TAG")
|
||||||
|
assertThatAllInputWasConsumed()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `readResponse() with LIST response containing NIL`() {
|
||||||
|
val parser = createParserWithResponses("""* LIST (\NoInferiors) NIL INBOX""")
|
||||||
|
|
||||||
|
val response = parser.readResponse()
|
||||||
|
|
||||||
|
assertThat(response).hasSize(4)
|
||||||
|
assertThat(response).index(0).isEqualTo("LIST")
|
||||||
|
assertThat(response).index(1).isInstanceOf(ImapList::class).containsExactly("""\NoInferiors""")
|
||||||
|
assertThat(response).index(2).isNull()
|
||||||
|
assertThat(response).index(3).isEqualTo("INBOX")
|
||||||
|
assertThatAllInputWasConsumed()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `readResponse() with list as first token should throw`() {
|
||||||
|
val parser = createParserWithResponses("* [1 2] 3")
|
||||||
|
|
||||||
|
assertThat {
|
||||||
|
parser.readResponse()
|
||||||
|
}.isFailure()
|
||||||
|
.isInstanceOf(IOException::class)
|
||||||
|
.hasMessage("Unexpected non-string token: ImapList - [1, 2]")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `readResponse() with FETCH response`() {
|
||||||
|
val parser = createParserWithResponses(
|
||||||
|
"* 1 FETCH (" +
|
||||||
|
"UID 23 " +
|
||||||
|
"""INTERNALDATE "01-Jul-2015 12:34:56 +0200" """ +
|
||||||
|
"RFC822.SIZE 3456 " +
|
||||||
|
"""BODY[HEADER.FIELDS (date subject from)] "<headers>" """ +
|
||||||
|
"""FLAGS (\Seen)""" +
|
||||||
|
")",
|
||||||
|
)
|
||||||
|
|
||||||
|
val response = parser.readResponse()
|
||||||
|
|
||||||
|
assertThat(response).hasSize(3)
|
||||||
|
assertThat(response).index(0).isEqualTo("1")
|
||||||
|
assertThat(response).index(1).isEqualTo("FETCH")
|
||||||
|
assertThat(response).index(2).isInstanceOf(ImapList::class).all {
|
||||||
|
hasSize(11)
|
||||||
|
index(0).isEqualTo("UID")
|
||||||
|
index(1).isEqualTo("23")
|
||||||
|
index(2).isEqualTo("INTERNALDATE")
|
||||||
|
index(3).isEqualTo("01-Jul-2015 12:34:56 +0200")
|
||||||
|
index(4).isEqualTo("RFC822.SIZE")
|
||||||
|
index(5).isEqualTo("3456")
|
||||||
|
index(6).isEqualTo("BODY")
|
||||||
|
index(7).isInstanceOf(ImapList::class).all {
|
||||||
|
hasSize(2)
|
||||||
|
index(0).isEqualTo("HEADER.FIELDS")
|
||||||
|
index(1).isInstanceOf(ImapList::class).containsExactly("date", "subject", "from")
|
||||||
|
}
|
||||||
|
index(8).isEqualTo("<headers>")
|
||||||
|
index(9).isEqualTo("FLAGS")
|
||||||
|
index(10).isInstanceOf(ImapList::class).containsExactly("""\Seen""")
|
||||||
|
}
|
||||||
|
assertThatAllInputWasConsumed()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `readStatusResponse() with NO response should throw`() {
|
||||||
|
val parser = createParserWithResponses("1 NO")
|
||||||
|
|
||||||
|
assertThat {
|
||||||
|
parser.readStatusResponse("1", "COMMAND", "[logId]", null)
|
||||||
|
}.isFailure()
|
||||||
|
.isInstanceOf(NegativeImapResponseException::class)
|
||||||
|
.hasMessage("Command: COMMAND; response: #1# [NO]")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `readStatusResponse() with NO response and alert text should throw with alert text`() {
|
||||||
|
val parser = createParserWithResponses("1 NO [ALERT] Access denied\r\n")
|
||||||
|
|
||||||
|
assertThat {
|
||||||
|
parser.readStatusResponse("1", "COMMAND", "[logId]", null)
|
||||||
|
}.isFailure()
|
||||||
|
.isInstanceOf(NegativeImapResponseException::class)
|
||||||
|
.prop(NegativeImapResponseException::getAlertText).isEqualTo("Access denied")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createParserWithResponses(vararg responses: String): ImapResponseParser {
|
||||||
|
val response = responses.joinToString(separator = "\r\n", postfix = "\r\n")
|
||||||
|
return createParserWithData(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createParserWithData(response: String): ImapResponseParser {
|
||||||
|
val byteArrayInputStream = ByteArrayInputStream(response.toByteArray(Charsets.UTF_8))
|
||||||
|
peekableInputStream = PeekableInputStream(byteArrayInputStream)
|
||||||
|
|
||||||
|
return ImapResponseParser(peekableInputStream)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assertThatAllInputWasConsumed() {
|
||||||
|
assertThat(peekableInputStream).isNotNull().prop(PeekableInputStream::available).isEqualTo(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestImapResponseCallback(
|
||||||
|
private val readNumberOfBytes: Long,
|
||||||
|
private val returnValue: Any?,
|
||||||
|
private val throwException: Boolean,
|
||||||
|
) : ImapResponseCallback {
|
||||||
|
private var exceptionCount = 0
|
||||||
|
var foundLiteralCalled = false
|
||||||
|
|
||||||
|
override fun foundLiteral(response: ImapResponse, literal: FixedLengthInputStream): Any? {
|
||||||
|
foundLiteralCalled = true
|
||||||
|
|
||||||
|
var skipBytes = readNumberOfBytes
|
||||||
|
while (skipBytes > 0) {
|
||||||
|
skipBytes -= literal.skip(skipBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (throwException) {
|
||||||
|
throw ImapResponseParserTestException(exceptionCount++)
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnValue
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun readBytesAndReturn(readNumberOfBytes: Int, returnValue: Any?): TestImapResponseCallback {
|
||||||
|
return TestImapResponseCallback(readNumberOfBytes.toLong(), returnValue, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun readBytesAndThrow(readNumberOfBytes: Int): TestImapResponseCallback {
|
||||||
|
return TestImapResponseCallback(readNumberOfBytes.toLong(), null, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ImapResponseParserTestException(val instanceNumber: Int) : RuntimeException()
|
||||||
|
|
||||||
|
private class TestUntaggedHandler : UntaggedHandler {
|
||||||
|
val responses = mutableListOf<ImapResponse>()
|
||||||
|
|
||||||
|
override fun handleAsyncUntaggedResponse(response: ImapResponse) {
|
||||||
|
responses.add(response)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue